// 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/file_system_provider/cloud_file_system.h"
#include <memory>
#include <utility>
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/rand_util.h"
#include "base/strings/string_util.h"
#include "base/timer/timer.h"
#include "chrome/browser/ash/file_manager/fileapi_util.h"
#include "chrome/browser/ash/file_system_provider/cloud_file_info.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
#include "chrome/browser/ash/file_system_provider/queue.h"
#include "net/base/io_buffer.h"
#include "url/origin.h"
namespace ash::file_system_provider {
namespace {
// The frequency that the FSP syncs with the cloud when the File Manager is a
// watcher.
constexpr base::TimeDelta kFileManagerWatcherInterval = base::Seconds(15);
// TODO(b/317137739): Remove this once a proper API call is introduced.
// Temp custom action to request ODFS sync with the cloud.
constexpr char kODFSSyncWithCloudAction[] = "HIDDEN_SYNC_WITH_CLOUD";
const GURL GetContentCacheURL() {
return GURL("chrome://content-cache/");
}
std::ostream& operator<<(std::ostream& out,
const std::vector<base::FilePath>& entry_paths) {
for (size_t i = 0; i < entry_paths.size(); ++i) {
out << entry_paths[i];
if (i < entry_paths.size() - 1) {
out << ", ";
}
}
return out;
}
std::ostream& operator<<(std::ostream& out, OpenFileMode mode) {
switch (mode) {
case OpenFileMode::OPEN_FILE_MODE_READ:
return out << "OPEN_FILE_MODE_READ";
case OpenFileMode::OPEN_FILE_MODE_WRITE:
return out << "OPEN_FILE_MODE_WRITE";
}
NOTREACHED() << "Unknown OpenFileMode: " << mode;
}
std::ostream& operator<<(std::ostream& out,
storage::WatcherManager::ChangeType type) {
using ChangeType = storage::WatcherManager::ChangeType;
switch (type) {
case ChangeType::CHANGED:
return out << "CHANGED";
case ChangeType::DELETED:
return out << "DELETED";
}
NOTREACHED() << "Unknown ChangeType: " << type;
}
std::ostream& operator<<(std::ostream& out, CloudFileInfo* cloud_file_info) {
if (!cloud_file_info) {
return out << "none";
}
return out << "{version_tag = '" << cloud_file_info->version_tag << "'}";
}
std::ostream& operator<<(std::ostream& out, EntryMetadata* metadata) {
if (!metadata) {
return out << "none";
}
out << "{ cloud_file_info = " << metadata->cloud_file_info.get();
if (metadata->size) {
out << ", size = '" << *metadata->size << "'";
}
return out << "}";
}
std::ostream& operator<<(std::ostream& out,
ProvidedFileSystemObserver::Changes* changes) {
if (!changes) {
return out << "none";
}
for (size_t i = 0; i < changes->size(); ++i) {
const auto& [entry_path, change_type, cloud_file_info] = (*changes)[i];
out << entry_path << ": change_type = " << change_type
<< ", cloud_file_info = " << cloud_file_info.get();
if (i < changes->size() - 1) {
out << ", ";
}
}
return out;
}
const std::string GetVersionTag(EntryMetadata* metadata) {
return (metadata && metadata->cloud_file_info)
? metadata->cloud_file_info->version_tag
: "";
}
std::optional<int64_t> GetCloudSize(EntryMetadata* metadata) {
// If the size doesn't exist, it may return -1, let's avoid this error case.
if (metadata && metadata->size && *metadata->size > -1) {
return *metadata->size;
}
return std::nullopt;
}
} // namespace
CloudFileSystem::CloudFileSystem(
std::unique_ptr<ProvidedFileSystemInterface> file_system)
: CloudFileSystem(std::move(file_system), nullptr) {}
CloudFileSystem::CloudFileSystem(
std::unique_ptr<ProvidedFileSystemInterface> file_system,
CacheManager* cache_manager)
: file_system_(std::move(file_system)) {
if (!cache_manager) {
return;
}
cache_manager->InitializeForProvider(
file_system_->GetFileSystemInfo(),
base::BindOnce(&CloudFileSystem::OnContentCacheInitialized,
weak_ptr_factory_.GetWeakPtr()));
}
CloudFileSystem::~CloudFileSystem() = default;
void CloudFileSystem::OnContentCacheInitialized(
base::FileErrorOr<std::unique_ptr<ContentCache>> error_or_cache) {
LOG_IF(ERROR, !error_or_cache.has_value())
<< "Error initializing the content cache: " << error_or_cache.error();
if (error_or_cache.has_value()) {
content_cache_ = std::move(error_or_cache.value());
content_cache_->AddObserver(this);
for (const base::FilePath& file_path :
content_cache_->GetCachedFilePaths()) {
AddWatcherOnCachedFile(file_path);
}
}
}
void CloudFileSystem::AddWatcherOnCachedFile(const base::FilePath& file_path) {
AddWatcherOnCachedFileImpl(file_path, /*attempts=*/0,
/*result=*/base::File::FILE_ERROR_SECURITY);
}
void CloudFileSystem::AddWatcherOnCachedFileImpl(
const base::FilePath& file_path,
int attempts,
base::File::Error result) {
if (result == base::File::FILE_OK) {
VLOG(1) << "Re-added file watcher on file '" << file_path << "'";
return;
}
if (result != base::File::FILE_ERROR_SECURITY || attempts > 6) {
LOG(ERROR) << "Failed to add file watcher on file with result: " << result
<< " after " << attempts << " attempts";
VLOG(2) << "Failed to add file watcher on file '" << file_path
<< "' with result: " << result << " after " << attempts
<< " attempts";
return;
}
// Set a random delay in the interval attempts*[0,2] seconds to stagger
// AddWatcher requests.
base::TimeDelta delay = attempts * base::Milliseconds(base::RandInt(1, 2000));
// Notifications are received though Notify() so no notification_callback
// is needed. Call this function recursively to continuously retry upon
// FILE_ERROR_SECURITY errors until the max number of attempts have been made.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
IgnoreResult(&CloudFileSystem::AddWatcher),
weak_ptr_factory_.GetWeakPtr(), GetContentCacheURL(), file_path,
/*recursive=*/false, /*persistent=*/false,
base::BindOnce(&CloudFileSystem::AddWatcherOnCachedFileImpl,
weak_ptr_factory_.GetWeakPtr(), file_path,
attempts + 1),
base::DoNothing()),
delay);
}
AbortCallback CloudFileSystem::RequestUnmount(
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(2) << "RequestUnmount {fsid = " << GetFileSystemId() << "}";
return file_system_->RequestUnmount(std::move(callback));
}
AbortCallback CloudFileSystem::GetMetadata(const base::FilePath& entry_path,
MetadataFieldMask fields,
GetMetadataCallback callback) {
VLOG(2) << "GetMetadata {fsid = '" << GetFileSystemId() << "', entry_path = '"
<< entry_path << "', fields = '" << fields << "'}";
fields |= METADATA_FIELD_CLOUD_FILE_INFO;
return file_system_->GetMetadata(
entry_path, fields,
base::BindOnce(&CloudFileSystem::OnGetMetadataCompleted,
weak_ptr_factory_.GetWeakPtr(), entry_path,
std::move(callback)));
}
AbortCallback CloudFileSystem::GetActions(
const std::vector<base::FilePath>& entry_paths,
GetActionsCallback callback) {
VLOG(2) << "GetActions {fsid = '" << GetFileSystemId() << "', entry_paths = '"
<< entry_paths << "'}";
return file_system_->GetActions(entry_paths, std::move(callback));
}
AbortCallback CloudFileSystem::ExecuteAction(
const std::vector<base::FilePath>& entry_paths,
const std::string& action_id,
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(2) << "ExecuteAction {fsid = '" << GetFileSystemId()
<< "', entry_paths = '" << entry_paths << "', action_id = '"
<< action_id << "'}";
return file_system_->ExecuteAction(entry_paths, action_id,
std::move(callback));
}
AbortCallback CloudFileSystem::ReadDirectory(
const base::FilePath& directory_path,
storage::AsyncFileUtil::ReadDirectoryCallback callback) {
VLOG(1) << "ReadDirectory {fsid = '" << GetFileSystemId()
<< "', directory_path = '" << directory_path << "'}";
return file_system_->ReadDirectory(directory_path, callback);
}
bool CloudFileSystem::ShouldAttemptToServeReadFileFromCache(
const OpenedCloudFileMap::const_iterator it) {
return content_cache_ && it != opened_files_.end() &&
it->second.mode == OpenFileMode::OPEN_FILE_MODE_READ &&
!it->second.version_tag.empty() &&
it->second.bytes_in_cloud.has_value();
}
AbortCallback CloudFileSystem::ReadFile(int file_handle,
net::IOBuffer* buffer,
int64_t offset,
int length,
ReadChunkReceivedCallback callback) {
VLOG(1) << "ReadFile {fsid = '" << GetFileSystemId() << "', file_handle = '"
<< file_handle << "', offset = '" << offset << "', length = '"
<< length << "'}";
// In the event the file isn't found in the `opened_files_` map, the content
// cache hasn't or won't be initialized OR there is an empty `version_tag`,
// then pass the request directly to the FSP.
const OpenedCloudFileMap::const_iterator it = opened_files_.find(file_handle);
if (!ShouldAttemptToServeReadFileFromCache(it)) {
return file_system_->ReadFile(file_handle, buffer, offset, length,
callback);
}
const OpenedCloudFile& opened_cloud_file = it->second;
scoped_refptr<net::IOBuffer> buffer_ref = base::WrapRefCounted(buffer);
content_cache_->ReadBytes(
opened_cloud_file, buffer_ref, offset, length,
base::BindRepeating(&CloudFileSystem::OnReadFileFromCacheCompleted,
weak_ptr_factory_.GetWeakPtr(), file_handle,
buffer_ref, offset, length, callback));
return AbortCallback();
}
void CloudFileSystem::OnReadFileFromCacheCompleted(
int file_handle,
scoped_refptr<net::IOBuffer> buffer,
int64_t offset,
int length,
ReadChunkReceivedCallback callback,
int bytes_read,
bool has_more,
base::File::Error result) {
VLOG(2) << "OnReadFileFromCacheCompleted {fsid = " << GetFileSystemId()
<< ", file_handle = '" << file_handle << "', result = '" << result
<< "}";
if (result == base::File::FILE_OK) {
// If the cached read file was successful, ensure that is passed to the
// caller.
callback.Run(bytes_read, has_more, result);
return;
}
if (result == base::File::FILE_ERROR_NOT_FOUND) {
// The file doesn't exist in the cache or is not available, we need to make
// a cloud request first and attempt to write the result into the cache upon
// successful return.
file_system_->ReadFile(
file_handle, buffer.get(), offset, length,
base::BindRepeating(&CloudFileSystem::OnReadFileCompleted,
weak_ptr_factory_.GetWeakPtr(), file_handle, buffer,
offset, length, callback));
return;
}
LOG(ERROR) << "Couldn't read the file from cache";
file_system_->ReadFile(file_handle, buffer.get(), offset, length,
std::move(callback));
}
void CloudFileSystem::OnReadFileCompleted(int file_handle,
scoped_refptr<net::IOBuffer> buffer,
int64_t offset,
int length,
ReadChunkReceivedCallback callback,
int bytes_read,
bool has_more,
base::File::Error result) {
VLOG(2) << "OnReadFileCompleted {fsid = " << GetFileSystemId()
<< ", file_handle = '" << file_handle << "', result = '" << result
<< ", bytes_read = " << bytes_read << "}";
const OpenedCloudFileMap::const_iterator it = opened_files_.find(file_handle);
if (it == opened_files_.end() || result != base::File::FILE_OK ||
!content_cache_) {
callback.Run(bytes_read, has_more, result);
return;
}
// The `ReadChunkReceivedCallback` should always respond with the result from
// the FSP. If the content cache write fails, we should always be serving this
// from the FSP.
auto readchunk_success_callback = base::BindRepeating(
std::move(callback), bytes_read, has_more, base::File::FILE_OK);
const OpenedCloudFile& opened_cloud_file = it->second;
content_cache_->WriteBytes(
opened_cloud_file, buffer, offset, bytes_read,
base::BindOnce(&CloudFileSystem::OnBytesWrittenToCache,
weak_ptr_factory_.GetWeakPtr(),
opened_cloud_file.file_path, readchunk_success_callback));
}
void CloudFileSystem::OnBytesWrittenToCache(
const base::FilePath& file_path,
base::RepeatingCallback<void()> readchunk_success_callback,
base::File::Error result) {
if (result == base::File::FILE_OK) {
Watchers::const_iterator watcher =
GetWatchers()->find(WatcherKey(file_path, /*recursive=*/false));
if (watcher == GetWatchers()->end() ||
!watcher->second.subscribers.contains(GetContentCacheURL())) {
// This file is newly added to the cache so watch it to track any changes.
// Notifications are received though Notify() so no notification_callback
// is needed.
AddWatcher(
GetContentCacheURL(), file_path,
/*recursive=*/false, /*persistent=*/false,
base::BindOnce(
[](const base::FilePath& file_path, base::File::Error result) {
VLOG(1) << "Added file watcher on '" << file_path
<< "' result: " << result;
},
file_path),
base::DoNothing());
}
}
readchunk_success_callback.Run();
}
AbortCallback CloudFileSystem::OpenFile(const base::FilePath& file_path,
OpenFileMode mode,
OpenFileCallback callback) {
VLOG(1) << "OpenFile {fsid = '" << GetFileSystemId() << "', file_path = '"
<< file_path << "', mode = '" << mode << "'}";
return file_system_->OpenFile(
file_path, mode,
base::BindOnce(&CloudFileSystem::OnOpenFileCompleted,
weak_ptr_factory_.GetWeakPtr(), file_path, mode,
std::move(callback)));
}
AbortCallback CloudFileSystem::CloseFile(
int file_handle,
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(1) << "CloseFile {fsid = '" << GetFileSystemId() << "', file_handle = '"
<< file_handle << "'}";
return file_system_->CloseFile(
file_handle, base::BindOnce(&CloudFileSystem::OnCloseFileCompleted,
weak_ptr_factory_.GetWeakPtr(), file_handle,
std::move(callback)));
}
AbortCallback CloudFileSystem::CreateDirectory(
const base::FilePath& directory_path,
bool recursive,
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(1) << "CreateDirectory {fsid = '" << GetFileSystemId()
<< "', directory_path = '" << directory_path << "', recursive = '"
<< recursive << "'}";
return file_system_->CreateDirectory(directory_path, recursive,
std::move(callback));
}
AbortCallback CloudFileSystem::DeleteEntry(
const base::FilePath& entry_path,
bool recursive,
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(1) << "DeleteEntry {fsid = '" << GetFileSystemId() << "', entry_path = '"
<< entry_path << "', recursive = '" << recursive << "'}";
return file_system_->DeleteEntry(
entry_path, recursive,
base::BindOnce(&CloudFileSystem::OnDeleteEntryCompleted,
weak_ptr_factory_.GetWeakPtr(), entry_path,
std::move(callback)));
}
AbortCallback CloudFileSystem::CreateFile(
const base::FilePath& file_path,
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(1) << "CreateFile {fsid = '" << GetFileSystemId() << "', file_path = '"
<< file_path << "'}";
return file_system_->CreateFile(file_path, std::move(callback));
}
AbortCallback CloudFileSystem::CopyEntry(
const base::FilePath& source_path,
const base::FilePath& target_path,
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(1) << "CopyEntry {fsid = '" << GetFileSystemId() << "', source_path = '"
<< source_path << "', target_path = '" << target_path << "'}";
return file_system_->CopyEntry(source_path, target_path, std::move(callback));
}
AbortCallback CloudFileSystem::WriteFile(
int file_handle,
net::IOBuffer* buffer,
int64_t offset,
int length,
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(1) << "WriteFile {fsid = '" << GetFileSystemId() << "', file_handle = '"
<< file_handle << "', offset = '" << offset << "', length = '"
<< length << "'}";
return file_system_->WriteFile(
file_handle, buffer, offset, length,
base::BindOnce(&CloudFileSystem::OnWriteFileCompleted,
weak_ptr_factory_.GetWeakPtr(), file_handle,
std::move(callback)));
}
AbortCallback CloudFileSystem::FlushFile(
int file_handle,
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(1) << "FlushFile {fsid = '" << GetFileSystemId() << "', file_handle = '"
<< file_handle << "'}";
return file_system_->FlushFile(file_handle, std::move(callback));
}
AbortCallback CloudFileSystem::MoveEntry(
const base::FilePath& source_path,
const base::FilePath& target_path,
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(1) << "MoveEntry {fsid = '" << GetFileSystemId() << "', source_path = '"
<< source_path << "', target_path = '" << target_path << "'}";
return file_system_->MoveEntry(source_path, target_path, std::move(callback));
}
AbortCallback CloudFileSystem::Truncate(
const base::FilePath& file_path,
int64_t length,
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(1) << "Truncate {fsid = '" << GetFileSystemId() << "', file_path = '"
<< file_path << "', length = '" << length << "'}";
return file_system_->Truncate(file_path, length, std::move(callback));
}
AbortCallback CloudFileSystem::AddWatcher(
const GURL& origin,
const base::FilePath& entry_path,
bool recursive,
bool persistent,
storage::AsyncFileUtil::StatusCallback callback,
storage::WatcherManager::NotificationCallback notification_callback) {
VLOG(2) << "AddWatcher {fsid = '" << GetFileSystemId() << "', origin = '"
<< origin.spec() << "', entry_path = '" << entry_path
<< "', recursive = '" << recursive << "', persistent = '"
<< persistent << "'}";
// Set timer if the File Manager is a watcher.
file_manager_watchers_ +=
file_manager::util::IsFileManagerURL(origin) ? 1 : 0;
if (file_manager_watchers_ > 0 && !timer_.IsRunning()) {
timer_.Start(FROM_HERE, kFileManagerWatcherInterval,
base::BindRepeating(&CloudFileSystem::OnTimer,
weak_ptr_factory_.GetWeakPtr()));
}
return file_system_->AddWatcher(origin, entry_path, recursive, persistent,
std::move(callback),
std::move(notification_callback));
}
void CloudFileSystem::RemoveWatcher(
const GURL& origin,
const base::FilePath& entry_path,
bool recursive,
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(2) << "RemoveWatcher {fsid = '" << GetFileSystemId() << "', origin = '"
<< origin.spec() << "', entry_path = '" << entry_path
<< "', recursive = '" << recursive << "'}";
// Stop timer if the File Manager is not a watcher.
file_manager_watchers_ -=
file_manager::util::IsFileManagerURL(origin) ? 1 : 0;
if (file_manager_watchers_ == 0 && timer_.IsRunning()) {
timer_.Stop();
}
file_system_->RemoveWatcher(origin, entry_path, recursive,
std::move(callback));
}
const ProvidedFileSystemInfo& CloudFileSystem::GetFileSystemInfo() const {
return file_system_->GetFileSystemInfo();
}
OperationRequestManager* CloudFileSystem::GetRequestManager() {
return file_system_->GetRequestManager();
}
Watchers* CloudFileSystem::GetWatchers() {
return file_system_->GetWatchers();
}
const OpenedFiles& CloudFileSystem::GetOpenedFiles() const {
return file_system_->GetOpenedFiles();
}
void CloudFileSystem::AddObserver(ProvidedFileSystemObserver* observer) {
file_system_->AddObserver(observer);
}
void CloudFileSystem::RemoveObserver(ProvidedFileSystemObserver* observer) {
file_system_->RemoveObserver(observer);
}
void CloudFileSystem::Notify(
const base::FilePath& entry_path,
bool recursive,
storage::WatcherManager::ChangeType change_type,
std::unique_ptr<ProvidedFileSystemObserver::Changes> changes,
const std::string& tag,
storage::AsyncFileUtil::StatusCallback callback) {
VLOG(2) << "Notify {fsid = '" << GetFileSystemId() << "', recursive = '"
<< recursive << "', change_type = '" << change_type << "', tag = '"
<< tag << "', changes = {" << changes.get() << "}}";
if (content_cache_ && changes) {
content_cache_->Notify(*changes);
}
return file_system_->Notify(entry_path, recursive, change_type,
std::move(changes), tag, std::move(callback));
}
void CloudFileSystem::Configure(
storage::AsyncFileUtil::StatusCallback callback) {
return file_system_->Configure(std::move(callback));
}
base::WeakPtr<ProvidedFileSystemInterface> CloudFileSystem::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
std::unique_ptr<ScopedUserInteraction>
CloudFileSystem::StartUserInteraction() {
return file_system_->StartUserInteraction();
}
const std::string CloudFileSystem::GetFileSystemId() const {
return file_system_->GetFileSystemInfo().file_system_id();
}
void CloudFileSystem::OnTimer() {
VLOG(2) << "OnTimer";
// TODO(b/317137739): Replace this with a proper API call once one is
// introduced.
// Request that the file system syncs with the Cloud. The entry path is
// insignficant, just pass it root.
ExecuteAction({base::FilePath("/")}, kODFSSyncWithCloudAction,
base::BindOnce([](base::File::Error result) {
VLOG(1) << "Action " << kODFSSyncWithCloudAction
<< " completed: " << result;
}));
}
void CloudFileSystem::OnOpenFileCompleted(
const base::FilePath& file_path,
OpenFileMode mode,
OpenFileCallback callback,
int file_handle,
base::File::Error result,
std::unique_ptr<EntryMetadata> metadata) {
VLOG(2) << "OnOpenFileCompleted {fsid = " << GetFileSystemId()
<< ", file_handle = '" << file_handle << "', result = '" << result
<< "', metadata = " << metadata.get() << "}";
if (result == base::File::FILE_OK) {
const std::string version_tag = GetVersionTag(metadata.get());
opened_files_.try_emplace(
file_handle, OpenedCloudFile(file_path, mode, file_handle, version_tag,
GetCloudSize(metadata.get())));
// Notify the cache with the observed version tag.
content_cache_->ObservedVersionTag(file_path, version_tag);
} else if (content_cache_ && result == base::File::FILE_ERROR_NOT_FOUND) {
// The file doesn't exist on the FSP, evict it from the cache.
content_cache_->Evict(file_path);
}
std::move(callback).Run(file_handle, result, std::move(metadata));
}
void CloudFileSystem::OnCloseFileCompleted(
int file_handle,
storage::AsyncFileUtil::StatusCallback callback,
base::File::Error result) {
VLOG(2) << "OnCloseFileCompleted {fsid = " << GetFileSystemId()
<< ", file_handle = '" << file_handle << "', result = '" << result
<< "}";
// Closing is always final. Even if an error happened, we remove it from the
// list of opened files.
if (content_cache_) {
const auto& opened_file = opened_files_.extract(file_handle);
if (!opened_file.empty()) {
content_cache_->CloseFile(opened_file.mapped());
}
}
std::move(callback).Run(result);
}
void CloudFileSystem::OnGetMetadataCompleted(
const base::FilePath& entry_path,
GetMetadataCallback callback,
std::unique_ptr<EntryMetadata> entry_metadata,
base::File::Error result) {
VLOG(2) << "OnGetMetadataCompleted {fsid = " << GetFileSystemId()
<< ", entry_path = '" << entry_path << "', result = '" << result
<< "', metadata = " << entry_metadata.get() << "}";
if (content_cache_) {
if (result == base::File::FILE_ERROR_NOT_FOUND) {
// The file doesn't exist on the FSP, evict it from the cache.
content_cache_->Evict(entry_path);
} else if (result == base::File::FILE_OK) {
// Notify the cache with the observed version tag.
content_cache_->ObservedVersionTag(entry_path,
GetVersionTag(entry_metadata.get()));
}
}
std::move(callback).Run(std::move(entry_metadata), result);
}
void CloudFileSystem::OnWriteFileCompleted(
int file_handle,
storage::AsyncFileUtil::StatusCallback callback,
base::File::Error result) {
VLOG(2) << "OnWriteFileCompleted {fsid = " << GetFileSystemId()
<< ", file_handle = '" << file_handle << "', result = '" << result
<< "}";
if (content_cache_ && result == base::File::FILE_OK) {
const auto& opened_file = opened_files_.extract(file_handle);
if (!opened_file.empty()) {
// The cached file is now out of date.
content_cache_->Evict(opened_file.mapped().file_path);
}
}
std::move(callback).Run(result);
}
void CloudFileSystem::OnDeleteEntryCompleted(
const base::FilePath& entry_path,
storage::AsyncFileUtil::StatusCallback callback,
base::File::Error result) {
VLOG(2) << "OnDeleteEntryCompleted {fsid = " << GetFileSystemId()
<< ", entry_path = '" << entry_path << "', result = '" << result
<< "}";
if (content_cache_ && result == base::File::FILE_OK) {
// The cached file should be deleted.
content_cache_->Evict(entry_path);
}
std::move(callback).Run(result);
}
void CloudFileSystem::OnItemEvicted(const base::FilePath& fsp_path) {
VLOG(1) << fsp_path << " evicted from the content cache";
RemoveWatcher(
GetContentCacheURL(), fsp_path, /*recursive=*/false,
base::BindOnce(
[](const base::FilePath& fsp_path, base::File::Error result) {
VLOG(1) << "Removed file watcher on '" << fsp_path
<< "' result: " << result;
},
fsp_path));
}
} // namespace ash::file_system_provider