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 "components/drive/file_system_core_util.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/render_frame_host.h"
17 #include "content/public/browser/site_instance.h"
18 #include "content/public/browser/storage_partition.h"
19 #include "content/public/common/file_chooser_file_info.h"
20 #include "extensions/common/extension.h"
21 #include "google_apis/drive/task_util.h"
22 #include "net/base/escape.h"
23 #include "storage/browser/fileapi/file_system_context.h"
24 #include "storage/browser/fileapi/isolated_context.h"
25 #include "storage/browser/fileapi/open_file_system_mode.h"
26 #include "storage/common/fileapi/file_system_util.h"
27 #include "ui/shell_dialogs/selected_file_info.h"
30 using content::BrowserThread
;
32 namespace file_manager
{
37 GURL
ConvertRelativeFilePathToFileSystemUrl(const base::FilePath
& relative_path
,
38 const std::string
& extension_id
) {
39 GURL base_url
= storage::GetFileSystemRootURI(
40 extensions::Extension::GetBaseURLFromExtensionId(extension_id
),
41 storage::kFileSystemTypeExternal
);
42 return GURL(base_url
.spec() +
43 net::EscapeUrlEncodedData(relative_path
.AsUTF8Unsafe(),
44 false)); // Space to %20 instead of +.
47 // Creates an ErrorDefinition with an error set to |error|.
48 EntryDefinition
CreateEntryDefinitionWithError(base::File::Error error
) {
49 EntryDefinition result
;
54 // Helper class for performing conversions from file definitions to entry
55 // definitions. It is possible to do it without a class, but the code would be
56 // crazy and super tricky.
58 // This class copies the input |file_definition_list|,
59 // so there is no need to worry about validity of passed |file_definition_list|
60 // reference. Also, it automatically deletes itself after converting finished,
61 // or if shutdown is invoked during ResolveURL(). Must be called on UI thread.
62 class FileDefinitionListConverter
{
64 FileDefinitionListConverter(Profile
* profile
,
65 const std::string
& extension_id
,
66 const FileDefinitionList
& file_definition_list
,
67 const EntryDefinitionListCallback
& callback
);
68 ~FileDefinitionListConverter() {}
71 // Converts the element under the iterator to an entry. First, converts
72 // the virtual path to an URL, and calls OnResolvedURL(). In case of error
73 // calls OnIteratorConverted with an error entry definition.
74 void ConvertNextIterator(scoped_ptr
<FileDefinitionListConverter
> self_deleter
,
75 FileDefinitionList::const_iterator iterator
);
77 // Creates an entry definition from the URL as well as the file definition.
78 // Then, calls OnIteratorConverted with the created entry definition.
79 void OnResolvedURL(scoped_ptr
<FileDefinitionListConverter
> self_deleter
,
80 FileDefinitionList::const_iterator iterator
,
81 base::File::Error error
,
82 const storage::FileSystemInfo
& info
,
83 const base::FilePath
& file_path
,
84 storage::FileSystemContext::ResolvedEntryType type
);
86 // Called when the iterator is converted. Adds the |entry_definition| to
87 // |results_| and calls ConvertNextIterator() for the next element.
88 void OnIteratorConverted(scoped_ptr
<FileDefinitionListConverter
> self_deleter
,
89 FileDefinitionList::const_iterator iterator
,
90 const EntryDefinition
& entry_definition
);
92 scoped_refptr
<storage::FileSystemContext
> file_system_context_
;
93 const std::string extension_id_
;
94 const FileDefinitionList file_definition_list_
;
95 const EntryDefinitionListCallback callback_
;
96 scoped_ptr
<EntryDefinitionList
> result_
;
99 FileDefinitionListConverter::FileDefinitionListConverter(
101 const std::string
& extension_id
,
102 const FileDefinitionList
& file_definition_list
,
103 const EntryDefinitionListCallback
& callback
)
104 : extension_id_(extension_id
),
105 file_definition_list_(file_definition_list
),
107 result_(new EntryDefinitionList
) {
108 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
110 // File browser APIs are meant to be used only from extension context, so
111 // the extension's site is the one in whose file system context the virtual
112 // path should be found.
113 GURL site
= extensions::util::GetSiteForExtensionId(extension_id_
, profile
);
114 file_system_context_
=
115 content::BrowserContext::GetStoragePartitionForSite(
116 profile
, site
)->GetFileSystemContext();
118 // Deletes the converter, once the scoped pointer gets out of scope. It is
119 // either, if the conversion is finished, or ResolveURL() is terminated, and
120 // the callback not called because of shutdown.
121 scoped_ptr
<FileDefinitionListConverter
> self_deleter(this);
122 ConvertNextIterator(self_deleter
.Pass(), file_definition_list_
.begin());
125 void FileDefinitionListConverter::ConvertNextIterator(
126 scoped_ptr
<FileDefinitionListConverter
> self_deleter
,
127 FileDefinitionList::const_iterator iterator
) {
128 if (iterator
== file_definition_list_
.end()) {
129 // The converter object will be destroyed since |self_deleter| gets out of
131 callback_
.Run(result_
.Pass());
135 if (!file_system_context_
.get()) {
136 OnIteratorConverted(self_deleter
.Pass(),
138 CreateEntryDefinitionWithError(
139 base::File::FILE_ERROR_INVALID_OPERATION
));
143 storage::FileSystemURL url
= file_system_context_
->CreateCrackedFileSystemURL(
144 extensions::Extension::GetBaseURLFromExtensionId(extension_id_
),
145 storage::kFileSystemTypeExternal
,
146 iterator
->virtual_path
);
147 DCHECK(url
.is_valid());
149 // The converter object will be deleted if the callback is not called because
150 // of shutdown during ResolveURL().
151 file_system_context_
->ResolveURL(
153 base::Bind(&FileDefinitionListConverter::OnResolvedURL
,
154 base::Unretained(this),
155 base::Passed(&self_deleter
),
159 void FileDefinitionListConverter::OnResolvedURL(
160 scoped_ptr
<FileDefinitionListConverter
> self_deleter
,
161 FileDefinitionList::const_iterator iterator
,
162 base::File::Error error
,
163 const storage::FileSystemInfo
& info
,
164 const base::FilePath
& file_path
,
165 storage::FileSystemContext::ResolvedEntryType type
) {
166 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
168 if (error
!= base::File::FILE_OK
) {
169 OnIteratorConverted(self_deleter
.Pass(),
171 CreateEntryDefinitionWithError(error
));
175 EntryDefinition entry_definition
;
176 entry_definition
.file_system_root_url
= info
.root_url
.spec();
177 entry_definition
.file_system_name
= info
.name
;
179 case storage::FileSystemContext::RESOLVED_ENTRY_FILE
:
180 entry_definition
.is_directory
= false;
182 case storage::FileSystemContext::RESOLVED_ENTRY_DIRECTORY
:
183 entry_definition
.is_directory
= true;
185 case storage::FileSystemContext::RESOLVED_ENTRY_NOT_FOUND
:
186 entry_definition
.is_directory
= iterator
->is_directory
;
189 entry_definition
.error
= base::File::FILE_OK
;
191 // Construct a target Entry.fullPath value from the virtual path and the
192 // root URL. Eg. Downloads/A/b.txt -> A/b.txt.
193 const base::FilePath root_virtual_path
=
194 file_system_context_
->CrackURL(info
.root_url
).virtual_path();
195 DCHECK(root_virtual_path
== iterator
->virtual_path
||
196 root_virtual_path
.IsParent(iterator
->virtual_path
));
197 base::FilePath full_path
;
198 root_virtual_path
.AppendRelativePath(iterator
->virtual_path
, &full_path
);
199 entry_definition
.full_path
= full_path
;
201 OnIteratorConverted(self_deleter
.Pass(), iterator
, entry_definition
);
204 void FileDefinitionListConverter::OnIteratorConverted(
205 scoped_ptr
<FileDefinitionListConverter
> self_deleter
,
206 FileDefinitionList::const_iterator iterator
,
207 const EntryDefinition
& entry_definition
) {
208 result_
->push_back(entry_definition
);
209 ConvertNextIterator(self_deleter
.Pass(), ++iterator
);
212 // Helper function to return the converted definition entry directly, without
213 // the redundant container.
214 void OnConvertFileDefinitionDone(
215 const EntryDefinitionCallback
& callback
,
216 scoped_ptr
<EntryDefinitionList
> entry_definition_list
) {
217 DCHECK_EQ(1u, entry_definition_list
->size());
218 callback
.Run(entry_definition_list
->at(0));
221 // Checks if the |file_path| points non-native location or not.
222 bool IsUnderNonNativeLocalPath(const storage::FileSystemContext
& context
,
223 const base::FilePath
& file_path
) {
224 base::FilePath virtual_path
;
225 if (!context
.external_backend()->GetVirtualPath(file_path
, &virtual_path
))
228 const storage::FileSystemURL url
= context
.CreateCrackedFileSystemURL(
229 GURL(), storage::kFileSystemTypeExternal
, virtual_path
);
233 return IsNonNativeFileSystemType(url
.type());
236 // Helper class to convert SelectedFileInfoList into ChooserFileInfoList.
237 class ConvertSelectedFileInfoListToFileChooserFileInfoListImpl
{
239 // The scoped pointer to control lifetime of the instance itself. The pointer
240 // is passed to callback functions and binds the lifetime of the instance to
241 // the callback's lifetime.
242 typedef scoped_ptr
<ConvertSelectedFileInfoListToFileChooserFileInfoListImpl
>
245 ConvertSelectedFileInfoListToFileChooserFileInfoListImpl(
246 storage::FileSystemContext
* context
,
248 const SelectedFileInfoList
& selected_info_list
,
249 const FileChooserFileInfoListCallback
& callback
)
251 chooser_info_list_(new FileChooserFileInfoList
),
252 callback_(callback
) {
253 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
255 Lifetime
lifetime(this);
256 bool need_fill_metadata
= false;
258 for (size_t i
= 0; i
< selected_info_list
.size(); ++i
) {
259 content::FileChooserFileInfo chooser_info
;
262 if (!IsUnderNonNativeLocalPath(*context
,
263 selected_info_list
[i
].file_path
)) {
264 chooser_info
.file_path
= selected_info_list
[i
].file_path
;
265 chooser_info
.display_name
= selected_info_list
[i
].display_name
;
266 chooser_info_list_
->push_back(chooser_info
);
270 // Non-native file, but it has a native snapshot file.
271 if (!selected_info_list
[i
].local_path
.empty()) {
272 chooser_info
.file_path
= selected_info_list
[i
].local_path
;
273 chooser_info
.display_name
= selected_info_list
[i
].display_name
;
274 chooser_info_list_
->push_back(chooser_info
);
278 // Non-native file without a snapshot file.
279 base::FilePath virtual_path
;
280 if (!context
->external_backend()->GetVirtualPath(
281 selected_info_list
[i
].file_path
, &virtual_path
)) {
282 NotifyError(lifetime
.Pass());
286 const GURL url
= CreateIsolatedURLFromVirtualPath(
287 *context_
, origin
, virtual_path
).ToGURL();
288 if (!url
.is_valid()) {
289 NotifyError(lifetime
.Pass());
293 chooser_info
.file_path
= selected_info_list
[i
].file_path
;
294 chooser_info
.file_system_url
= url
;
295 chooser_info_list_
->push_back(chooser_info
);
296 need_fill_metadata
= true;
299 // If the list includes at least one non-native file (wihtout a snapshot
300 // file), move to IO thread to obtian metadata for the non-native file.
301 if (need_fill_metadata
) {
302 BrowserThread::PostTask(
305 base::Bind(&ConvertSelectedFileInfoListToFileChooserFileInfoListImpl::
306 FillMetadataOnIOThread
,
307 base::Unretained(this),
308 base::Passed(&lifetime
),
309 chooser_info_list_
->begin()));
313 NotifyComplete(lifetime
.Pass());
316 ~ConvertSelectedFileInfoListToFileChooserFileInfoListImpl() {
317 if (chooser_info_list_
) {
318 for (size_t i
= 0; i
< chooser_info_list_
->size(); ++i
) {
319 if (chooser_info_list_
->at(i
).file_system_url
.is_valid()) {
320 storage::IsolatedContext::GetInstance()->RevokeFileSystem(
321 context_
->CrackURL(chooser_info_list_
->at(i
).file_system_url
)
322 .mount_filesystem_id());
329 // Obtains metadata for the non-native file |it|.
330 void FillMetadataOnIOThread(Lifetime lifetime
,
331 const FileChooserFileInfoList::iterator
& it
) {
332 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
334 if (it
== chooser_info_list_
->end()) {
335 BrowserThread::PostTask(
338 base::Bind(&ConvertSelectedFileInfoListToFileChooserFileInfoListImpl::
340 base::Unretained(this),
341 base::Passed(&lifetime
)));
345 if (!it
->file_system_url
.is_valid()) {
346 FillMetadataOnIOThread(lifetime
.Pass(), it
+ 1);
350 context_
->operation_runner()->GetMetadata(
351 context_
->CrackURL(it
->file_system_url
),
352 base::Bind(&ConvertSelectedFileInfoListToFileChooserFileInfoListImpl::
353 OnGotMetadataOnIOThread
,
354 base::Unretained(this),
355 base::Passed(&lifetime
),
359 // Callback invoked after GetMetadata.
360 void OnGotMetadataOnIOThread(Lifetime lifetime
,
361 const FileChooserFileInfoList::iterator
& it
,
362 base::File::Error result
,
363 const base::File::Info
& file_info
) {
364 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
366 if (result
!= base::File::FILE_OK
) {
367 BrowserThread::PostTask(
370 base::Bind(&ConvertSelectedFileInfoListToFileChooserFileInfoListImpl::
372 base::Unretained(this),
373 base::Passed(&lifetime
)));
377 it
->length
= file_info
.size
;
378 it
->modification_time
= file_info
.last_modified
;
379 it
->is_directory
= file_info
.is_directory
;
380 FillMetadataOnIOThread(lifetime
.Pass(), it
+ 1);
383 // Returns a result to the |callback_|.
384 void NotifyComplete(Lifetime
/* lifetime */) {
385 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
386 callback_
.Run(*chooser_info_list_
);
387 // Reset the list so that the file systems are not revoked at the
389 chooser_info_list_
.reset();
392 // Returns an empty list to the |callback_|.
393 void NotifyError(Lifetime
/* lifetime */) {
394 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
395 callback_
.Run(FileChooserFileInfoList());
398 scoped_refptr
<storage::FileSystemContext
> context_
;
399 scoped_ptr
<FileChooserFileInfoList
> chooser_info_list_
;
400 const FileChooserFileInfoListCallback callback_
;
402 DISALLOW_COPY_AND_ASSIGN(
403 ConvertSelectedFileInfoListToFileChooserFileInfoListImpl
);
408 EntryDefinition::EntryDefinition() {
411 EntryDefinition::~EntryDefinition() {
414 storage::FileSystemContext
* GetFileSystemContextForExtensionId(
416 const std::string
& extension_id
) {
417 GURL site
= extensions::util::GetSiteForExtensionId(extension_id
, profile
);
418 return content::BrowserContext::GetStoragePartitionForSite(profile
, site
)->
419 GetFileSystemContext();
422 storage::FileSystemContext
* GetFileSystemContextForRenderFrameHost(
424 content::RenderFrameHost
* render_frame_host
) {
425 content::SiteInstance
* site_instance
= render_frame_host
->GetSiteInstance();
426 return content::BrowserContext::GetStoragePartition(profile
, site_instance
)->
427 GetFileSystemContext();
430 base::FilePath
ConvertDrivePathToRelativeFileSystemPath(
432 const std::string
& extension_id
,
433 const base::FilePath
& drive_path
) {
434 // "/special/drive-xxx"
435 base::FilePath path
= drive::util::GetDriveMountPointPath(profile
);
436 // appended with (|drive_path| - "drive").
437 drive::util::GetDriveGrandRootPath().AppendRelativePath(drive_path
, &path
);
439 base::FilePath relative_path
;
440 ConvertAbsoluteFilePathToRelativeFileSystemPath(profile
,
444 return relative_path
;
447 GURL
ConvertDrivePathToFileSystemUrl(Profile
* profile
,
448 const base::FilePath
& drive_path
,
449 const std::string
& extension_id
) {
450 const base::FilePath relative_path
=
451 ConvertDrivePathToRelativeFileSystemPath(profile
, extension_id
,
453 if (relative_path
.empty())
455 return ConvertRelativeFilePathToFileSystemUrl(relative_path
, extension_id
);
458 bool ConvertAbsoluteFilePathToFileSystemUrl(Profile
* profile
,
459 const base::FilePath
& absolute_path
,
460 const std::string
& extension_id
,
462 base::FilePath relative_path
;
463 if (!ConvertAbsoluteFilePathToRelativeFileSystemPath(profile
,
469 *url
= ConvertRelativeFilePathToFileSystemUrl(relative_path
, extension_id
);
473 bool ConvertAbsoluteFilePathToRelativeFileSystemPath(
475 const std::string
& extension_id
,
476 const base::FilePath
& absolute_path
,
477 base::FilePath
* virtual_path
) {
478 // File browser APIs are meant to be used only from extension context, so the
479 // extension's site is the one in whose file system context the virtual path
481 GURL site
= extensions::util::GetSiteForExtensionId(extension_id
, profile
);
482 storage::ExternalFileSystemBackend
* backend
=
483 content::BrowserContext::GetStoragePartitionForSite(profile
, site
)
484 ->GetFileSystemContext()
485 ->external_backend();
489 // Find if this file path is managed by the external backend.
490 if (!backend
->GetVirtualPath(absolute_path
, virtual_path
))
496 void ConvertFileDefinitionListToEntryDefinitionList(
498 const std::string
& extension_id
,
499 const FileDefinitionList
& file_definition_list
,
500 const EntryDefinitionListCallback
& callback
) {
501 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
503 // The converter object destroys itself.
504 new FileDefinitionListConverter(
505 profile
, extension_id
, file_definition_list
, callback
);
508 void ConvertFileDefinitionToEntryDefinition(
510 const std::string
& extension_id
,
511 const FileDefinition
& file_definition
,
512 const EntryDefinitionCallback
& callback
) {
513 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
515 FileDefinitionList file_definition_list
;
516 file_definition_list
.push_back(file_definition
);
517 ConvertFileDefinitionListToEntryDefinitionList(
520 file_definition_list
,
521 base::Bind(&OnConvertFileDefinitionDone
, callback
));
524 void ConvertSelectedFileInfoListToFileChooserFileInfoList(
525 storage::FileSystemContext
* context
,
527 const SelectedFileInfoList
& selected_info_list
,
528 const FileChooserFileInfoListCallback
& callback
) {
529 // The object deletes itself.
530 new ConvertSelectedFileInfoListToFileChooserFileInfoListImpl(
531 context
, origin
, selected_info_list
, callback
);
534 void CheckIfDirectoryExists(
535 scoped_refptr
<storage::FileSystemContext
> file_system_context
,
536 const base::FilePath
& directory_path
,
537 const storage::FileSystemOperationRunner::StatusCallback
& callback
) {
538 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
540 storage::ExternalFileSystemBackend
* const backend
=
541 file_system_context
->external_backend();
543 const storage::FileSystemURL internal_url
=
544 backend
->CreateInternalURL(file_system_context
.get(), directory_path
);
546 BrowserThread::PostTask(
547 BrowserThread::IO
, FROM_HERE
,
548 base::Bind(base::IgnoreResult(
549 &storage::FileSystemOperationRunner::DirectoryExists
),
550 file_system_context
->operation_runner()->AsWeakPtr(),
551 internal_url
, google_apis::CreateRelayCallback(callback
)));
554 void GetMetadataForPath(
555 scoped_refptr
<storage::FileSystemContext
> file_system_context
,
556 const base::FilePath
& entry_path
,
557 const storage::FileSystemOperationRunner::GetMetadataCallback
& callback
) {
558 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
560 storage::ExternalFileSystemBackend
* const backend
=
561 file_system_context
->external_backend();
563 const storage::FileSystemURL internal_url
=
564 backend
->CreateInternalURL(file_system_context
.get(), entry_path
);
566 BrowserThread::PostTask(
567 BrowserThread::IO
, FROM_HERE
,
569 base::IgnoreResult(&storage::FileSystemOperationRunner::GetMetadata
),
570 file_system_context
->operation_runner()->AsWeakPtr(), internal_url
,
571 google_apis::CreateRelayCallback(callback
)));
574 storage::FileSystemURL
CreateIsolatedURLFromVirtualPath(
575 const storage::FileSystemContext
& context
,
577 const base::FilePath
& virtual_path
) {
578 const storage::FileSystemURL original_url
=
579 context
.CreateCrackedFileSystemURL(
580 origin
, storage::kFileSystemTypeExternal
, virtual_path
);
582 std::string register_name
;
583 const std::string isolated_file_system_id
=
584 storage::IsolatedContext::GetInstance()->RegisterFileSystemForPath(
586 original_url
.filesystem_id(),
589 const storage::FileSystemURL isolated_url
=
590 context
.CreateCrackedFileSystemURL(
592 storage::kFileSystemTypeIsolated
,
593 base::FilePath(isolated_file_system_id
).Append(register_name
));
598 } // namespace file_manager