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