| Index: chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.cc
|
| diff --git a/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.cc b/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.cc
|
| index aa1b86890526123ae9f5168754b6ce218dc540b8..644415c7aae0184586dec13dc5c849c247e95ad6 100644
|
| --- a/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.cc
|
| +++ b/chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.cc
|
| @@ -4,16 +4,77 @@
|
|
|
| #include "chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.h"
|
|
|
| +#include <algorithm>
|
| +#include <utility>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/callback.h"
|
| +#include "base/files/file.h"
|
| #include "base/logging.h"
|
| +#include "base/memory/ptr_util.h"
|
| +#include "base/strings/string_util.h"
|
| +#include "base/strings/stringprintf.h"
|
| +#include "chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.h"
|
| +#include "chrome/browser/chromeos/arc/fileapi/arc_file_system_instance_util.h"
|
| #include "content/public/browser/browser_thread.h"
|
| +#include "net/base/mime_util.h"
|
|
|
| using content::BrowserThread;
|
| +using EntryList = storage::AsyncFileUtil::EntryList;
|
|
|
| namespace arc {
|
|
|
| +namespace {
|
| +
|
| +// Computes a file name for a document.
|
| +// TODO(crbug.com/675868): Consolidate with the similar logic for Drive.
|
| +base::FilePath::StringType GetFileNameForDocument(
|
| + const mojom::DocumentPtr& document) {
|
| + base::FilePath::StringType filename = document->display_name;
|
| +
|
| + // Replace path separators appearing in the file name.
|
| + // Chrome OS is POSIX and kSeparators is "/".
|
| + base::ReplaceChars(filename, base::FilePath::kSeparators, "_", &filename);
|
| +
|
| + // Do not allow an empty file name and all-dots file names.
|
| + if (filename.empty() ||
|
| + filename.find_first_not_of('.', 0) == std::string::npos) {
|
| + filename = "_";
|
| + }
|
| +
|
| + // Since Chrome detects MIME type from file name extensions, we need to change
|
| + // the file name extension of the document if it does not match with its MIME
|
| + // type.
|
| + // For example, Audio Media Provider presents a music file with its title as
|
| + // the file name.
|
| + base::FilePath::StringType extension =
|
| + base::ToLowerASCII(base::FilePath(filename).Extension());
|
| + if (!extension.empty())
|
| + extension = extension.substr(1); // Strip the leading dot.
|
| + std::vector<base::FilePath::StringType> possible_extensions;
|
| + net::GetExtensionsForMimeType(document->mime_type, &possible_extensions);
|
| + if (!possible_extensions.empty() &&
|
| + std::find(possible_extensions.begin(), possible_extensions.end(),
|
| + extension) == possible_extensions.end()) {
|
| + base::FilePath::StringType new_extension;
|
| + if (!net::GetPreferredExtensionForMimeType(document->mime_type,
|
| + &new_extension)) {
|
| + new_extension = possible_extensions[0];
|
| + }
|
| + filename = base::FilePath(filename).AddExtension(new_extension).value();
|
| + }
|
| +
|
| + return filename;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| ArcDocumentsProviderRoot::ArcDocumentsProviderRoot(
|
| const std::string& authority,
|
| - const std::string& root_document_id) {}
|
| + const std::string& root_document_id)
|
| + : authority_(authority),
|
| + root_document_id_(root_document_id),
|
| + weak_ptr_factory_(this) {}
|
|
|
| ArcDocumentsProviderRoot::~ArcDocumentsProviderRoot() {
|
| DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
| @@ -23,14 +84,186 @@ void ArcDocumentsProviderRoot::GetFileInfo(
|
| const base::FilePath& path,
|
| const GetFileInfoCallback& callback) {
|
| DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
| - NOTIMPLEMENTED(); // TODO(crbug.com/671511): Implement this function.
|
| + ResolveToDocumentId(
|
| + path, base::Bind(&ArcDocumentsProviderRoot::GetFileInfoWithDocumentId,
|
| + weak_ptr_factory_.GetWeakPtr(), callback));
|
| }
|
|
|
| void ArcDocumentsProviderRoot::ReadDirectory(
|
| const base::FilePath& path,
|
| const ReadDirectoryCallback& callback) {
|
| DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
| - NOTIMPLEMENTED(); // TODO(crbug.com/671511): Implement this function.
|
| + ResolveToDocumentId(
|
| + path, base::Bind(&ArcDocumentsProviderRoot::ReadDirectoryWithDocumentId,
|
| + weak_ptr_factory_.GetWeakPtr(), callback));
|
| +}
|
| +
|
| +void ArcDocumentsProviderRoot::GetFileInfoWithDocumentId(
|
| + const GetFileInfoCallback& callback,
|
| + const std::string& document_id) {
|
| + if (document_id.empty()) {
|
| + callback.Run(base::File::FILE_ERROR_NOT_FOUND, base::File::Info());
|
| + return;
|
| + }
|
| + file_system_instance_util::GetDocumentOnIOThread(
|
| + authority_, document_id,
|
| + base::Bind(&ArcDocumentsProviderRoot::GetFileInfoWithDocument,
|
| + weak_ptr_factory_.GetWeakPtr(), callback));
|
| +}
|
| +
|
| +void ArcDocumentsProviderRoot::GetFileInfoWithDocument(
|
| + const GetFileInfoCallback& callback,
|
| + mojom::DocumentPtr document) {
|
| + DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
| + if (document.is_null()) {
|
| + callback.Run(base::File::FILE_ERROR_NOT_FOUND, base::File::Info());
|
| + return;
|
| + }
|
| + base::File::Info info;
|
| + info.size = document->size;
|
| + info.is_directory = document->mime_type == kAndroidDirectoryMimeType;
|
| + info.is_symbolic_link = false;
|
| + info.last_modified = info.last_accessed = info.creation_time =
|
| + base::Time::FromJavaTime(document->last_modified);
|
| + callback.Run(base::File::FILE_OK, info);
|
| +}
|
| +
|
| +void ArcDocumentsProviderRoot::ReadDirectoryWithDocumentId(
|
| + const ReadDirectoryCallback& callback,
|
| + const std::string& document_id) {
|
| + DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
| + if (document_id.empty()) {
|
| + callback.Run(base::File::FILE_ERROR_NOT_FOUND, EntryList(),
|
| + false /* has_more */);
|
| + return;
|
| + }
|
| + ReadDirectoryInternal(
|
| + document_id,
|
| + base::Bind(
|
| + &ArcDocumentsProviderRoot::ReadDirectoryWithNameToThinDocumentMap,
|
| + weak_ptr_factory_.GetWeakPtr(), callback));
|
| +}
|
| +
|
| +void ArcDocumentsProviderRoot::ReadDirectoryWithNameToThinDocumentMap(
|
| + const ReadDirectoryCallback& callback,
|
| + base::File::Error error,
|
| + NameToThinDocumentMap mapping) {
|
| + DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
| + if (error != base::File::FILE_OK) {
|
| + callback.Run(error, EntryList(), false /* has_more */);
|
| + return;
|
| + }
|
| + EntryList entry_list;
|
| + for (const auto& pair : mapping) {
|
| + entry_list.emplace_back(pair.first, pair.second.is_directory
|
| + ? storage::DirectoryEntry::DIRECTORY
|
| + : storage::DirectoryEntry::FILE);
|
| + }
|
| + callback.Run(base::File::FILE_OK, entry_list, false /* has_more */);
|
| +}
|
| +
|
| +void ArcDocumentsProviderRoot::ResolveToDocumentId(
|
| + const base::FilePath& path,
|
| + const ResolveToDocumentIdCallback& callback) {
|
| + DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
| + std::vector<base::FilePath::StringType> components;
|
| + path.GetComponents(&components);
|
| + ResolveToDocumentIdRecursively(root_document_id_, components, callback);
|
| +}
|
| +
|
| +void ArcDocumentsProviderRoot::ResolveToDocumentIdRecursively(
|
| + const std::string& document_id,
|
| + const std::vector<base::FilePath::StringType>& components,
|
| + const ResolveToDocumentIdCallback& callback) {
|
| + DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
| + if (components.empty()) {
|
| + callback.Run(document_id);
|
| + return;
|
| + }
|
| + ReadDirectoryInternal(
|
| + document_id,
|
| + base::Bind(&ArcDocumentsProviderRoot::
|
| + ResolveToDocumentIdRecursivelyWithNameToThinDocumentMap,
|
| + weak_ptr_factory_.GetWeakPtr(), components, callback));
|
| +}
|
| +
|
| +void ArcDocumentsProviderRoot::
|
| + ResolveToDocumentIdRecursivelyWithNameToThinDocumentMap(
|
| + const std::vector<base::FilePath::StringType>& components,
|
| + const ResolveToDocumentIdCallback& callback,
|
| + base::File::Error error,
|
| + NameToThinDocumentMap mapping) {
|
| + DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
| + DCHECK(!components.empty());
|
| + if (error != base::File::FILE_OK) {
|
| + callback.Run(std::string());
|
| + return;
|
| + }
|
| + auto iter = mapping.find(components[0]);
|
| + if (iter == mapping.end()) {
|
| + callback.Run(std::string());
|
| + return;
|
| + }
|
| + ResolveToDocumentIdRecursively(iter->second.document_id,
|
| + std::vector<base::FilePath::StringType>(
|
| + components.begin() + 1, components.end()),
|
| + callback);
|
| +}
|
| +
|
| +void ArcDocumentsProviderRoot::ReadDirectoryInternal(
|
| + const std::string& document_id,
|
| + const ReadDirectoryInternalCallback& callback) {
|
| + file_system_instance_util::GetChildDocumentsOnIOThread(
|
| + authority_, document_id,
|
| + base::Bind(
|
| + &ArcDocumentsProviderRoot::ReadDirectoryInternalWithChildDocuments,
|
| + weak_ptr_factory_.GetWeakPtr(), callback));
|
| +}
|
| +
|
| +void ArcDocumentsProviderRoot::ReadDirectoryInternalWithChildDocuments(
|
| + const ReadDirectoryInternalCallback& callback,
|
| + base::Optional<std::vector<mojom::DocumentPtr>> maybe_children) {
|
| + if (!maybe_children) {
|
| + callback.Run(base::File::FILE_ERROR_NOT_FOUND, NameToThinDocumentMap());
|
| + return;
|
| + }
|
| + std::vector<mojom::DocumentPtr>& children = 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;
|
| + });
|
| +
|
| + NameToThinDocumentMap mapping;
|
| + std::map<base::FilePath::StringType, int> suffix_counters;
|
| +
|
| + for (const 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] =
|
| + ThinDocument{document->document_id,
|
| + document->mime_type == kAndroidDirectoryMimeType};
|
| + }
|
| +
|
| + callback.Run(base::File::FILE_OK, std::move(mapping));
|
| }
|
|
|
| } // namespace arc
|
|
|