chromium/chrome/browser/ash/extensions/file_manager/search_by_pattern.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/extensions/file_manager/search_by_pattern.h"

#include "base/files/file_enumerator.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "chrome/browser/ash/fileapi/recent_disk_source.h"
#include "third_party/abseil-cpp/absl/strings/ascii.h"

namespace extensions {

std::string CreateFnmatchQuery(const std::string& query) {
  std::vector<std::string> query_pieces = {"*"};
  size_t sequence_start = 0;
  for (size_t i = 0; i < query.size(); ++i) {
    if (absl::ascii_isalpha(static_cast<unsigned char>(query[i]))) {
      if (sequence_start != i) {
        query_pieces.push_back(
            query.substr(sequence_start, i - sequence_start));
      }
      std::string piece("[");
      piece.resize(4);
      piece[1] = absl::ascii_tolower(static_cast<unsigned char>(query[i]));
      piece[2] = absl::ascii_toupper(static_cast<unsigned char>(query[i]));
      piece[3] = ']';
      query_pieces.push_back(std::move(piece));
      sequence_start = i + 1;
    }
  }
  if (sequence_start != query.size()) {
    query_pieces.push_back(query.substr(sequence_start));
  }
  if (query_pieces.size() > 1) {
    query_pieces.push_back("*");
  }

  return base::StrCat(query_pieces);
}

std::vector<std::pair<base::FilePath, bool>> SearchByPattern(
    const base::FilePath& root,
    const std::vector<base::FilePath>& excluded_paths,
    const std::string& query,
    const base::Time& min_timestamp,
    ash::RecentSource::FileType file_type,
    size_t max_results) {
  std::vector<std::pair<base::FilePath, bool>> prefix_matches;
  std::vector<std::pair<base::FilePath, bool>> other_matches;

  base::FileEnumerator enumerator(
      root, true,
      base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES,
      CreateFnmatchQuery(query), base::FileEnumerator::FolderSearchPolicy::ALL);

  for (base::FilePath path = enumerator.Next(); !path.empty();
       path = enumerator.Next()) {
    if (enumerator.GetInfo().GetLastModifiedTime() < min_timestamp) {
      continue;
    }
    if (!ash::RecentDiskSource::MatchesFileType(path, file_type)) {
      continue;
    }
    // Reject files that have path in excluded paths.
    if (base::ranges::any_of(
            excluded_paths, [&path](const base::FilePath& excluded_path) {
              DCHECK(!path.EndsWithSeparator());
              DCHECK(!excluded_path.EndsWithSeparator());
              return excluded_path == path || excluded_path.IsParent(path);
            })) {
      continue;
    }
    if (base::StartsWith(path.BaseName().value(), query,
                         base::CompareCase::INSENSITIVE_ASCII)) {
      prefix_matches.emplace_back(path, enumerator.GetInfo().IsDirectory());
      if (max_results && prefix_matches.size() == max_results) {
        return prefix_matches;
      }
      continue;
    }
    if (!max_results ||
        prefix_matches.size() + other_matches.size() < max_results) {
      other_matches.emplace_back(path, enumerator.GetInfo().IsDirectory());
    }
  }
  prefix_matches.insert(
      prefix_matches.end(), other_matches.begin(),
      other_matches.begin() +
          std::min(max_results - prefix_matches.size(), other_matches.size()));

  return prefix_matches;
}

}  // namespace extensions