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