OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.h" | 5 #include "chrome/browser/chromeos/arc/fileapi/arc_documents_provider_root.h" |
6 | 6 |
| 7 #include <algorithm> |
| 8 #include <utility> |
| 9 |
| 10 #include "base/bind.h" |
| 11 #include "base/callback.h" |
| 12 #include "base/files/file.h" |
7 #include "base/logging.h" | 13 #include "base/logging.h" |
| 14 #include "base/memory/ptr_util.h" |
| 15 #include "base/strings/string_util.h" |
| 16 #include "base/strings/stringprintf.h" |
| 17 #include "chrome/browser/chromeos/arc/fileapi/arc_documents_provider_util.h" |
| 18 #include "chrome/browser/chromeos/arc/fileapi/arc_file_system_instance_util.h" |
8 #include "content/public/browser/browser_thread.h" | 19 #include "content/public/browser/browser_thread.h" |
| 20 #include "net/base/mime_util.h" |
9 | 21 |
10 using content::BrowserThread; | 22 using content::BrowserThread; |
| 23 using EntryList = storage::AsyncFileUtil::EntryList; |
11 | 24 |
12 namespace arc { | 25 namespace arc { |
13 | 26 |
| 27 namespace { |
| 28 |
| 29 // Computes a file name for a document. |
| 30 // TODO(crbug.com/675868): Consolidate with the similar logic for Drive. |
| 31 base::FilePath::StringType GetFileNameForDocument( |
| 32 const mojom::DocumentPtr& document) { |
| 33 base::FilePath::StringType filename = document->display_name; |
| 34 |
| 35 // Replace path separators appearing in the file name. |
| 36 // Chrome OS is POSIX and kSeparators is "/". |
| 37 base::ReplaceChars(filename, base::FilePath::kSeparators, "_", &filename); |
| 38 |
| 39 // Do not allow an empty file name and all-dots file names. |
| 40 if (filename.empty() || |
| 41 filename.find_first_not_of('.', 0) == std::string::npos) { |
| 42 filename = "_"; |
| 43 } |
| 44 |
| 45 // Since Chrome detects MIME type from file name extensions, we need to change |
| 46 // the file name extension of the document if it does not match with its MIME |
| 47 // type. |
| 48 // For example, Audio Media Provider presents a music file with its title as |
| 49 // the file name. |
| 50 base::FilePath::StringType extension = |
| 51 base::ToLowerASCII(base::FilePath(filename).Extension()); |
| 52 if (!extension.empty()) |
| 53 extension = extension.substr(1); // Strip the leading dot. |
| 54 std::vector<base::FilePath::StringType> possible_extensions; |
| 55 net::GetExtensionsForMimeType(document->mime_type, &possible_extensions); |
| 56 if (!possible_extensions.empty() && |
| 57 std::find(possible_extensions.begin(), possible_extensions.end(), |
| 58 extension) == possible_extensions.end()) { |
| 59 base::FilePath::StringType new_extension; |
| 60 if (!net::GetPreferredExtensionForMimeType(document->mime_type, |
| 61 &new_extension)) { |
| 62 new_extension = possible_extensions[0]; |
| 63 } |
| 64 filename = base::FilePath(filename).AddExtension(new_extension).value(); |
| 65 } |
| 66 |
| 67 return filename; |
| 68 } |
| 69 |
| 70 } // namespace |
| 71 |
14 ArcDocumentsProviderRoot::ArcDocumentsProviderRoot( | 72 ArcDocumentsProviderRoot::ArcDocumentsProviderRoot( |
15 const std::string& authority, | 73 const std::string& authority, |
16 const std::string& root_document_id) {} | 74 const std::string& root_document_id) |
| 75 : authority_(authority), |
| 76 root_document_id_(root_document_id), |
| 77 weak_ptr_factory_(this) {} |
17 | 78 |
18 ArcDocumentsProviderRoot::~ArcDocumentsProviderRoot() { | 79 ArcDocumentsProviderRoot::~ArcDocumentsProviderRoot() { |
19 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 80 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
20 } | 81 } |
21 | 82 |
22 void ArcDocumentsProviderRoot::GetFileInfo( | 83 void ArcDocumentsProviderRoot::GetFileInfo( |
23 const base::FilePath& path, | 84 const base::FilePath& path, |
24 const GetFileInfoCallback& callback) { | 85 const GetFileInfoCallback& callback) { |
25 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 86 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
26 NOTIMPLEMENTED(); // TODO(crbug.com/671511): Implement this function. | 87 ResolveToDocumentId( |
| 88 path, base::Bind(&ArcDocumentsProviderRoot::GetFileInfoWithDocumentId, |
| 89 weak_ptr_factory_.GetWeakPtr(), callback)); |
27 } | 90 } |
28 | 91 |
29 void ArcDocumentsProviderRoot::ReadDirectory( | 92 void ArcDocumentsProviderRoot::ReadDirectory( |
30 const base::FilePath& path, | 93 const base::FilePath& path, |
31 const ReadDirectoryCallback& callback) { | 94 const ReadDirectoryCallback& callback) { |
32 DCHECK_CURRENTLY_ON(BrowserThread::IO); | 95 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
33 NOTIMPLEMENTED(); // TODO(crbug.com/671511): Implement this function. | 96 ResolveToDocumentId( |
| 97 path, base::Bind(&ArcDocumentsProviderRoot::ReadDirectoryWithDocumentId, |
| 98 weak_ptr_factory_.GetWeakPtr(), callback)); |
| 99 } |
| 100 |
| 101 void ArcDocumentsProviderRoot::GetFileInfoWithDocumentId( |
| 102 const GetFileInfoCallback& callback, |
| 103 const std::string& document_id) { |
| 104 if (document_id.empty()) { |
| 105 callback.Run(base::File::FILE_ERROR_NOT_FOUND, base::File::Info()); |
| 106 return; |
| 107 } |
| 108 file_system_instance_util::GetDocumentOnIOThread( |
| 109 authority_, document_id, |
| 110 base::Bind(&ArcDocumentsProviderRoot::GetFileInfoWithDocument, |
| 111 weak_ptr_factory_.GetWeakPtr(), callback)); |
| 112 } |
| 113 |
| 114 void ArcDocumentsProviderRoot::GetFileInfoWithDocument( |
| 115 const GetFileInfoCallback& callback, |
| 116 mojom::DocumentPtr document) { |
| 117 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 118 if (document.is_null()) { |
| 119 callback.Run(base::File::FILE_ERROR_NOT_FOUND, base::File::Info()); |
| 120 return; |
| 121 } |
| 122 base::File::Info info; |
| 123 info.size = document->size; |
| 124 info.is_directory = document->mime_type == kAndroidDirectoryMimeType; |
| 125 info.is_symbolic_link = false; |
| 126 info.last_modified = info.last_accessed = info.creation_time = |
| 127 base::Time::FromJavaTime(document->last_modified); |
| 128 callback.Run(base::File::FILE_OK, info); |
| 129 } |
| 130 |
| 131 void ArcDocumentsProviderRoot::ReadDirectoryWithDocumentId( |
| 132 const ReadDirectoryCallback& callback, |
| 133 const std::string& document_id) { |
| 134 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 135 if (document_id.empty()) { |
| 136 callback.Run(base::File::FILE_ERROR_NOT_FOUND, EntryList(), |
| 137 false /* has_more */); |
| 138 return; |
| 139 } |
| 140 ReadDirectoryInternal( |
| 141 document_id, |
| 142 base::Bind( |
| 143 &ArcDocumentsProviderRoot::ReadDirectoryWithNameToThinDocumentMap, |
| 144 weak_ptr_factory_.GetWeakPtr(), callback)); |
| 145 } |
| 146 |
| 147 void ArcDocumentsProviderRoot::ReadDirectoryWithNameToThinDocumentMap( |
| 148 const ReadDirectoryCallback& callback, |
| 149 base::File::Error error, |
| 150 NameToThinDocumentMap mapping) { |
| 151 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 152 if (error != base::File::FILE_OK) { |
| 153 callback.Run(error, EntryList(), false /* has_more */); |
| 154 return; |
| 155 } |
| 156 EntryList entry_list; |
| 157 for (const auto& pair : mapping) { |
| 158 entry_list.emplace_back(pair.first, pair.second.is_directory |
| 159 ? storage::DirectoryEntry::DIRECTORY |
| 160 : storage::DirectoryEntry::FILE); |
| 161 } |
| 162 callback.Run(base::File::FILE_OK, entry_list, false /* has_more */); |
| 163 } |
| 164 |
| 165 void ArcDocumentsProviderRoot::ResolveToDocumentId( |
| 166 const base::FilePath& path, |
| 167 const ResolveToDocumentIdCallback& callback) { |
| 168 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 169 std::vector<base::FilePath::StringType> components; |
| 170 path.GetComponents(&components); |
| 171 ResolveToDocumentIdRecursively(root_document_id_, components, callback); |
| 172 } |
| 173 |
| 174 void ArcDocumentsProviderRoot::ResolveToDocumentIdRecursively( |
| 175 const std::string& document_id, |
| 176 const std::vector<base::FilePath::StringType>& components, |
| 177 const ResolveToDocumentIdCallback& callback) { |
| 178 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 179 if (components.empty()) { |
| 180 callback.Run(document_id); |
| 181 return; |
| 182 } |
| 183 ReadDirectoryInternal( |
| 184 document_id, |
| 185 base::Bind(&ArcDocumentsProviderRoot:: |
| 186 ResolveToDocumentIdRecursivelyWithNameToThinDocumentMap, |
| 187 weak_ptr_factory_.GetWeakPtr(), components, callback)); |
| 188 } |
| 189 |
| 190 void ArcDocumentsProviderRoot:: |
| 191 ResolveToDocumentIdRecursivelyWithNameToThinDocumentMap( |
| 192 const std::vector<base::FilePath::StringType>& components, |
| 193 const ResolveToDocumentIdCallback& callback, |
| 194 base::File::Error error, |
| 195 NameToThinDocumentMap mapping) { |
| 196 DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| 197 DCHECK(!components.empty()); |
| 198 if (error != base::File::FILE_OK) { |
| 199 callback.Run(std::string()); |
| 200 return; |
| 201 } |
| 202 auto iter = mapping.find(components[0]); |
| 203 if (iter == mapping.end()) { |
| 204 callback.Run(std::string()); |
| 205 return; |
| 206 } |
| 207 ResolveToDocumentIdRecursively(iter->second.document_id, |
| 208 std::vector<base::FilePath::StringType>( |
| 209 components.begin() + 1, components.end()), |
| 210 callback); |
| 211 } |
| 212 |
| 213 void ArcDocumentsProviderRoot::ReadDirectoryInternal( |
| 214 const std::string& document_id, |
| 215 const ReadDirectoryInternalCallback& callback) { |
| 216 file_system_instance_util::GetChildDocumentsOnIOThread( |
| 217 authority_, document_id, |
| 218 base::Bind( |
| 219 &ArcDocumentsProviderRoot::ReadDirectoryInternalWithChildDocuments, |
| 220 weak_ptr_factory_.GetWeakPtr(), callback)); |
| 221 } |
| 222 |
| 223 void ArcDocumentsProviderRoot::ReadDirectoryInternalWithChildDocuments( |
| 224 const ReadDirectoryInternalCallback& callback, |
| 225 base::Optional<std::vector<mojom::DocumentPtr>> maybe_children) { |
| 226 if (!maybe_children) { |
| 227 callback.Run(base::File::FILE_ERROR_NOT_FOUND, NameToThinDocumentMap()); |
| 228 return; |
| 229 } |
| 230 std::vector<mojom::DocumentPtr>& children = maybe_children.value(); |
| 231 |
| 232 // Sort entries to keep the mapping stable as far as possible. |
| 233 std::sort(children.begin(), children.end(), |
| 234 [](const mojom::DocumentPtr& a, const mojom::DocumentPtr& b) { |
| 235 return a->document_id < b->document_id; |
| 236 }); |
| 237 |
| 238 NameToThinDocumentMap mapping; |
| 239 std::map<base::FilePath::StringType, int> suffix_counters; |
| 240 |
| 241 for (const mojom::DocumentPtr& document : children) { |
| 242 base::FilePath::StringType filename = GetFileNameForDocument(document); |
| 243 |
| 244 if (mapping.count(filename) > 0) { |
| 245 // Resolve a conflict by adding a suffix. |
| 246 int& suffix_counter = suffix_counters[filename]; |
| 247 while (true) { |
| 248 ++suffix_counter; |
| 249 std::string suffix = base::StringPrintf(" (%d)", suffix_counter); |
| 250 base::FilePath::StringType new_filename = |
| 251 base::FilePath(filename).InsertBeforeExtensionASCII(suffix).value(); |
| 252 if (mapping.count(new_filename) == 0) { |
| 253 filename = new_filename; |
| 254 break; |
| 255 } |
| 256 } |
| 257 } |
| 258 |
| 259 DCHECK_EQ(0u, mapping.count(filename)); |
| 260 |
| 261 mapping[filename] = |
| 262 ThinDocument{document->document_id, |
| 263 document->mime_type == kAndroidDirectoryMimeType}; |
| 264 } |
| 265 |
| 266 callback.Run(base::File::FILE_OK, std::move(mapping)); |
34 } | 267 } |
35 | 268 |
36 } // namespace arc | 269 } // namespace arc |
OLD | NEW |