blob: e439913512ecd03ee18bbfcf3a47806ccfdc7428 [file] [log] [blame]
Avi Drissman4a8573c2022-09-09 19:35:541// Copyright 2017 The Chromium Authors
Shuhei Takahashi882a4dbb2017-08-16 06:40:402// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Yeunjoo Choief8a4a42022-11-08 04:21:055#include "chrome/browser/ash/fileapi/recent_disk_source.h"
Shuhei Takahashi882a4dbb2017-08-16 06:40:406
7#include <utility>
8
9#include "base/files/file_path.h"
Avi Drissman02e49e582023-01-07 01:23:1810#include "base/functional/bind.h"
Joel Hockeyc7649cc02019-03-08 00:38:4211#include "base/metrics/histogram_functions.h"
12#include "base/strings/string_util.h"
Bo Majewskid51908972023-10-30 04:40:0113#include "base/strings/utf_string_conversions.h"
Shuhei Takahashi882a4dbb2017-08-16 06:40:4014#include "base/time/time.h"
Eric Seckler8652dcd52018-09-20 10:42:2815#include "content/public/browser/browser_task_traits.h"
Shuhei Takahashi882a4dbb2017-08-16 06:40:4016#include "content/public/browser/browser_thread.h"
Naoki Fukino14a30c262020-01-21 10:39:3217#include "net/base/mime_util.h"
DongJun Kimfebb3c22019-10-21 02:08:0618#include "storage/browser/file_system/external_mount_points.h"
19#include "storage/browser/file_system/file_system_context.h"
20#include "storage/browser/file_system/file_system_operation.h"
21#include "storage/browser/file_system/file_system_operation_runner.h"
22#include "storage/browser/file_system/file_system_url.h"
Kyra Seeversc5cba52c2021-08-20 16:31:2723#include "third_party/blink/public/common/storage_key/storage_key.h"
Wenbo Jie055062ff2022-03-01 06:05:5824#include "ui/file_manager/file_types_data.h"
Md. Hasanur Rashid7093fad02020-02-06 03:53:2325#include "url/origin.h"
Shuhei Takahashi882a4dbb2017-08-16 06:40:4026
27using content::BrowserThread;
28
Yeunjoo Choi3d9ed38a2022-11-10 02:51:2429namespace ash {
Shuhei Takahashi882a4dbb2017-08-16 06:40:4030
31namespace {
32
Naoki Fukino14a30c262020-01-21 10:39:3233constexpr char kAudioMimeType[] = "audio/*";
34constexpr char kImageMimeType[] = "image/*";
35constexpr char kVideoMimeType[] = "video/*";
36
Shuhei Takahashi882a4dbb2017-08-16 06:40:4037void OnReadDirectoryOnIOThread(
38 const storage::FileSystemOperation::ReadDirectoryCallback& callback,
39 base::File::Error result,
tzika40b4402017-08-28 14:46:1240 storage::FileSystemOperation::FileEntryList entries,
Shuhei Takahashi882a4dbb2017-08-16 06:40:4041 bool has_more) {
42 DCHECK_CURRENTLY_ON(BrowserThread::IO);
43
Gabriel Charettee7cdc5cd2020-05-27 23:35:0544 content::GetUIThreadTaskRunner({})->PostTask(
45 FROM_HERE,
tzika40b4402017-08-28 14:46:1246 base::BindOnce(callback, result, std::move(entries), has_more));
Shuhei Takahashi882a4dbb2017-08-16 06:40:4047}
48
49void ReadDirectoryOnIOThread(
50 scoped_refptr<storage::FileSystemContext> file_system_context,
51 const storage::FileSystemURL& url,
52 const storage::FileSystemOperation::ReadDirectoryCallback& callback) {
53 DCHECK_CURRENTLY_ON(BrowserThread::IO);
54
55 file_system_context->operation_runner()->ReadDirectory(
tzika40b4402017-08-28 14:46:1256 url, base::BindRepeating(&OnReadDirectoryOnIOThread, callback));
Shuhei Takahashi882a4dbb2017-08-16 06:40:4057}
58
59void OnGetMetadataOnIOThread(
Marijn Kruisselbrink94893f72018-10-01 23:36:0460 storage::FileSystemOperation::GetMetadataCallback callback,
Shuhei Takahashi882a4dbb2017-08-16 06:40:4061 base::File::Error result,
62 const base::File::Info& info) {
63 DCHECK_CURRENTLY_ON(BrowserThread::IO);
64
Gabriel Charettee7cdc5cd2020-05-27 23:35:0565 content::GetUIThreadTaskRunner({})->PostTask(
66 FROM_HERE, base::BindOnce(std::move(callback), result, info));
Shuhei Takahashi882a4dbb2017-08-16 06:40:4067}
68
69void GetMetadataOnIOThread(
70 scoped_refptr<storage::FileSystemContext> file_system_context,
71 const storage::FileSystemURL& url,
Nigel Taoaa2cf6a2023-12-03 23:07:1472 storage::FileSystemOperation::GetMetadataFieldSet fields,
Marijn Kruisselbrink94893f72018-10-01 23:36:0473 storage::FileSystemOperation::GetMetadataCallback callback) {
Shuhei Takahashi882a4dbb2017-08-16 06:40:4074 DCHECK_CURRENTLY_ON(BrowserThread::IO);
75
76 file_system_context->operation_runner()->GetMetadata(
Marijn Kruisselbrink94893f72018-10-01 23:36:0477 url, fields,
78 base::BindOnce(&OnGetMetadataOnIOThread, std::move(callback)));
Shuhei Takahashi882a4dbb2017-08-16 06:40:4079}
80
81} // namespace
82
Bo Majewski65e328462024-02-12 06:15:0483RecentDiskSource::RecentDiskSource::CallContext::CallContext(
Bo Majewski3e5a6bd2024-02-29 03:06:2384 const Params& params,
Bo Majewski65e328462024-02-12 06:15:0485 GetRecentFilesCallback callback)
Bo Majewski3e5a6bd2024-02-29 03:06:2386 : params(params),
87 callback(std::move(callback)),
Bo Majewski65e328462024-02-12 06:15:0488 build_start_time(base::TimeTicks::Now()),
Bo Majewski3fbd24402024-05-16 01:06:1489 accumulator(params.max_files()) {}
Bo Majewski65e328462024-02-12 06:15:0490
91RecentDiskSource::RecentDiskSource::CallContext::CallContext(
92 CallContext&& context)
Bo Majewski3e5a6bd2024-02-29 03:06:2393 : params(context.params),
94 callback(std::move(context.callback)),
Bo Majewski65e328462024-02-12 06:15:0495 build_start_time(context.build_start_time),
96 inflight_readdirs(context.inflight_readdirs),
97 inflight_stats(context.inflight_stats),
98 accumulator(std::move(context.accumulator)) {}
99
100RecentDiskSource::RecentDiskSource::CallContext::~CallContext() = default;
101
Bo Majewski6f33bc82024-05-28 02:24:00102RecentDiskSource::RecentDiskSource(
103 extensions::api::file_manager_private::VolumeType volume_type,
104 std::string mount_point_name,
105 bool ignore_dotfiles,
106 int max_depth,
107 std::string uma_histogram_name)
108 : RecentSource(volume_type),
109 mount_point_name_(std::move(mount_point_name)),
Joel Hockeyc7649cc02019-03-08 00:38:42110 ignore_dotfiles_(ignore_dotfiles),
111 max_depth_(max_depth),
Bo Majewski3fbd24402024-05-16 01:06:14112 uma_histogram_name_(std::move(uma_histogram_name)) {
Shuhei Takahashi882a4dbb2017-08-16 06:40:40113 DCHECK_CURRENTLY_ON(BrowserThread::UI);
114}
115
Joel Hockeyc7649cc02019-03-08 00:38:42116RecentDiskSource::~RecentDiskSource() {
Shuhei Takahashi882a4dbb2017-08-16 06:40:40117 DCHECK_CURRENTLY_ON(BrowserThread::UI);
118}
119
Bo Majewski3e5a6bd2024-02-29 03:06:23120void RecentDiskSource::GetRecentFiles(const Params& params,
Bo Majewskib5b18802023-11-22 06:04:53121 GetRecentFilesCallback callback) {
Shuhei Takahashi882a4dbb2017-08-16 06:40:40122 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Bo Majewski65e328462024-02-12 06:15:04123 DCHECK(context_map_.Lookup(params.call_id()) == nullptr);
Shuhei Takahashi882a4dbb2017-08-16 06:40:40124
Joel Hockeyc7649cc02019-03-08 00:38:42125 // Return immediately if mount point does not exist.
126 storage::ExternalMountPoints* mount_points =
127 storage::ExternalMountPoints::GetSystemInstance();
128 base::FilePath path;
129 if (!mount_points->GetRegisteredPath(mount_point_name_, &path)) {
Bo Majewskib5b18802023-11-22 06:04:53130 std::move(callback).Run({});
Joel Hockeyc7649cc02019-03-08 00:38:42131 return;
132 }
133
Bo Majewski65e328462024-02-12 06:15:04134 // Create a unique context for this call.
Bo Majewski3fbd24402024-05-16 01:06:14135 auto context = std::make_unique<CallContext>(params, std::move(callback));
Bo Majewski65e328462024-02-12 06:15:04136 context_map_.AddWithID(std::move(context), params.call_id());
Shuhei Takahashi1c79df02017-08-17 08:07:30137
Bo Majewski3e5a6bd2024-02-29 03:06:23138 ScanDirectory(params.call_id(), base::FilePath(), 1);
Shuhei Takahashi882a4dbb2017-08-16 06:40:40139}
140
Bo Majewski65e328462024-02-12 06:15:04141std::vector<RecentFile> RecentDiskSource::Stop(const int32_t call_id) {
142 DCHECK_CURRENTLY_ON(BrowserThread::UI);
143 CallContext* context = context_map_.Lookup(call_id);
144 if (context == nullptr) {
145 // The Stop method was called after we already responded. Just return empty
146 // list of files.
147 return {};
148 }
149 // Proper stop; get the files and erase the context.
150 const std::vector<RecentFile> files = context->accumulator.Get();
151 context_map_.Remove(call_id);
152 return files;
153}
154
Bo Majewski3e5a6bd2024-02-29 03:06:23155void RecentDiskSource::ScanDirectory(const int32_t call_id,
Bo Majewskib5b18802023-11-22 06:04:53156 const base::FilePath& path,
157 int depth) {
Shuhei Takahashi882a4dbb2017-08-16 06:40:40158 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Shuhei Takahashi882a4dbb2017-08-16 06:40:40159
Bo Majewski65e328462024-02-12 06:15:04160 // If context is gone, that is Stop() has been called, exit immediately.
Bo Majewski3e5a6bd2024-02-29 03:06:23161 CallContext* context = context_map_.Lookup(call_id);
Bo Majewski65e328462024-02-12 06:15:04162 if (context == nullptr) {
163 return;
164 }
165
Bo Majewski3e5a6bd2024-02-29 03:06:23166 storage::FileSystemURL url = BuildDiskURL(context->params, path);
Shuhei Takahashi882a4dbb2017-08-16 06:40:40167
Bo Majewski65e328462024-02-12 06:15:04168 ++context->inflight_readdirs;
Gabriel Charettee7cdc5cd2020-05-27 23:35:05169 content::GetIOThreadTaskRunner({})->PostTask(
170 FROM_HERE,
Bo Majewski3e5a6bd2024-02-29 03:06:23171 base::BindOnce(
172 &ReadDirectoryOnIOThread,
173 base::WrapRefCounted(context->params.file_system_context()), url,
174 base::BindRepeating(&RecentDiskSource::OnReadDirectory,
175 weak_ptr_factory_.GetWeakPtr(), call_id, path,
176 depth)));
Shuhei Takahashi882a4dbb2017-08-16 06:40:40177}
178
Joel Hockeyc7649cc02019-03-08 00:38:42179void RecentDiskSource::OnReadDirectory(
Bo Majewski3e5a6bd2024-02-29 03:06:23180 const int32_t call_id,
Shuhei Takahashi882a4dbb2017-08-16 06:40:40181 const base::FilePath& path,
Joel Hockeyc7649cc02019-03-08 00:38:42182 const int depth,
Shuhei Takahashi882a4dbb2017-08-16 06:40:40183 base::File::Error result,
tzika40b4402017-08-28 14:46:12184 storage::FileSystemOperation::FileEntryList entries,
Shuhei Takahashi882a4dbb2017-08-16 06:40:40185 bool has_more) {
186 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Bo Majewski65e328462024-02-12 06:15:04187 // If context is gone, that is Stop() has been called, exit immediately.
Bo Majewski3e5a6bd2024-02-29 03:06:23188 CallContext* context = context_map_.Lookup(call_id);
Bo Majewski65e328462024-02-12 06:15:04189 if (context == nullptr) {
190 return;
191 }
Shuhei Takahashi882a4dbb2017-08-16 06:40:40192
Bo Majewski3e5a6bd2024-02-29 03:06:23193 const std::u16string q16 = base::UTF8ToUTF16(context->params.query());
Shuhei Takahashi882a4dbb2017-08-16 06:40:40194 for (const auto& entry : entries) {
Joel Hockeyc7649cc02019-03-08 00:38:42195 // Ignore directories and files that start with dot.
196 if (ignore_dotfiles_ &&
197 base::StartsWith(entry.name.value(), ".",
198 base::CompareCase::INSENSITIVE_ASCII)) {
199 continue;
200 }
Shuhei Takahashi882a4dbb2017-08-16 06:40:40201 base::FilePath subpath = path.Append(entry.name);
Joel Hockeyc7649cc02019-03-08 00:38:42202
Luciano Pacheco99533e82018-03-28 07:49:07203 if (entry.type == filesystem::mojom::FsFileType::DIRECTORY) {
Bo Majewski3e5a6bd2024-02-29 03:06:23204 if ((max_depth_ > 0 && depth >= max_depth_) || context->params.IsLate()) {
Joel Hockeyc7649cc02019-03-08 00:38:42205 continue;
206 }
Bo Majewski3e5a6bd2024-02-29 03:06:23207 ScanDirectory(call_id, subpath, depth + 1);
Shuhei Takahashi882a4dbb2017-08-16 06:40:40208 } else {
Joel Hockeydab864e2024-10-15 22:46:12209 if (!MatchesFileType(entry.name.path(), context->params.file_type())) {
Naoki Fukino14a30c262020-01-21 10:39:32210 continue;
211 }
Bo Majewskid51908972023-10-30 04:40:01212 if (!FileNameMatches(base::UTF8ToUTF16(entry.name.value()), q16)) {
213 continue;
214 }
Bo Majewski3e5a6bd2024-02-29 03:06:23215 storage::FileSystemURL url = BuildDiskURL(context->params, subpath);
Bo Majewski65e328462024-02-12 06:15:04216 ++context->inflight_stats;
Gabriel Charettee7cdc5cd2020-05-27 23:35:05217 content::GetIOThreadTaskRunner({})->PostTask(
218 FROM_HERE,
Bo Majewski65e328462024-02-12 06:15:04219 base::BindOnce(
220 &GetMetadataOnIOThread,
Bo Majewski3e5a6bd2024-02-29 03:06:23221 base::WrapRefCounted(context->params.file_system_context()), url,
Bo Majewski65e328462024-02-12 06:15:04222 storage::FileSystemOperation::GetMetadataFieldSet(
223 {storage::FileSystemOperation::GetMetadataField::
224 kLastModified}),
225 base::BindOnce(&RecentDiskSource::OnGotMetadata,
Bo Majewski3e5a6bd2024-02-29 03:06:23226 weak_ptr_factory_.GetWeakPtr(), call_id, url)));
Shuhei Takahashi882a4dbb2017-08-16 06:40:40227 }
228 }
229
Bo Majewskib5b18802023-11-22 06:04:53230 if (has_more) {
Shuhei Takahashi882a4dbb2017-08-16 06:40:40231 return;
Bo Majewskib5b18802023-11-22 06:04:53232 }
Shuhei Takahashi882a4dbb2017-08-16 06:40:40233
Bo Majewski65e328462024-02-12 06:15:04234 --context->inflight_readdirs;
235 if (context->inflight_stats == 0 && context->inflight_readdirs == 0) {
Bo Majewski3e5a6bd2024-02-29 03:06:23236 OnReadOrStatFinished(call_id);
Bo Majewski65e328462024-02-12 06:15:04237 }
Shuhei Takahashi882a4dbb2017-08-16 06:40:40238}
239
Bo Majewski3e5a6bd2024-02-29 03:06:23240void RecentDiskSource::OnGotMetadata(const int32_t call_id,
Bo Majewskib5b18802023-11-22 06:04:53241 const storage::FileSystemURL& url,
Joel Hockeyc7649cc02019-03-08 00:38:42242 base::File::Error result,
243 const base::File::Info& info) {
Shuhei Takahashi882a4dbb2017-08-16 06:40:40244 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Bo Majewski65e328462024-02-12 06:15:04245 // If context is gone, that is Stop() has been called, exit immediately.
Bo Majewski3e5a6bd2024-02-29 03:06:23246 CallContext* context = context_map_.Lookup(call_id);
Bo Majewski65e328462024-02-12 06:15:04247 if (context == nullptr) {
Shuhei Takahashi882a4dbb2017-08-16 06:40:40248 return;
Bo Majewskib5b18802023-11-22 06:04:53249 }
Shuhei Takahashi882a4dbb2017-08-16 06:40:40250
Bo Majewski65e328462024-02-12 06:15:04251 if (result == base::File::FILE_OK &&
Bo Majewski3e5a6bd2024-02-29 03:06:23252 info.last_modified >= context->params.cutoff_time()) {
Bo Majewski65e328462024-02-12 06:15:04253 context->accumulator.Add(RecentFile(url, info.last_modified));
254 }
255
256 --context->inflight_stats;
257 if (context->inflight_stats == 0 && context->inflight_readdirs == 0) {
Bo Majewski3e5a6bd2024-02-29 03:06:23258 OnReadOrStatFinished(call_id);
Bo Majewski65e328462024-02-12 06:15:04259 }
260}
261
Bo Majewski3e5a6bd2024-02-29 03:06:23262void RecentDiskSource::OnReadOrStatFinished(const int32_t call_id) {
Bo Majewski65e328462024-02-12 06:15:04263 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Bo Majewski3e5a6bd2024-02-29 03:06:23264 CallContext* context = context_map_.Lookup(call_id);
Bo Majewski65e328462024-02-12 06:15:04265 // If context is gone, that is Stop() has been called, exit immediately.
266 if (context == nullptr) {
267 return;
268 }
269
270 DCHECK(context->inflight_stats == 0);
271 DCHECK(context->inflight_readdirs == 0);
272 DCHECK(!context->build_start_time.is_null());
273
Shuhei Takahashi882a4dbb2017-08-16 06:40:40274 // All reads/scans completed.
Joel Hockeyc7649cc02019-03-08 00:38:42275 UmaHistogramTimes(uma_histogram_name_,
Bo Majewski65e328462024-02-12 06:15:04276 base::TimeTicks::Now() - context->build_start_time);
Shuhei Takahashi1c79df02017-08-17 08:07:30277
Bo Majewski65e328462024-02-12 06:15:04278 std::move(context->callback).Run(context->accumulator.Get());
Bo Majewski3e5a6bd2024-02-29 03:06:23279 context_map_.Remove(call_id);
Shuhei Takahashi882a4dbb2017-08-16 06:40:40280}
281
Joel Hockeyc7649cc02019-03-08 00:38:42282storage::FileSystemURL RecentDiskSource::BuildDiskURL(
Bo Majewskib5b18802023-11-22 06:04:53283 const Params& params,
Shuhei Takahashi882a4dbb2017-08-16 06:40:40284 const base::FilePath& path) const {
285 DCHECK_CURRENTLY_ON(BrowserThread::UI);
Shuhei Takahashi882a4dbb2017-08-16 06:40:40286
287 storage::ExternalMountPoints* mount_points =
288 storage::ExternalMountPoints::GetSystemInstance();
Md. Hasanur Rashid7093fad02020-02-06 03:53:23289 return mount_points->CreateExternalFileSystemURL(
Bo Majewskib5b18802023-11-22 06:04:53290 blink::StorageKey::CreateFirstParty(url::Origin::Create(params.origin())),
Kyra Seeversc5cba52c2021-08-20 16:31:27291 mount_point_name_, path);
Shuhei Takahashi882a4dbb2017-08-16 06:40:40292}
293
Bo Majewski965f1c72023-02-06 01:51:44294bool RecentDiskSource::MatchesFileType(const base::FilePath& path,
295 RecentSource::FileType file_type) {
296 if (file_type == RecentSource::FileType::kAll) {
297 return true;
298 }
299
300 // File type for |path| is guessed by data generated from file_types.json5.
301 // It guesses mime types based on file extensions, but it has a limited set
302 // of file extensions.
303 // TODO(fukino): It is better to have better coverage of file extensions to be
304 // consistent with file-type detection on Android system. crbug.com/1034874.
305 const auto ext = base::ToLowerASCII(path.Extension());
306 if (!file_types_data::kExtensionToMIME.contains(ext)) {
307 return false;
308 }
309 std::string mime_type = file_types_data::kExtensionToMIME.at(ext);
310
311 switch (file_type) {
312 case RecentSource::FileType::kAudio:
313 return net::MatchesMimeType(kAudioMimeType, mime_type);
314 case RecentSource::FileType::kImage:
315 return net::MatchesMimeType(kImageMimeType, mime_type);
316 case RecentSource::FileType::kVideo:
317 return net::MatchesMimeType(kVideoMimeType, mime_type);
318 case RecentSource::FileType::kDocument:
319 return file_types_data::kDocumentMIMETypes.contains(mime_type);
320 default:
321 return false;
322 }
323}
324
Yeunjoo Choi3d9ed38a2022-11-10 02:51:24325} // namespace ash