Future search()

in app/lib/search/mem_index.dart [128:275]


  Future<PackageSearchResult> search(ServiceSearchQuery query) async {
    final Set<String> packages = Set.from(_packages.keys);

    // filter on package prefix
    if (query.parsedQuery.packagePrefix != null) {
      final String prefix = query.parsedQuery.packagePrefix!.toLowerCase();
      packages.removeWhere(
        (package) =>
            !_packages[package]!.package.toLowerCase().startsWith(prefix),
      );
    }

    // filter on tags
    final combinedTagsPredicate =
        query.tagsPredicate.appendPredicate(query.parsedQuery.tagsPredicate);
    if (combinedTagsPredicate.isNotEmpty) {
      packages.retainWhere(
          (package) => combinedTagsPredicate.matches(_packages[package]!.tags));
    }

    // filter on dependency
    if (query.parsedQuery.hasAnyDependency) {
      packages.removeWhere((package) {
        final doc = _packages[package]!;
        if (doc.dependencies.isEmpty) return true;
        for (String dependency in query.parsedQuery.allDependencies) {
          if (!doc.dependencies.containsKey(dependency)) return true;
        }
        for (String dependency in query.parsedQuery.refDependencies) {
          final type = doc.dependencies[dependency];
          if (type == null || type == DependencyTypes.transitive) return true;
        }
        return false;
      });
    }

    // filter on publisher
    if (query.publisherId != null || query.parsedQuery.publisher != null) {
      final publisherId = query.publisherId ?? query.parsedQuery.publisher;
      packages.removeWhere((package) {
        final doc = _packages[package]!;
        return doc.publisherId != publisherId;
      });
    }

    // filter on points
    if (query.minPoints != null && query.minPoints! > 0) {
      packages.removeWhere((package) {
        final doc = _packages[package]!;
        return (doc.grantedPoints ?? 0) < query.minPoints!;
      });
    }

    // filter on updatedInDays
    if (query.updatedInDays != null && query.updatedInDays! > 0) {
      final threshold =
          Duration(days: query.updatedInDays!, hours: 11, minutes: 59);
      final now = clock.now();
      packages.removeWhere((package) {
        final doc = _packages[package]!;
        final diff = now.difference(doc.updated!);
        return diff > threshold;
      });
    }

    PackageHit? highlightedHit;
    if (query.considerHighlightedHit) {
      final queryText = query.parsedQuery.text;
      final matchingPackage =
          _packages[queryText] ?? _packages[queryText!.toLowerCase()];

      if (matchingPackage != null) {
        // Remove higlighted package from the final packages set.
        packages.remove(matchingPackage.package);

        // higlight only if we are on the first page
        if (query.includeHighlightedHit) {
          highlightedHit = PackageHit(package: matchingPackage.package);
        }
      }
    }

    // do text matching
    final textResults = _searchText(
      packages,
      query.parsedQuery.text,
      hasHighlightedHit: highlightedHit != null,
    );

    // filter packages that doesn't match text query
    if (textResults != null) {
      final keys = textResults.pkgScore.getKeys();
      packages.removeWhere((x) => !keys.contains(x));
    }

    late List<PackageHit> packageHits;
    switch (query.order ?? SearchOrder.top) {
      case SearchOrder.top:
        final List<Score> scores = [
          _getOverallScore(packages),
          if (textResults != null) textResults.pkgScore,
        ];
        final overallScore = Score.multiply(scores);
        packageHits = _rankWithValues(overallScore.getValues());
        break;
      case SearchOrder.text:
        final score = textResults?.pkgScore ?? Score.empty();
        packageHits = _rankWithValues(score.getValues());
        break;
      case SearchOrder.created:
        packageHits = _rankWithComparator(packages, _compareCreated);
        break;
      case SearchOrder.updated:
        packageHits = _rankWithComparator(packages, _compareUpdated);
        break;
      case SearchOrder.popularity:
        packageHits = _rankWithValues(getPopularityScore(packages));
        break;
      case SearchOrder.like:
        packageHits = _rankWithValues(getLikeScore(packages));
        break;
      case SearchOrder.points:
        packageHits = _rankWithValues(getPubPoints(packages));
        break;
    }

    // bound by offset and limit (or randomize items)
    final totalCount = packageHits.length + (highlightedHit == null ? 0 : 1);
    packageHits =
        boundedList(packageHits, offset: query.offset, limit: query.limit);

    if (textResults != null && textResults.topApiPages.isNotEmpty) {
      packageHits = packageHits.map((ps) {
        final apiPages = textResults.topApiPages[ps.package]
            // TODO: extract title for the page
            ?.map((String page) => ApiPageRef(path: page))
            .toList();
        return ps.change(apiPages: apiPages);
      }).toList();
    }

    return PackageSearchResult(
      timestamp: clock.now().toUtc(),
      totalCount: totalCount,
      highlightedHit: highlightedHit,
      packageHits: packageHits,
    );
  }