| // Copyright 2016 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/ash/arc/fileapi/arc_documents_provider_root.h" |
| |
| #include <algorithm> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/check_op.h" |
| #include "base/files/file.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/thread_pool.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "chrome/browser/ash/arc/fileapi/arc_content_file_system_size_util.h" |
| #include "chrome/browser/ash/arc/fileapi/arc_documents_provider_util.h" |
| #include "components/arc/arc_features.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "net/base/mime_util.h" |
| #include "url/gurl.h" |
| |
| using content::BrowserThread; |
| using EntryList = storage::AsyncFileUtil::EntryList; |
| |
| namespace arc { |
| |
| namespace { |
| |
| // Directory cache will be cleared this duration after it is built. |
| constexpr base::TimeDelta kCacheExpiration = base::Seconds(60); |
| |
| void OnGetFileSizeFromOpenFile( |
| ArcDocumentsProviderRoot::GetFileInfoCallback callback, |
| base::File::Info info, |
| base::File::Error error, |
| int64_t size) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (error == base::File::FILE_OK) { |
| info.size = size; |
| std::move(callback).Run(error, info); |
| } else { |
| std::move(callback).Run(error, base::File::Info()); |
| } |
| } |
| |
| void OnResolveToContentUrl( |
| ArcDocumentsProviderRoot::GetFileInfoCallback callback, |
| ArcFileSystemOperationRunner* runner, |
| const base::File::Info& info, |
| const GURL& content_url) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| GetFileSizeFromOpenFileOnUIThread( |
| content_url, runner, |
| base::BindOnce(&OnGetFileSizeFromOpenFile, std::move(callback), info)); |
| } |
| |
| } // namespace |
| |
| // Represents the status of a document watcher. |
| struct ArcDocumentsProviderRoot::WatcherData { |
| // ID of a watcher in the remote file system service. |
| // |
| // Valid IDs are represented by positive integers. An invalid watcher is |
| // represented by |kInvalidWatcherId|, which occurs in several cases: |
| // |
| // - AddWatcher request is still in-flight. In this case, a valid ID is set |
| // to |inflight_request_id|. |
| // |
| // - The remote file system service notified us that it stopped and all |
| // watchers were forgotten. Such watchers are still tracked here, but they |
| // are not known by the remote service. |
| int64_t id; |
| |
| // A unique ID of AddWatcher() request. |
| // |
| // While AddWatcher() is in-flight, a positive integer is set to this |
| // variable, and |id| is |kInvalidWatcherId|. Otherwise it is set to |
| // |kInvalidWatcherRequestId|. |
| uint64_t inflight_request_id; |
| }; |
| |
| // Cache of directory contents. |
| struct ArcDocumentsProviderRoot::DirectoryCache { |
| // Files under the directory. |
| NameToDocumentMap mapping; |
| |
| // Timer to delete this cache. |
| base::OneShotTimer clear_timer; |
| }; |
| |
| // static |
| const int64_t ArcDocumentsProviderRoot::kInvalidWatcherId = -1; |
| // static |
| const uint64_t ArcDocumentsProviderRoot::kInvalidWatcherRequestId = 0; |
| // static |
| const ArcDocumentsProviderRoot::WatcherData |
| ArcDocumentsProviderRoot::kInvalidWatcherData = {kInvalidWatcherId, |
| kInvalidWatcherRequestId}; |
| |
| ArcDocumentsProviderRoot::ArcDocumentsProviderRoot( |
| ArcFileSystemOperationRunner* runner, |
| const std::string& authority, |
| const std::string& root_document_id, |
| const std::string& root_id, |
| bool read_only, |
| const std::vector<std::string>& mime_types) |
| : runner_(runner), |
| authority_(authority), |
| root_document_id_(root_document_id), |
| root_id_(root_id), |
| read_only_(read_only), |
| mime_types_(mime_types) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| runner_->AddObserver(this); |
| } |
| |
| ArcDocumentsProviderRoot::~ArcDocumentsProviderRoot() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| runner_->RemoveObserver(this); |
| } |
| |
| void ArcDocumentsProviderRoot::GetFileInfo(const base::FilePath& path, |
| int fields, |
| GetFileInfoCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (path.IsAbsolute()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND, |
| base::File::Info()); |
| return; |
| } |
| |
| // Specially handle the root directory since Files app does not update the |
| // list of file systems (left pane) until all volumes respond to GetMetadata |
| // requests to root directories. |
| if (path.empty()) { |
| base::File::Info info; |
| info.size = -1; |
| info.is_directory = true; |
| info.is_symbolic_link = false; |
| info.last_modified = info.last_accessed = info.creation_time = |
| base::Time::UnixEpoch(); // arbitrary |
| std::move(callback).Run(base::File::FILE_OK, info); |
| return; |
| } |
| |
| GetDocument( |
| path, base::BindOnce(&ArcDocumentsProviderRoot::GetFileInfoFromDocument, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| path, fields)); |
| } |
| |
| void ArcDocumentsProviderRoot::ReadDirectory(const base::FilePath& path, |
| ReadDirectoryCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ResolveToDocumentId( |
| path, |
| base::BindOnce(&ArcDocumentsProviderRoot::ReadDirectoryWithDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ArcDocumentsProviderRoot::DeleteFile(const base::FilePath& path, |
| StatusCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (read_only_) { |
| std::move(callback).Run(base::File::FILE_ERROR_ACCESS_DENIED); |
| return; |
| } |
| ResolveToDocumentId( |
| path, |
| base::BindOnce(&ArcDocumentsProviderRoot::DeleteFileWithDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ArcDocumentsProviderRoot::CreateFile(const base::FilePath& path, |
| StatusCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (read_only_) { |
| std::move(callback).Run(base::File::FILE_ERROR_ACCESS_DENIED); |
| return; |
| } |
| ResolveToDocumentId( |
| path, base::BindOnce( |
| &ArcDocumentsProviderRoot::CreateFileAfterConflictCheck, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), path)); |
| } |
| |
| void ArcDocumentsProviderRoot::CreateDirectory(const base::FilePath& path, |
| StatusCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (read_only_) { |
| std::move(callback).Run(base::File::FILE_ERROR_ACCESS_DENIED); |
| return; |
| } |
| ResolveToDocumentId( |
| path, base::BindOnce( |
| &ArcDocumentsProviderRoot::CreateDirectoryAfterConflictCheck, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), path)); |
| } |
| |
| void ArcDocumentsProviderRoot::CopyFileLocal(const base::FilePath& src_path, |
| const base::FilePath& dest_path, |
| StatusCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (read_only_) { |
| std::move(callback).Run(base::File::FILE_ERROR_ACCESS_DENIED); |
| return; |
| } |
| if (src_path == dest_path) { |
| std::move(callback).Run(base::File::FILE_ERROR_INVALID_OPERATION); |
| return; |
| } |
| ResolveToDocumentId( |
| src_path, |
| base::BindOnce(&ArcDocumentsProviderRoot::CopyFileWithSourceDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| dest_path, src_path.BaseName().value())); |
| } |
| |
| void ArcDocumentsProviderRoot::MoveFileLocal(const base::FilePath& src_path, |
| const base::FilePath& dest_path, |
| StatusCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (read_only_) { |
| std::move(callback).Run(base::File::FILE_ERROR_ACCESS_DENIED); |
| return; |
| } |
| if (src_path.DirName() == dest_path.DirName()) { |
| RenameFileInternal(src_path, dest_path.BaseName().value(), |
| std::move(callback)); |
| } else { |
| MoveFileInternal(src_path, dest_path, std::move(callback)); |
| } |
| } |
| |
| void ArcDocumentsProviderRoot::AddWatcher( |
| const base::FilePath& path, |
| WatcherNotificationCallback watcher_callback, |
| WatcherStatusCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (path_to_watcher_data_.count(path)) { |
| std::move(callback).Run(base::File::FILE_ERROR_FAILED); |
| return; |
| } |
| uint64_t watcher_request_id = next_watcher_request_id_++; |
| path_to_watcher_data_.insert( |
| std::make_pair(path, WatcherData{kInvalidWatcherId, watcher_request_id})); |
| ResolveToDocumentId( |
| path, base::BindOnce(&ArcDocumentsProviderRoot::AddWatcherWithDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), path, |
| watcher_request_id, std::move(watcher_callback))); |
| |
| // HACK: Invoke |callback| immediately. |
| // |
| // TODO(crbug.com/698624): Remove this hack. It was introduced because Files |
| // app freezes until AddWatcher() finishes, but it should be handled in Files |
| // app rather than here. |
| std::move(callback).Run(base::File::FILE_OK); |
| } |
| |
| void ArcDocumentsProviderRoot::RemoveWatcher(const base::FilePath& path, |
| WatcherStatusCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| auto iter = path_to_watcher_data_.find(path); |
| if (iter == path_to_watcher_data_.end()) { |
| std::move(callback).Run(base::File::FILE_ERROR_FAILED); |
| return; |
| } |
| int64_t watcher_id = iter->second.id; |
| path_to_watcher_data_.erase(iter); |
| if (watcher_id == kInvalidWatcherId) { |
| // This is an invalid watcher. No need to send a request to the remote |
| // service. |
| std::move(callback).Run(base::File::FILE_OK); |
| return; |
| } |
| runner_->RemoveWatcher( |
| watcher_id, |
| base::BindOnce(&ArcDocumentsProviderRoot::OnWatcherRemoved, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ArcDocumentsProviderRoot::ResolveToContentUrl( |
| const base::FilePath& path, |
| ResolveToContentUrlCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ResolveToDocumentId( |
| path, base::BindOnce( |
| &ArcDocumentsProviderRoot::ResolveToContentUrlWithDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ArcDocumentsProviderRoot::GetExtraFileMetadata( |
| const base::FilePath& path, |
| GetExtraMetadataCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (path.IsAbsolute()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND, {}); |
| return; |
| } |
| GetDocument(path, base::BindOnce( |
| &ArcDocumentsProviderRoot::GetExtraMetadataFromDocument, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ArcDocumentsProviderRoot::SetDirectoryCacheExpireSoonForTesting() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| directory_cache_expire_soon_ = true; |
| } |
| |
| void ArcDocumentsProviderRoot::OnWatchersCleared() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| // Mark all watchers invalid. |
| for (auto& entry : path_to_watcher_data_) |
| entry.second = kInvalidWatcherData; |
| } |
| |
| void ArcDocumentsProviderRoot::GetRootSize(GetRootSizeCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (root_id_.empty()) { |
| // Exit early if ID is missing for the given provider authority. |
| std::move(callback).Run(true /* error */, 0, 0); |
| return; |
| } |
| |
| runner_->GetRootSize( |
| authority_, root_id_, |
| base::BindOnce(&ArcDocumentsProviderRoot::OnGetRootSize, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ArcDocumentsProviderRoot::OnGetRootSize(GetRootSizeCallback callback, |
| mojom::RootSizePtr root_size) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (root_size.is_null() || root_size->available_bytes < 0) { |
| // The root_size and its available bytes are required from the file system. |
| std::move(callback).Run(true /* error */, 0, 0); |
| return; |
| } |
| if (root_size->capacity_bytes < 0) { |
| // If available bytes is provided but not capacity bytes, it's still valid. |
| std::move(callback).Run(false, root_size->available_bytes, 0); |
| return; |
| } |
| |
| std::move(callback).Run(false, |
| static_cast<uint64_t>(root_size->available_bytes), |
| static_cast<uint64_t>(root_size->capacity_bytes)); |
| } |
| |
| void ArcDocumentsProviderRoot::GetFileInfoFromDocument( |
| GetFileInfoCallback callback, |
| const base::FilePath& path, |
| int fields, |
| base::File::Error error, |
| const mojom::DocumentPtr& document) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (error != base::File::FILE_OK) { |
| std::move(callback).Run(error, {}); |
| return; |
| } |
| DCHECK(document); |
| |
| base::File::Info info; |
| if (fields & storage::FileSystemOperation::GET_METADATA_FIELD_SIZE) { |
| info.size = document->size; |
| } |
| bool is_directory = document->mime_type == kAndroidDirectoryMimeType; |
| if (fields & storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY) { |
| info.is_directory = is_directory; |
| } |
| info.is_symbolic_link = false; |
| if (fields & storage::FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED) { |
| info.last_modified = info.last_accessed = info.creation_time = |
| base::Time::FromJavaTime(document->last_modified); |
| } |
| |
| if (base::FeatureList::IsEnabled(kDocumentsProviderUnknownSizeFeature) && |
| (fields & storage::FileSystemOperation::GET_METADATA_FIELD_SIZE) && |
| info.size == kUnknownFileSize && !is_directory) { |
| // We don't know the size from metadata and the size is requested, find it |
| // out by opening the file |
| ResolveToContentUrl( |
| path, base::BindOnce(&OnResolveToContentUrl, std::move(callback), |
| runner_, info)); |
| } else { |
| std::move(callback).Run(base::File::FILE_OK, info); |
| } |
| } |
| |
| void ArcDocumentsProviderRoot::ReadDirectoryWithDocumentId( |
| ReadDirectoryCallback callback, |
| const std::string& document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND, {}); |
| return; |
| } |
| ReadDirectoryInternal( |
| document_id, true /* force_refresh */, |
| base::BindOnce( |
| &ArcDocumentsProviderRoot::ReadDirectoryWithNameToDocumentMap, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ArcDocumentsProviderRoot::ReadDirectoryWithNameToDocumentMap( |
| ReadDirectoryCallback callback, |
| base::File::Error error, |
| const NameToDocumentMap& mapping) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (error != base::File::FILE_OK) { |
| std::move(callback).Run(error, {}); |
| return; |
| } |
| |
| std::vector<ThinFileInfo> files; |
| for (const auto& pair : mapping) { |
| const base::FilePath::StringType& name = pair.first; |
| const mojom::DocumentPtr& document = pair.second; |
| files.emplace_back( |
| ThinFileInfo{name, document->document_id, |
| document->mime_type == kAndroidDirectoryMimeType, |
| base::Time::FromJavaTime(document->last_modified)}); |
| } |
| std::move(callback).Run(base::File::FILE_OK, std::move(files)); |
| } |
| |
| void ArcDocumentsProviderRoot::DeleteFileWithDocumentId( |
| StatusCallback callback, |
| const std::string& document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| |
| runner_->DeleteDocument( |
| authority_, document_id, |
| base::BindOnce(&ArcDocumentsProviderRoot::OnFileDeleted, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ArcDocumentsProviderRoot::OnFileDeleted(StatusCallback callback, |
| bool success) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::move(callback).Run(success ? base::File::FILE_OK |
| : base::File::FILE_ERROR_FAILED); |
| } |
| |
| void ArcDocumentsProviderRoot::CreateFileAfterConflictCheck( |
| StatusCallback callback, |
| const base::FilePath& path, |
| const std::string& document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_EXISTS); |
| return; |
| } |
| ResolveToDocumentId( |
| path.DirName(), |
| base::BindOnce(&ArcDocumentsProviderRoot::CreateFileWithParentDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| path.BaseName())); |
| } |
| |
| void ArcDocumentsProviderRoot::CreateFileWithParentDocumentId( |
| StatusCallback callback, |
| const base::FilePath& basename, |
| const std::string& parent_document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (parent_document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| std::string mime_type; |
| if (!net::GetMimeTypeFromFile(basename, &mime_type)) |
| mime_type = "application/octet-stream"; |
| runner_->CreateDocument( |
| authority_, parent_document_id, mime_type, basename.value(), |
| base::BindOnce(&ArcDocumentsProviderRoot::OnFileCreated, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| parent_document_id)); |
| } |
| |
| void ArcDocumentsProviderRoot::CreateDirectoryAfterConflictCheck( |
| StatusCallback callback, |
| const base::FilePath& path, |
| const std::string& document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_EXISTS); |
| return; |
| } |
| ResolveToDocumentId( |
| path.DirName(), |
| base::BindOnce( |
| &ArcDocumentsProviderRoot::CreateDirectoryWithParentDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| path.BaseName())); |
| } |
| |
| void ArcDocumentsProviderRoot::CreateDirectoryWithParentDocumentId( |
| StatusCallback callback, |
| const base::FilePath& basename, |
| const std::string& parent_document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (parent_document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| runner_->CreateDocument( |
| authority_, parent_document_id, kAndroidDirectoryMimeType, |
| basename.value(), |
| base::BindOnce(&ArcDocumentsProviderRoot::OnFileCreated, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| parent_document_id)); |
| } |
| |
| void ArcDocumentsProviderRoot::OnFileCreated( |
| StatusCallback callback, |
| const std::string& parent_document_id, |
| mojom::DocumentPtr document) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (document.is_null()) { |
| std::move(callback).Run(base::File::FILE_ERROR_FAILED); |
| return; |
| } |
| // Invalidate directory cache as the directory content is updated. |
| ClearDirectoryCache(parent_document_id); |
| std::move(callback).Run(base::File::FILE_OK); |
| } |
| |
| void ArcDocumentsProviderRoot::RenameFileInternal( |
| const base::FilePath& path, |
| const std::string& display_name, |
| StatusCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ResolveToDocumentId( |
| path, base::BindOnce(&ArcDocumentsProviderRoot::RenameFileWithDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| display_name)); |
| } |
| |
| void ArcDocumentsProviderRoot::RenameFileWithDocumentId( |
| StatusCallback callback, |
| const std::string& display_name, |
| const std::string& document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| // TODO(fukino): Consider updating MIME type of the document based when the |
| // file extension is changed. |
| runner_->RenameDocument( |
| authority_, document_id, display_name, |
| base::BindOnce(&ArcDocumentsProviderRoot::OnFileRenamed, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ArcDocumentsProviderRoot::OnFileRenamed(StatusCallback callback, |
| mojom::DocumentPtr document) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (document.is_null()) { |
| std::move(callback).Run(base::File::FILE_ERROR_FAILED); |
| return; |
| } |
| std::move(callback).Run(base::File::FILE_OK); |
| } |
| |
| void ArcDocumentsProviderRoot::CopyFileWithSourceDocumentId( |
| StatusCallback callback, |
| const base::FilePath& target_path, |
| const std::string& source_display_name, |
| const std::string& source_document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (source_document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| const std::string target_display_name = target_path.BaseName().value(); |
| const std::string target_display_name_to_rename = |
| source_display_name == target_display_name ? "" : target_display_name; |
| ResolveToDocumentId( |
| target_path.DirName(), |
| base::BindOnce( |
| &ArcDocumentsProviderRoot::CopyFileWithTargetParentDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| source_document_id, target_display_name_to_rename)); |
| } |
| |
| void ArcDocumentsProviderRoot::CopyFileWithTargetParentDocumentId( |
| StatusCallback callback, |
| const std::string& source_document_id, |
| const std::string& target_display_name_to_rename, |
| const std::string& target_parent_document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (target_parent_document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| runner_->CopyDocument( |
| authority_, source_document_id, target_parent_document_id, |
| base::BindOnce(&ArcDocumentsProviderRoot::OnFileCopied, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| target_display_name_to_rename)); |
| } |
| |
| void ArcDocumentsProviderRoot::OnFileCopied( |
| StatusCallback callback, |
| const std::string& target_display_name_to_rename, |
| mojom::DocumentPtr document) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (document.is_null()) { |
| std::move(callback).Run(base::File::FILE_ERROR_FAILED); |
| return; |
| } |
| if (target_display_name_to_rename.empty()) { |
| std::move(callback).Run(base::File::FILE_OK); |
| return; |
| } |
| RenameFileWithDocumentId(std::move(callback), target_display_name_to_rename, |
| document->document_id); |
| } |
| |
| void ArcDocumentsProviderRoot::MoveFileInternal( |
| const base::FilePath& source_path, |
| const base::FilePath& target_path, |
| StatusCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ResolveToDocumentId( |
| source_path, |
| base::BindOnce(&ArcDocumentsProviderRoot::MoveFileWithSourceDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| source_path.DirName(), target_path, |
| source_path.BaseName().value())); |
| } |
| |
| void ArcDocumentsProviderRoot::MoveFileWithSourceDocumentId( |
| StatusCallback callback, |
| const base::FilePath& source_parent_path, |
| const base::FilePath& target_path, |
| const std::string& source_display_name, |
| const std::string& source_document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (source_document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| ResolveToDocumentId( |
| source_parent_path, |
| base::BindOnce( |
| &ArcDocumentsProviderRoot::MoveFileWithSourceParentDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| source_document_id, target_path, source_display_name)); |
| } |
| |
| void ArcDocumentsProviderRoot::MoveFileWithSourceParentDocumentId( |
| StatusCallback callback, |
| const std::string& source_document_id, |
| const base::FilePath& target_path, |
| const std::string& source_display_name, |
| const std::string& source_parent_document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (source_parent_document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| const std::string target_display_name = target_path.BaseName().value(); |
| const std::string target_display_name_to_rename = |
| source_display_name == target_display_name ? "" : target_display_name; |
| ResolveToDocumentId( |
| target_path.DirName(), |
| base::BindOnce( |
| &ArcDocumentsProviderRoot::MoveFileWithTargetParentDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| source_document_id, source_parent_document_id, |
| target_display_name_to_rename)); |
| } |
| |
| void ArcDocumentsProviderRoot::MoveFileWithTargetParentDocumentId( |
| StatusCallback callback, |
| const std::string& source_document_id, |
| const std::string& source_parent_document_id, |
| const std::string& target_display_name_to_rename, |
| const std::string& target_parent_document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (target_parent_document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| runner_->MoveDocument( |
| authority_, source_document_id, source_parent_document_id, |
| target_parent_document_id, |
| base::BindOnce(&ArcDocumentsProviderRoot::OnFileMoved, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| target_display_name_to_rename)); |
| } |
| |
| void ArcDocumentsProviderRoot::OnFileMoved( |
| StatusCallback callback, |
| const std::string& target_display_name_to_rename, |
| mojom::DocumentPtr document) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (document.is_null()) { |
| std::move(callback).Run(base::File::FILE_ERROR_FAILED); |
| return; |
| } |
| if (target_display_name_to_rename.empty()) { |
| std::move(callback).Run(base::File::FILE_OK); |
| return; |
| } |
| RenameFileWithDocumentId(std::move(callback), target_display_name_to_rename, |
| document->document_id); |
| } |
| |
| void ArcDocumentsProviderRoot::AddWatcherWithDocumentId( |
| const base::FilePath& path, |
| uint64_t watcher_request_id, |
| WatcherNotificationCallback watcher_callback, |
| const std::string& document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (IsWatcherInflightRequestCanceled(path, watcher_request_id)) |
| return; |
| |
| if (document_id.empty()) { |
| DCHECK(path_to_watcher_data_.count(path)); |
| path_to_watcher_data_[path] = kInvalidWatcherData; |
| return; |
| } |
| |
| runner_->AddWatcher( |
| authority_, document_id, std::move(watcher_callback), |
| base::BindOnce(&ArcDocumentsProviderRoot::OnWatcherAdded, |
| weak_ptr_factory_.GetWeakPtr(), path, watcher_request_id)); |
| } |
| |
| void ArcDocumentsProviderRoot::OnWatcherAdded(const base::FilePath& path, |
| uint64_t watcher_request_id, |
| int64_t watcher_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (IsWatcherInflightRequestCanceled(path, watcher_request_id)) { |
| runner_->RemoveWatcher( |
| watcher_id, |
| base::BindOnce(&ArcDocumentsProviderRoot::OnWatcherAddedButRemoved, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| DCHECK(path_to_watcher_data_.count(path)); |
| path_to_watcher_data_[path] = |
| WatcherData{watcher_id < 0 ? kInvalidWatcherId : watcher_id, |
| kInvalidWatcherRequestId}; |
| } |
| |
| void ArcDocumentsProviderRoot::OnWatcherAddedButRemoved(bool success) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| // Ignore |success|. |
| } |
| |
| void ArcDocumentsProviderRoot::OnWatcherRemoved(WatcherStatusCallback callback, |
| bool success) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::move(callback).Run(success ? base::File::FILE_OK |
| : base::File::FILE_ERROR_FAILED); |
| } |
| |
| bool ArcDocumentsProviderRoot::IsWatcherInflightRequestCanceled( |
| const base::FilePath& path, |
| uint64_t watcher_request_id) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| auto iter = path_to_watcher_data_.find(path); |
| return (iter == path_to_watcher_data_.end() || |
| iter->second.inflight_request_id != watcher_request_id); |
| } |
| |
| void ArcDocumentsProviderRoot::ResolveToContentUrlWithDocumentId( |
| ResolveToContentUrlCallback callback, |
| const std::string& document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (document_id.empty()) { |
| std::move(callback).Run(GURL()); |
| return; |
| } |
| std::move(callback).Run(BuildDocumentUrl(authority_, document_id)); |
| } |
| |
| void ArcDocumentsProviderRoot::GetExtraMetadataFromDocument( |
| GetExtraMetadataCallback callback, |
| base::File::Error error, |
| const mojom::DocumentPtr& document) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (error != base::File::FILE_OK) { |
| std::move(callback).Run(error, {}); |
| return; |
| } |
| DCHECK(document); |
| |
| ExtraFileMetadata metadata; |
| metadata.supports_delete = document->supports_delete; |
| metadata.supports_rename = document->supports_rename; |
| metadata.dir_supports_create = document->dir_supports_create; |
| metadata.supports_thumbnail = document->supports_thumbnail; |
| metadata.last_modified = base::Time::FromJavaTime(document->last_modified); |
| metadata.size = document->size; |
| std::move(callback).Run(base::File::FILE_OK, metadata); |
| } |
| |
| void ArcDocumentsProviderRoot::GetDocument(const base::FilePath& path, |
| GetDocumentCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| base::FilePath basename = path.BaseName(); |
| base::FilePath parent = path.DirName(); |
| if (parent.value() == base::FilePath::kCurrentDirectory) |
| parent = base::FilePath(); |
| |
| ResolveToDocumentId( |
| parent, |
| base::BindOnce(&ArcDocumentsProviderRoot::GetDocumentWithParentDocumentId, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), |
| basename)); |
| } |
| |
| void ArcDocumentsProviderRoot::GetDocumentWithParentDocumentId( |
| GetDocumentCallback callback, |
| const base::FilePath& basename, |
| const std::string& parent_document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (parent_document_id.empty()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND, |
| mojom::DocumentPtr()); |
| return; |
| } |
| ReadDirectoryInternal( |
| parent_document_id, false /* force_refresh */, |
| base::BindOnce( |
| &ArcDocumentsProviderRoot::GetDocumentWithNameToDocumentMap, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), basename)); |
| } |
| |
| void ArcDocumentsProviderRoot::GetDocumentWithNameToDocumentMap( |
| GetDocumentCallback callback, |
| const base::FilePath& basename, |
| base::File::Error error, |
| const NameToDocumentMap& mapping) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| if (error != base::File::FILE_OK) { |
| std::move(callback).Run(error, mojom::DocumentPtr()); |
| return; |
| } |
| |
| auto iter = mapping.find(basename.value()); |
| if (iter == mapping.end()) { |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND, |
| mojom::DocumentPtr()); |
| return; |
| } |
| |
| std::move(callback).Run(base::File::FILE_OK, iter->second); |
| } |
| |
| void ArcDocumentsProviderRoot::ResolveToDocumentId( |
| const base::FilePath& path, |
| ResolveToDocumentIdCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::vector<base::FilePath::StringType> components; |
| path.GetComponents(&components); |
| ResolveToDocumentIdRecursively(root_document_id_, components, |
| std::move(callback)); |
| } |
| |
| void ArcDocumentsProviderRoot::ResolveToDocumentIdRecursively( |
| const std::string& document_id, |
| const std::vector<base::FilePath::StringType>& components, |
| ResolveToDocumentIdCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (components.empty()) { |
| std::move(callback).Run(document_id); |
| return; |
| } |
| ReadDirectoryInternal( |
| document_id, false /* force_refresh */, |
| base::BindOnce(&ArcDocumentsProviderRoot:: |
| ResolveToDocumentIdRecursivelyWithNameToDocumentMap, |
| weak_ptr_factory_.GetWeakPtr(), components, |
| std::move(callback))); |
| } |
| |
| void ArcDocumentsProviderRoot:: |
| ResolveToDocumentIdRecursivelyWithNameToDocumentMap( |
| const std::vector<base::FilePath::StringType>& components, |
| ResolveToDocumentIdCallback callback, |
| base::File::Error error, |
| const NameToDocumentMap& mapping) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(!components.empty()); |
| if (error != base::File::FILE_OK) { |
| std::move(callback).Run(std::string()); |
| return; |
| } |
| auto iter = mapping.find(components[0]); |
| if (iter == mapping.end()) { |
| std::move(callback).Run(std::string()); |
| return; |
| } |
| ResolveToDocumentIdRecursively(iter->second->document_id, |
| std::vector<base::FilePath::StringType>( |
| components.begin() + 1, components.end()), |
| std::move(callback)); |
| } |
| |
| void ArcDocumentsProviderRoot::ReadDirectoryInternal( |
| const std::string& document_id, |
| bool force_refresh, |
| ReadDirectoryInternalCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| // Use cache if possible. Note that we do not invalidate it immediately |
| // even if we decide not to use it for now. |
| if (!force_refresh) { |
| auto iter = directory_cache_.find(document_id); |
| if (iter != directory_cache_.end()) { |
| std::move(callback).Run(base::File::FILE_OK, iter->second.mapping); |
| return; |
| } |
| } |
| |
| auto& pending_callbacks = pending_callbacks_map_[document_id]; |
| bool read_in_flight = !pending_callbacks.empty(); |
| pending_callbacks.emplace_back(std::move(callback)); |
| |
| if (read_in_flight) { |
| // There is already an in-flight ReadDirectoryInternal() call, so |
| // just enqueue the callback and return. |
| return; |
| } |
| |
| runner_->GetChildDocuments( |
| authority_, document_id, |
| base::BindOnce( |
| &ArcDocumentsProviderRoot::ReadDirectoryInternalWithChildDocuments, |
| weak_ptr_factory_.GetWeakPtr(), document_id)); |
| } |
| |
| void ArcDocumentsProviderRoot::ReadDirectoryInternalWithChildDocuments( |
| const std::string& document_id, |
| absl::optional<std::vector<mojom::DocumentPtr>> maybe_children) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| auto iter = pending_callbacks_map_.find(document_id); |
| DCHECK(iter != pending_callbacks_map_.end()); |
| |
| std::vector<ReadDirectoryInternalCallback> pending_callbacks = |
| std::move(iter->second); |
| DCHECK(!pending_callbacks.empty()); |
| |
| pending_callbacks_map_.erase(iter); |
| |
| if (!maybe_children) { |
| for (auto& callback : pending_callbacks) |
| std::move(callback).Run(base::File::FILE_ERROR_NOT_FOUND, |
| NameToDocumentMap()); |
| return; |
| } |
| |
| std::vector<mojom::DocumentPtr> children = std::move(maybe_children.value()); |
| |
| // Sort entries to keep the mapping stable as far as possible. |
| std::sort(children.begin(), children.end(), |
| [](const mojom::DocumentPtr& a, const mojom::DocumentPtr& b) { |
| return a->document_id < b->document_id; |
| }); |
| |
| NameToDocumentMap mapping; |
| std::map<base::FilePath::StringType, int> suffix_counters; |
| |
| for (mojom::DocumentPtr& document : children) { |
| base::FilePath::StringType filename = GetFileNameForDocument(document); |
| |
| if (mapping.count(filename) > 0) { |
| // Resolve a conflict by adding a suffix. |
| int& suffix_counter = suffix_counters[filename]; |
| while (true) { |
| ++suffix_counter; |
| std::string suffix = base::StringPrintf(" (%d)", suffix_counter); |
| base::FilePath::StringType new_filename = |
| base::FilePath(filename).InsertBeforeExtensionASCII(suffix).value(); |
| if (mapping.count(new_filename) == 0) { |
| filename = new_filename; |
| break; |
| } |
| } |
| } |
| |
| DCHECK_EQ(0u, mapping.count(filename)); |
| |
| mapping[filename] = std::move(document); |
| } |
| |
| // This may create a new cache, or just update an existing cache. |
| DirectoryCache& cache = directory_cache_[document_id]; |
| cache.mapping = std::move(mapping); |
| cache.clear_timer.Start( |
| FROM_HERE, |
| directory_cache_expire_soon_ ? base::TimeDelta() : kCacheExpiration, |
| base::BindOnce(&ArcDocumentsProviderRoot::ClearDirectoryCache, |
| weak_ptr_factory_.GetWeakPtr(), document_id)); |
| |
| for (auto& callback : pending_callbacks) |
| std::move(callback).Run(base::File::FILE_OK, cache.mapping); |
| } |
| |
| void ArcDocumentsProviderRoot::ClearDirectoryCache( |
| const std::string& document_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| directory_cache_.erase(document_id); |
| } |
| |
| } // namespace arc |