| // 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/time/time.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "net/base/mime_util.h" |
| #include "storage/browser/file_system/external_mount_points.h" |
| #include "storage/browser/file_system/file_system_context.h" |
| #include "storage/browser/file_system/file_system_operation.h" |
| #include "storage/browser/file_system/file_system_operation_runner.h" |
| #include "storage/browser/file_system/file_system_url.h" |
| #include "url/origin.h" |
| |
| using content::BrowserThread; |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| constexpr char kAudioMimeType[] = "audio/*"; |
| constexpr char kImageMimeType[] = "image/*"; |
| constexpr char kVideoMimeType[] = "video/*"; |
| constexpr char kAmrMimeType[] = "audio/amr"; |
| constexpr char kAmrExtension[] = ".amr"; |
| |
| void OnReadDirectoryOnIOThread( |
| const storage::FileSystemOperation::ReadDirectoryCallback& callback, |
| base::File::Error result, |
| storage::FileSystemOperation::FileEntryList entries, |
| bool has_more) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| 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); |
| |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, 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))); |
| } |
| |
| // Returns true if the files at |path| matches the given |file_type|. |
| bool MatchesFileType(const base::FilePath& path, |
| RecentSource::FileType file_type) { |
| if (file_type == RecentSource::FileType::kAll) |
| return true; |
| |
| // File type for |path| is guessed using net::GetMimeTypeFromFile. |
| // It guesses mime types based on file extensions, but it has a limited set |
| // of file extensions. |
| // TODO(fukino): It is better to have better coverage of file extensions to be |
| // consistent with file-type detection on Android system. crbug.com/1034874. |
| std::string mime_type; |
| if (!net::GetMimeTypeFromFile(path, &mime_type)) { |
| const base::FilePath::StringType ext = path.Extension(); |
| if (base::ToLowerASCII(ext) != kAmrExtension) { |
| return false; |
| } |
| mime_type = kAmrMimeType; |
| } |
| |
| switch (file_type) { |
| case RecentSource::FileType::kAudio: |
| return net::MatchesMimeType(kAudioMimeType, mime_type); |
| case RecentSource::FileType::kImage: |
| return net::MatchesMimeType(kImageMimeType, mime_type); |
| case RecentSource::FileType::kVideo: |
| return net::MatchesMimeType(kVideoMimeType, mime_type); |
| default: |
| return false; |
| } |
| } |
| |
| } // 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)) { |
| 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_; |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| 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 { |
| if (!MatchesFileType(entry.name, params_.value().file_type())) { |
| continue; |
| } |
| storage::FileSystemURL url = BuildDiskURL(subpath); |
| ++inflight_stats_; |
| content::GetIOThreadTaskRunner({})->PostTask( |
| FROM_HERE, |
| 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( |
| url::Origin::Create(params_.value().origin()), mount_point_name_, path); |
| } |
| |
| } // namespace chromeos |