1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
7 #include "base/files/file.h"
8 #include "base/files/file_path.h"
9 #include "chrome/browser/chromeos/drive/file_system_util.h"
10 #include "chrome/browser/chromeos/file_manager/app_id.h"
11 #include "chrome/browser/chromeos/file_manager/filesystem_api_util.h"
12 #include "chrome/browser/extensions/extension_util.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "content/public/browser/render_view_host.h"
16 #include "content/public/browser/site_instance.h"
17 #include "content/public/browser/storage_partition.h"
18 #include "content/public/common/file_chooser_file_info.h"
19 #include "extensions/common/extension.h"
20 #include "google_apis/drive/task_util.h"
21 #include "net/base/escape.h"
22 #include "storage/browser/fileapi/file_system_context.h"
23 #include "storage/browser/fileapi/isolated_context.h"
24 #include "storage/browser/fileapi/open_file_system_mode.h"
25 #include "storage/common/fileapi/file_system_util.h"
26 #include "ui/shell_dialogs/selected_file_info.h"
29 using content::BrowserThread
;
31 namespace file_manager
{
36 GURL
ConvertRelativeFilePathToFileSystemUrl(const base::FilePath
& relative_path
,
37 const std::string
& extension_id
) {
38 GURL base_url
= storage::GetFileSystemRootURI(
39 extensions::Extension::GetBaseURLFromExtensionId(extension_id
),
40 storage::kFileSystemTypeExternal
);
41 return GURL(base_url
.spec() +
42 net::EscapeUrlEncodedData(relative_path
.AsUTF8Unsafe(),
43 false)); // Space to %20 instead of +.
46 // Creates an ErrorDefinition with an error set to |error|.
47 EntryDefinition
CreateEntryDefinitionWithError(base::File::Error error
) {
48 EntryDefinition result
;
53 // Helper class for performing conversions from file definitions to entry
54 // definitions. It is possible to do it without a class, but the code would be
55 // crazy and super tricky.
57 // This class copies the input |file_definition_list|,
58 // so there is no need to worry about validity of passed |file_definition_list|
59 // reference. Also, it automatically deletes itself after converting finished,
60 // or if shutdown is invoked during ResolveURL(). Must be called on UI thread.
61 class FileDefinitionListConverter
{
63 FileDefinitionListConverter(Profile
* profile
,
64 const std::string
& extension_id
,
65 const FileDefinitionList
& file_definition_list
,
66 const EntryDefinitionListCallback
& callback
);
67 ~FileDefinitionListConverter() {}
70 // Converts the element under the iterator to an entry. First, converts
71 // the virtual path to an URL, and calls OnResolvedURL(). In case of error
72 // calls OnIteratorConverted with an error entry definition.
73 void ConvertNextIterator(scoped_ptr
<FileDefinitionListConverter
> self_deleter
,
74 FileDefinitionList::const_iterator iterator
);
76 // Creates an entry definition from the URL as well as the file definition.
77 // Then, calls OnIteratorConverted with the created entry definition.
78 void OnResolvedURL(scoped_ptr
<FileDefinitionListConverter
> self_deleter
,
79 FileDefinitionList::const_iterator iterator
,
80 base::File::Error error
,
81 const storage::FileSystemInfo
& info
,
82 const base::FilePath
& file_path
,
83 storage::FileSystemContext::ResolvedEntryType type
);
85 // Called when the iterator is converted. Adds the |entry_definition| to
86 // |results_| and calls ConvertNextIterator() for the next element.
87 void OnIteratorConverted(scoped_ptr
<FileDefinitionListConverter
> self_deleter
,
88 FileDefinitionList::const_iterator iterator
,
89 const EntryDefinition
& entry_definition
);
91 scoped_refptr
<storage::FileSystemContext
> file_system_context_
;
92 const std::string extension_id_
;
93 const FileDefinitionList file_definition_list_
;
94 const EntryDefinitionListCallback callback_
;
95 scoped_ptr
<EntryDefinitionList
> result_
;
98 FileDefinitionListConverter::FileDefinitionListConverter(
100 const std::string
& extension_id
,
101 const FileDefinitionList
& file_definition_list
,
102 const EntryDefinitionListCallback
& callback
)
103 : extension_id_(extension_id
),
104 file_definition_list_(file_definition_list
),
106 result_(new EntryDefinitionList
) {
107 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
109 // File browser APIs are meant to be used only from extension context, so
110 // the extension's site is the one in whose file system context the virtual
111 // path should be found.
112 GURL site
= extensions::util::GetSiteForExtensionId(extension_id_
, profile
);
113 file_system_context_
=
114 content::BrowserContext::GetStoragePartitionForSite(
115 profile
, site
)->GetFileSystemContext();
117 // Deletes the converter, once the scoped pointer gets out of scope. It is
118 // either, if the conversion is finished, or ResolveURL() is terminated, and
119 // the callback not called because of shutdown.
120 scoped_ptr
<FileDefinitionListConverter
> self_deleter(this);
121 ConvertNextIterator(self_deleter
.Pass(), file_definition_list_
.begin());
124 void FileDefinitionListConverter::ConvertNextIterator(
125 scoped_ptr
<FileDefinitionListConverter
> self_deleter
,
126 FileDefinitionList::const_iterator iterator
) {
127 if (iterator
== file_definition_list_
.end()) {
128 // The converter object will be destroyed since |self_deleter| gets out of
130 callback_
.Run(result_
.Pass());
134 if (!file_system_context_
.get()) {
135 OnIteratorConverted(self_deleter
.Pass(),
137 CreateEntryDefinitionWithError(
138 base::File::FILE_ERROR_INVALID_OPERATION
));
142 storage::FileSystemURL url
= file_system_context_
->CreateCrackedFileSystemURL(
143 extensions::Extension::GetBaseURLFromExtensionId(extension_id_
),
144 storage::kFileSystemTypeExternal
,
145 iterator
->virtual_path
);
146 DCHECK(url
.is_valid());
148 // The converter object will be deleted if the callback is not called because
149 // of shutdown during ResolveURL().
150 file_system_context_
->ResolveURL(
152 base::Bind(&FileDefinitionListConverter::OnResolvedURL
,
153 base::Unretained(this),
154 base::Passed(&self_deleter
),
158 void FileDefinitionListConverter::OnResolvedURL(
159 scoped_ptr
<FileDefinitionListConverter
> self_deleter
,
160 FileDefinitionList::const_iterator iterator
,
161 base::File::Error error
,
162 const storage::FileSystemInfo
& info
,
163 const base::FilePath
& file_path
,
164 storage::FileSystemContext::ResolvedEntryType type
) {
165 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
167 if (error
!= base::File::FILE_OK
) {
168 OnIteratorConverted(self_deleter
.Pass(),
170 CreateEntryDefinitionWithError(error
));
174 EntryDefinition entry_definition
;
175 entry_definition
.file_system_root_url
= info
.root_url
.spec();
176 entry_definition
.file_system_name
= info
.name
;
178 case storage::FileSystemContext::RESOLVED_ENTRY_FILE
:
179 entry_definition
.is_directory
= false;
181 case storage::FileSystemContext::RESOLVED_ENTRY_DIRECTORY
:
182 entry_definition
.is_directory
= true;
184 case storage::FileSystemContext::RESOLVED_ENTRY_NOT_FOUND
:
185 entry_definition
.is_directory
= iterator
->is_directory
;
188 entry_definition
.error
= base::File::FILE_OK
;
190 // Construct a target Entry.fullPath value from the virtual path and the
191 // root URL. Eg. Downloads/A/b.txt -> A/b.txt.
192 const base::FilePath root_virtual_path
=
193 file_system_context_
->CrackURL(info
.root_url
).virtual_path();
194 DCHECK(root_virtual_path
== iterator
->virtual_path
||
195 root_virtual_path
.IsParent(iterator
->virtual_path
));
196 base::FilePath full_path
;
197 root_virtual_path
.AppendRelativePath(iterator
->virtual_path
, &full_path
);
198 entry_definition
.full_path
= full_path
;
200 OnIteratorConverted(self_deleter
.Pass(), iterator
, entry_definition
);
203 void FileDefinitionListConverter::OnIteratorConverted(
204 scoped_ptr
<FileDefinitionListConverter
> self_deleter
,
205 FileDefinitionList::const_iterator iterator
,
206 const EntryDefinition
& entry_definition
) {
207 result_
->push_back(entry_definition
);
208 ConvertNextIterator(self_deleter
.Pass(), ++iterator
);
211 // Helper function to return the converted definition entry directly, without
212 // the redundant container.
213 void OnConvertFileDefinitionDone(
214 const EntryDefinitionCallback
& callback
,
215 scoped_ptr
<EntryDefinitionList
> entry_definition_list
) {
216 DCHECK_EQ(1u, entry_definition_list
->size());
217 callback
.Run(entry_definition_list
->at(0));
220 // Checks if the |file_path| points non-native location or not.
221 bool IsUnderNonNativeLocalPath(const storage::FileSystemContext
& context
,
222 const base::FilePath
& file_path
) {
223 base::FilePath virtual_path
;
224 if (!context
.external_backend()->GetVirtualPath(file_path
, &virtual_path
))
227 const storage::FileSystemURL url
= context
.CreateCrackedFileSystemURL(
228 GURL(), storage::kFileSystemTypeExternal
, virtual_path
);
232 return IsNonNativeFileSystemType(url
.type());
235 // Helper class to convert SelectedFileInfoList into ChooserFileInfoList.
236 class ConvertSelectedFileInfoListToFileChooserFileInfoListImpl
{
238 // The scoped pointer to control lifetime of the instance itself. The pointer
239 // is passed to callback functions and binds the lifetime of the instance to
240 // the callback's lifetime.
241 typedef scoped_ptr
<ConvertSelectedFileInfoListToFileChooserFileInfoListImpl
>
244 ConvertSelectedFileInfoListToFileChooserFileInfoListImpl(
245 storage::FileSystemContext
* context
,
247 const SelectedFileInfoList
& selected_info_list
,
248 const FileChooserFileInfoListCallback
& callback
)
250 chooser_info_list_(new FileChooserFileInfoList
),
251 callback_(callback
) {
252 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
254 Lifetime
lifetime(this);
255 bool need_fill_metadata
= false;
257 for (size_t i
= 0; i
< selected_info_list
.size(); ++i
) {
258 content::FileChooserFileInfo chooser_info
;
261 if (!IsUnderNonNativeLocalPath(*context
,
262 selected_info_list
[i
].file_path
)) {
263 chooser_info
.file_path
= selected_info_list
[i
].file_path
;
264 chooser_info
.display_name
= selected_info_list
[i
].display_name
;
265 chooser_info_list_
->push_back(chooser_info
);
269 // Non-native file, but it has a native snapshot file.
270 if (!selected_info_list
[i
].local_path
.empty()) {
271 chooser_info
.file_path
= selected_info_list
[i
].local_path
;
272 chooser_info
.display_name
= selected_info_list
[i
].display_name
;
273 chooser_info_list_
->push_back(chooser_info
);
277 // Non-native file without a snapshot file.
278 base::FilePath virtual_path
;
279 if (!context
->external_backend()->GetVirtualPath(
280 selected_info_list
[i
].file_path
, &virtual_path
)) {
281 NotifyError(lifetime
.Pass());
285 const GURL url
= CreateIsolatedURLFromVirtualPath(
286 *context_
, origin
, virtual_path
).ToGURL();
287 if (!url
.is_valid()) {
288 NotifyError(lifetime
.Pass());
292 chooser_info
.file_path
= selected_info_list
[i
].file_path
;
293 chooser_info
.file_system_url
= url
;
294 chooser_info_list_
->push_back(chooser_info
);
295 need_fill_metadata
= true;
298 // If the list includes at least one non-native file (wihtout a snapshot
299 // file), move to IO thread to obtian metadata for the non-native file.
300 if (need_fill_metadata
) {
301 BrowserThread::PostTask(
304 base::Bind(&ConvertSelectedFileInfoListToFileChooserFileInfoListImpl::
305 FillMetadataOnIOThread
,
306 base::Unretained(this),
307 base::Passed(&lifetime
),
308 chooser_info_list_
->begin()));
312 NotifyComplete(lifetime
.Pass());
315 ~ConvertSelectedFileInfoListToFileChooserFileInfoListImpl() {
316 if (chooser_info_list_
) {
317 for (size_t i
= 0; i
< chooser_info_list_
->size(); ++i
) {
318 if (chooser_info_list_
->at(i
).file_system_url
.is_valid()) {
319 storage::IsolatedContext::GetInstance()->RevokeFileSystem(
320 context_
->CrackURL(chooser_info_list_
->at(i
).file_system_url
)
321 .mount_filesystem_id());
328 // Obtains metadata for the non-native file |it|.
329 void FillMetadataOnIOThread(Lifetime lifetime
,
330 const FileChooserFileInfoList::iterator
& it
) {
331 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
333 if (it
== chooser_info_list_
->end()) {
334 BrowserThread::PostTask(
337 base::Bind(&ConvertSelectedFileInfoListToFileChooserFileInfoListImpl::
339 base::Unretained(this),
340 base::Passed(&lifetime
)));
344 if (!it
->file_system_url
.is_valid()) {
345 FillMetadataOnIOThread(lifetime
.Pass(), it
+ 1);
349 context_
->operation_runner()->GetMetadata(
350 context_
->CrackURL(it
->file_system_url
),
351 base::Bind(&ConvertSelectedFileInfoListToFileChooserFileInfoListImpl::
352 OnGotMetadataOnIOThread
,
353 base::Unretained(this),
354 base::Passed(&lifetime
),
358 // Callback invoked after GetMetadata.
359 void OnGotMetadataOnIOThread(Lifetime lifetime
,
360 const FileChooserFileInfoList::iterator
& it
,
361 base::File::Error result
,
362 const base::File::Info
& file_info
) {
363 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
365 if (result
!= base::File::FILE_OK
) {
366 BrowserThread::PostTask(
369 base::Bind(&ConvertSelectedFileInfoListToFileChooserFileInfoListImpl::
371 base::Unretained(this),
372 base::Passed(&lifetime
)));
376 it
->length
= file_info
.size
;
377 it
->modification_time
= file_info
.last_modified
;
378 it
->is_directory
= file_info
.is_directory
;
379 FillMetadataOnIOThread(lifetime
.Pass(), it
+ 1);
382 // Returns a result to the |callback_|.
383 void NotifyComplete(Lifetime
/* lifetime */) {
384 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
385 callback_
.Run(*chooser_info_list_
);
386 // Reset the list so that the file systems are not revoked at the
388 chooser_info_list_
.reset();
391 // Returns an empty list to the |callback_|.
392 void NotifyError(Lifetime
/* lifetime */) {
393 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
394 callback_
.Run(FileChooserFileInfoList());
397 scoped_refptr
<storage::FileSystemContext
> context_
;
398 scoped_ptr
<FileChooserFileInfoList
> chooser_info_list_
;
399 const FileChooserFileInfoListCallback callback_
;
401 DISALLOW_COPY_AND_ASSIGN(
402 ConvertSelectedFileInfoListToFileChooserFileInfoListImpl
);
407 EntryDefinition::EntryDefinition() {
410 EntryDefinition::~EntryDefinition() {
413 storage::FileSystemContext
* GetFileSystemContextForExtensionId(
415 const std::string
& extension_id
) {
416 GURL site
= extensions::util::GetSiteForExtensionId(extension_id
, profile
);
417 return content::BrowserContext::GetStoragePartitionForSite(profile
, site
)->
418 GetFileSystemContext();
421 storage::FileSystemContext
* GetFileSystemContextForRenderViewHost(
423 content::RenderViewHost
* render_view_host
) {
424 content::SiteInstance
* site_instance
= render_view_host
->GetSiteInstance();
425 return content::BrowserContext::GetStoragePartition(profile
, site_instance
)->
426 GetFileSystemContext();
429 base::FilePath
ConvertDrivePathToRelativeFileSystemPath(
431 const std::string
& extension_id
,
432 const base::FilePath
& drive_path
) {
433 // "/special/drive-xxx"
434 base::FilePath path
= drive::util::GetDriveMountPointPath(profile
);
435 // appended with (|drive_path| - "drive").
436 drive::util::GetDriveGrandRootPath().AppendRelativePath(drive_path
, &path
);
438 base::FilePath relative_path
;
439 ConvertAbsoluteFilePathToRelativeFileSystemPath(profile
,
443 return relative_path
;
446 GURL
ConvertDrivePathToFileSystemUrl(Profile
* profile
,
447 const base::FilePath
& drive_path
,
448 const std::string
& extension_id
) {
449 const base::FilePath relative_path
=
450 ConvertDrivePathToRelativeFileSystemPath(profile
, extension_id
,
452 if (relative_path
.empty())
454 return ConvertRelativeFilePathToFileSystemUrl(relative_path
, extension_id
);
457 bool ConvertAbsoluteFilePathToFileSystemUrl(Profile
* profile
,
458 const base::FilePath
& absolute_path
,
459 const std::string
& extension_id
,
461 base::FilePath relative_path
;
462 if (!ConvertAbsoluteFilePathToRelativeFileSystemPath(profile
,
468 *url
= ConvertRelativeFilePathToFileSystemUrl(relative_path
, extension_id
);
472 bool ConvertAbsoluteFilePathToRelativeFileSystemPath(
474 const std::string
& extension_id
,
475 const base::FilePath
& absolute_path
,
476 base::FilePath
* virtual_path
) {
477 // File browser APIs are meant to be used only from extension context, so the
478 // extension's site is the one in whose file system context the virtual path
480 GURL site
= extensions::util::GetSiteForExtensionId(extension_id
, profile
);
481 storage::ExternalFileSystemBackend
* backend
=
482 content::BrowserContext::GetStoragePartitionForSite(profile
, site
)
483 ->GetFileSystemContext()
484 ->external_backend();
488 // Find if this file path is managed by the external backend.
489 if (!backend
->GetVirtualPath(absolute_path
, virtual_path
))
495 void ConvertFileDefinitionListToEntryDefinitionList(
497 const std::string
& extension_id
,
498 const FileDefinitionList
& file_definition_list
,
499 const EntryDefinitionListCallback
& callback
) {
500 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
502 // The converter object destroys itself.
503 new FileDefinitionListConverter(
504 profile
, extension_id
, file_definition_list
, callback
);
507 void ConvertFileDefinitionToEntryDefinition(
509 const std::string
& extension_id
,
510 const FileDefinition
& file_definition
,
511 const EntryDefinitionCallback
& callback
) {
512 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
514 FileDefinitionList file_definition_list
;
515 file_definition_list
.push_back(file_definition
);
516 ConvertFileDefinitionListToEntryDefinitionList(
519 file_definition_list
,
520 base::Bind(&OnConvertFileDefinitionDone
, callback
));
523 void ConvertSelectedFileInfoListToFileChooserFileInfoList(
524 storage::FileSystemContext
* context
,
526 const SelectedFileInfoList
& selected_info_list
,
527 const FileChooserFileInfoListCallback
& callback
) {
528 // The object deletes itself.
529 new ConvertSelectedFileInfoListToFileChooserFileInfoListImpl(
530 context
, origin
, selected_info_list
, callback
);
533 void CheckIfDirectoryExists(
534 scoped_refptr
<storage::FileSystemContext
> file_system_context
,
535 const base::FilePath
& directory_path
,
536 const storage::FileSystemOperationRunner::StatusCallback
& callback
) {
537 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
539 storage::ExternalFileSystemBackend
* const backend
=
540 file_system_context
->external_backend();
542 const storage::FileSystemURL internal_url
=
543 backend
->CreateInternalURL(file_system_context
.get(), directory_path
);
545 BrowserThread::PostTask(
546 BrowserThread::IO
, FROM_HERE
,
547 base::Bind(base::IgnoreResult(
548 &storage::FileSystemOperationRunner::DirectoryExists
),
549 file_system_context
->operation_runner()->AsWeakPtr(),
550 internal_url
, google_apis::CreateRelayCallback(callback
)));
553 void GetMetadataForPath(
554 scoped_refptr
<storage::FileSystemContext
> file_system_context
,
555 const base::FilePath
& entry_path
,
556 const storage::FileSystemOperationRunner::GetMetadataCallback
& callback
) {
557 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
559 storage::ExternalFileSystemBackend
* const backend
=
560 file_system_context
->external_backend();
562 const storage::FileSystemURL internal_url
=
563 backend
->CreateInternalURL(file_system_context
.get(), entry_path
);
565 BrowserThread::PostTask(
566 BrowserThread::IO
, FROM_HERE
,
568 base::IgnoreResult(&storage::FileSystemOperationRunner::GetMetadata
),
569 file_system_context
->operation_runner()->AsWeakPtr(), internal_url
,
570 google_apis::CreateRelayCallback(callback
)));
573 storage::FileSystemURL
CreateIsolatedURLFromVirtualPath(
574 const storage::FileSystemContext
& context
,
576 const base::FilePath
& virtual_path
) {
577 const storage::FileSystemURL original_url
=
578 context
.CreateCrackedFileSystemURL(
579 origin
, storage::kFileSystemTypeExternal
, virtual_path
);
581 std::string register_name
;
582 const std::string isolated_file_system_id
=
583 storage::IsolatedContext::GetInstance()->RegisterFileSystemForPath(
585 original_url
.filesystem_id(),
588 const storage::FileSystemURL isolated_url
=
589 context
.CreateCrackedFileSystemURL(
591 storage::kFileSystemTypeIsolated
,
592 base::FilePath(isolated_file_system_id
).Append(register_name
));
597 } // namespace file_manager