blob: e55500bd18d1eb9fc8bf10ce11012bbdfae27966 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/app_list/search/files/file_search_provider.h"
#include <cctype>
#include <cmath>
#include "base/files/file_enumerator.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/app_list/search/files/file_result.h"
namespace app_list {
namespace {
using chromeos::string_matching::TokenizedString;
constexpr char kFileSearchSchema[] = "file_search://";
constexpr int kMaxResults = 25;
constexpr int kSearchTimeoutMs = 100;
// Construct a case-insensitive fnmatch query from |query|. E.g. for abc123, the
// result would be *[aA][bB][cC]123*.
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 (isalpha(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] = tolower(query[i]);
piece[2] = toupper(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));
}
query_pieces.push_back("*");
return base::StrCat(query_pieces);
}
// Returns a vector of matched filepaths and a bool indicating whether or not
// the path is a directory.
std::vector<FileSearchProvider::PathInfo> SearchFilesByPattern(
const base::FilePath& root_path,
const std::string& query,
const base::TimeTicks& query_start_time) {
base::FileEnumerator enumerator(
root_path,
/*recursive=*/true,
base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES,
CreateFnmatchQuery(query), base::FileEnumerator::FolderSearchPolicy::ALL);
const auto time_limit = base::TimeDelta::FromMilliseconds(kSearchTimeoutMs);
bool timed_out = false;
std::vector<FileSearchProvider::PathInfo> matched_paths;
for (base::FilePath path = enumerator.Next(); !path.empty();
path = enumerator.Next()) {
matched_paths.emplace_back(
path, enumerator.GetInfo().IsDirectory(),
base::Time::FromTimeT(enumerator.GetInfo().stat().st_atime));
if (matched_paths.size() == kMaxResults) {
break;
} else if (base::TimeTicks::Now() - query_start_time > time_limit) {
timed_out = true;
break;
}
}
UMA_HISTOGRAM_BOOLEAN("Apps.AppList.FileSearchProvider.TimedOut", timed_out);
return matched_paths;
}
} // namespace
FileSearchProvider::FileSearchProvider(Profile* profile)
: profile_(profile),
root_path_(file_manager::util::GetMyFilesFolderForProfile(profile)) {
DCHECK(profile_);
DCHECK(!root_path_.empty());
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
FileSearchProvider::~FileSearchProvider() = default;
ash::AppListSearchResultType FileSearchProvider::ResultType() {
return ash::AppListSearchResultType::kFileSearch;
}
void FileSearchProvider::Start(const std::u16string& query) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
query_start_time_ = base::TimeTicks::Now();
// Clear results and cancel any outgoing requests.
ClearResultsSilently();
weak_factory_.InvalidateWeakPtrs();
// This provider does not handle zero-state.
if (query.empty())
return;
last_query_ = query;
last_tokenized_query_.emplace(query, TokenizedString::Mode::kWords);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
base::BindOnce(SearchFilesByPattern, root_path_, base::UTF16ToUTF8(query),
query_start_time_),
base::BindOnce(&FileSearchProvider::OnSearchComplete,
weak_factory_.GetWeakPtr()));
}
void FileSearchProvider::OnSearchComplete(
std::vector<FileSearchProvider::PathInfo> paths) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Sort paths by the time of last access.
std::sort(paths.begin(), paths.end(),
[](const FileSearchProvider::PathInfo& a,
const FileSearchProvider::PathInfo& b) {
return a.last_accessed < b.last_accessed;
});
constexpr float kScoreEps = 1.0e-5f;
SearchProvider::Results results;
for (int i = 0; i < paths.size(); ++i) {
// Add increasing score boosts for more recently accessed files.
double relevance =
FileResult::CalculateRelevance(last_tokenized_query_, paths[i].path);
relevance += i * kScoreEps;
results.emplace_back(MakeResult(paths[i], relevance));
}
SwapResults(&results);
UMA_HISTOGRAM_TIMES("Apps.AppList.FileSearchProvider.Latency",
base::TimeTicks::Now() - query_start_time_);
}
std::unique_ptr<FileResult> FileSearchProvider::MakeResult(
const FileSearchProvider::PathInfo& path,
const double relevance) {
const auto type = path.is_directory ? FileResult::Type::kDirectory
: FileResult::Type::kFile;
return std::make_unique<FileResult>(kFileSearchSchema, path.path,
ash::AppListSearchResultType::kFileSearch,
last_query_, relevance, type, profile_);
}
} // namespace app_list