blob: 5205f0ad24e9ce15066bc9b6b2395319e145b7c9 [file] [log] [blame]
// 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