1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
|
// Copyright 2024 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/app_list/search/search_file_scanner.h"
#include <algorithm>
#include <map>
#include <string_view>
#include <utility>
#include "ash/constants/ash_pref_names.h"
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_map.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "chrome/browser/ash/file_manager/path_util.h"
#include "chrome/browser/profiles/profile.h"
#include "components/prefs/pref_service.h"
#include "ui/base/metadata/base_type_conversion.h"
namespace app_list {
namespace {
// The minimum time in days required to wait after the previous file scan
// logging. Assuming that the user local file won't change dramatically, log at
// most once per week to minimize the power and performance impact.
constexpr int kMinFileScanDelayDays = 7;
// The delay before we start the file scan when the file is constructed.
constexpr base::TimeDelta kFileScanDelay = base::Minutes(10);
// The key for total file numbers in `extension_file_counts`.
constexpr std::string_view kTotal = "Total";
// The list of file extensions we are interested in.
// It is in the form of {"extension format", "extension name"}, with the format
// to match the file extension and the name to get the uma metric name.
// Note:
// When adding new extension to this map, also updates the
// `SearchFileExtension` variants in
// `tools/metrics/histograms/metadata/apps/histograms.xml`.
constexpr auto kExtensionMap =
base::MakeFixedFlatMap<std::string_view, std::string_view>({
// Image extensions.
{".png", "Png"},
{".jpg", "Jpg"},
{".jpeg", "Jpeg"},
{".webp", "Webp"},
});
base::Time GetLastScanLogTime(Profile* profile) {
return profile->GetPrefs()->GetTime(
ash::prefs::kLauncherSearchLastFileScanLogTime);
}
void SetLastScanLogTime(Profile* profile, const base::Time& time) {
profile->GetPrefs()->SetTime(ash::prefs::kLauncherSearchLastFileScanLogTime,
time);
}
// Looks up and counts the total file number and the file numbers of each
// extension that is of interest in the `search_path` and excludes the ones that
// overlaps with `trash_paths`.
//
// This function can be executed on any thread other than the UI thread, while
// the `SearchFileScanner` is created on UI thread.
void FullScan(const base::FilePath& search_path,
const std::vector<base::FilePath>& trash_paths) {
base::Time start_time = base::Time::Now();
base::FileEnumerator file_enumerator(search_path, /*recursive=*/true,
base::FileEnumerator::FILES);
std::map<std::string_view, int> extension_file_counts;
for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty();
file_path = file_enumerator.Next()) {
// Exclude any paths that are parented at an enabled trash location.
if (std::ranges::any_of(trash_paths,
[&file_path](const base::FilePath& trash_path) {
return trash_path.IsParent(file_path);
})) {
continue;
}
// Always counts the total file number.
extension_file_counts[kTotal]++;
// Increments the extension count if the extension of the current file is of
// interest.
const auto extension_lookup =
kExtensionMap.find(base::ToLowerASCII(file_path.Extension()));
if (extension_lookup != kExtensionMap.end()) {
extension_file_counts[extension_lookup->second]++;
}
}
// Logs execution time.
base::UmaHistogramTimes("Apps.AppList.SearchFileScan.ExecutionTime",
base::Time::Now() - start_time);
// Logs file extension count data.
for (const auto& it : extension_file_counts) {
base::UmaHistogramCounts100000(
base::StrCat({"Apps.AppList.SearchFileScan.", it.first}), it.second);
}
}
} // namespace
SearchFileScanner::SearchFileScanner(
Profile* profile,
const base::FilePath& root_path,
const std::vector<base::FilePath>& excluded_paths,
std::optional<base::TimeDelta> start_delay_override)
: profile_(profile),
root_path_(root_path),
excluded_paths_(excluded_paths) {
// Early returns if file scan has been done recently.
// TODO(b/337130427) considering replacing this with a timer, which can covers
// the users which does not logout frequently.
if (profile_ && base::Time::Now() - GetLastScanLogTime(profile_) <
base::Days(kMinFileScanDelayDays)) {
return;
}
// Delays the file scan so that it does not add regressions to the user login.
// Skips the delay in test.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&SearchFileScanner::StartFileScan,
weak_factory_.GetWeakPtr()),
start_delay_override.value_or(kFileScanDelay));
}
SearchFileScanner::~SearchFileScanner() = default;
void SearchFileScanner::StartFileScan() {
// Do the file scan on a non-UI thread.
base::ThreadPool::PostTaskAndReply(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&FullScan, std::move(root_path_),
std::move(excluded_paths_)),
base::BindOnce(&SearchFileScanner::OnScanComplete,
weak_factory_.GetWeakPtr()));
}
void SearchFileScanner::OnScanComplete() {
// Ensures the `profile_` is still alive to avoid any possible crashes during
// shutdown.
if (profile_) {
SetLastScanLogTime(profile_, base::Time::Now());
}
}
} // namespace app_list
|