| // Copyright 2017 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/chromeos/fileapi/recent_disk_source.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/string_util.h" |
| #include "base/task/post_task.h" |
| #include "base/time/time.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "storage/browser/fileapi/external_mount_points.h" |
| #include "storage/browser/fileapi/file_system_context.h" |
| #include "storage/browser/fileapi/file_system_operation.h" |
| #include "storage/browser/fileapi/file_system_operation_runner.h" |
| #include "storage/browser/fileapi/file_system_url.h" |
| |
| using content::BrowserThread; |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| void OnReadDirectoryOnIOThread( |
| const storage::FileSystemOperation::ReadDirectoryCallback& callback, |
| base::File::Error result, |
| storage::FileSystemOperation::FileEntryList entries, |
| bool has_more) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(callback, result, std::move(entries), has_more)); |
| } |
| |
| void ReadDirectoryOnIOThread( |
| scoped_refptr<storage::FileSystemContext> file_system_context, |
| const storage::FileSystemURL& url, |
| const storage::FileSystemOperation::ReadDirectoryCallback& callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| file_system_context->operation_runner()->ReadDirectory( |
| url, base::BindRepeating(&OnReadDirectoryOnIOThread, callback)); |
| } |
| |
| void OnGetMetadataOnIOThread( |
| storage::FileSystemOperation::GetMetadataCallback callback, |
| base::File::Error result, |
| const base::File::Info& info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(std::move(callback), result, info)); |
| } |
| |
| void GetMetadataOnIOThread( |
| scoped_refptr<storage::FileSystemContext> file_system_context, |
| const storage::FileSystemURL& url, |
| int fields, |
| storage::FileSystemOperation::GetMetadataCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| file_system_context->operation_runner()->GetMetadata( |
| url, fields, |
| base::BindOnce(&OnGetMetadataOnIOThread, std::move(callback))); |
| } |
| |
| } // namespace |
| |
| RecentDiskSource::RecentDiskSource(std::string mount_point_name, |
| bool ignore_dotfiles, |
| int max_depth, |
| std::string uma_histogram_name) |
| : mount_point_name_(std::move(mount_point_name)), |
| ignore_dotfiles_(ignore_dotfiles), |
| max_depth_(max_depth), |
| uma_histogram_name_(std::move(uma_histogram_name)), |
| weak_ptr_factory_(this) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| } |
| |
| RecentDiskSource::~RecentDiskSource() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| } |
| |
| void RecentDiskSource::GetRecentFiles(Params params) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(!params_.has_value()); |
| DCHECK(build_start_time_.is_null()); |
| DCHECK_EQ(0, inflight_readdirs_); |
| DCHECK_EQ(0, inflight_stats_); |
| DCHECK(recent_files_.empty()); |
| |
| // Return immediately if mount point does not exist. |
| storage::ExternalMountPoints* mount_points = |
| storage::ExternalMountPoints::GetSystemInstance(); |
| base::FilePath path; |
| if (!mount_points->GetRegisteredPath(mount_point_name_, &path)) { |
| std::move(params.callback()).Run({}); |
| return; |
| } |
| |
| params_.emplace(std::move(params)); |
| |
| DCHECK(params_.has_value()); |
| |
| build_start_time_ = base::TimeTicks::Now(); |
| |
| ScanDirectory(base::FilePath(), 1); |
| } |
| |
| void RecentDiskSource::ScanDirectory(const base::FilePath& path, int depth) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(params_.has_value()); |
| |
| storage::FileSystemURL url = BuildDiskURL(path); |
| |
| ++inflight_readdirs_; |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce( |
| &ReadDirectoryOnIOThread, |
| base::WrapRefCounted(params_.value().file_system_context()), url, |
| base::BindRepeating(&RecentDiskSource::OnReadDirectory, |
| weak_ptr_factory_.GetWeakPtr(), path, depth))); |
| } |
| |
| void RecentDiskSource::OnReadDirectory( |
| const base::FilePath& path, |
| const int depth, |
| base::File::Error result, |
| storage::FileSystemOperation::FileEntryList entries, |
| bool has_more) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(params_.has_value()); |
| |
| for (const auto& entry : entries) { |
| // Ignore directories and files that start with dot. |
| if (ignore_dotfiles_ && |
| base::StartsWith(entry.name.value(), ".", |
| base::CompareCase::INSENSITIVE_ASCII)) { |
| continue; |
| } |
| base::FilePath subpath = path.Append(entry.name); |
| |
| if (entry.type == filesystem::mojom::FsFileType::DIRECTORY) { |
| if (max_depth_ > 0 && depth >= max_depth_) { |
| continue; |
| } |
| ScanDirectory(subpath, depth + 1); |
| } else { |
| storage::FileSystemURL url = BuildDiskURL(subpath); |
| ++inflight_stats_; |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce( |
| &GetMetadataOnIOThread, |
| base::WrapRefCounted(params_.value().file_system_context()), url, |
| storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED, |
| base::BindOnce(&RecentDiskSource::OnGetMetadata, |
| weak_ptr_factory_.GetWeakPtr(), url))); |
| } |
| } |
| |
| if (has_more) |
| return; |
| |
| --inflight_readdirs_; |
| OnReadOrStatFinished(); |
| } |
| |
| void RecentDiskSource::OnGetMetadata(const storage::FileSystemURL& url, |
| base::File::Error result, |
| const base::File::Info& info) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(params_.has_value()); |
| |
| if (result == base::File::FILE_OK && |
| info.last_modified >= params_.value().cutoff_time()) { |
| recent_files_.emplace(RecentFile(url, info.last_modified)); |
| while (recent_files_.size() > params_.value().max_files()) |
| recent_files_.pop(); |
| } |
| |
| --inflight_stats_; |
| OnReadOrStatFinished(); |
| } |
| |
| void RecentDiskSource::OnReadOrStatFinished() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (inflight_readdirs_ > 0 || inflight_stats_ > 0) |
| return; |
| |
| // All reads/scans completed. |
| std::vector<RecentFile> files; |
| while (!recent_files_.empty()) { |
| files.emplace_back(recent_files_.top()); |
| recent_files_.pop(); |
| } |
| |
| DCHECK(!build_start_time_.is_null()); |
| UmaHistogramTimes(uma_histogram_name_, |
| base::TimeTicks::Now() - build_start_time_); |
| build_start_time_ = base::TimeTicks(); |
| |
| Params params = std::move(params_.value()); |
| params_.reset(); |
| |
| DCHECK(!params_.has_value()); |
| DCHECK(build_start_time_.is_null()); |
| DCHECK_EQ(0, inflight_readdirs_); |
| DCHECK_EQ(0, inflight_stats_); |
| DCHECK(recent_files_.empty()); |
| |
| std::move(params.callback()).Run(std::move(files)); |
| } |
| |
| storage::FileSystemURL RecentDiskSource::BuildDiskURL( |
| const base::FilePath& path) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(params_.has_value()); |
| |
| storage::ExternalMountPoints* mount_points = |
| storage::ExternalMountPoints::GetSystemInstance(); |
| return mount_points->CreateExternalFileSystemURL(params_.value().origin(), |
| mount_point_name_, path); |
| } |
| |
| } // namespace chromeos |