async_file_util.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
1// Copyright 2014 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.
4
5#include "chrome/browser/chromeos/drive/fileapi/async_file_util.h"
6
7#include "base/callback.h"
8#include "base/files/file_path.h"
9#include "base/logging.h"
10#include "base/platform_file.h"
11#include "base/threading/sequenced_worker_pool.h"
12#include "chrome/browser/chromeos/drive/drive_integration_service.h"
13#include "chrome/browser/chromeos/drive/file_system_util.h"
14#include "chrome/browser/chromeos/drive/fileapi/fileapi_worker.h"
15#include "content/public/browser/browser_thread.h"
16#include "google_apis/drive/task_util.h"
17#include "webkit/browser/fileapi/file_system_operation_context.h"
18#include "webkit/browser/fileapi/file_system_url.h"
19#include "webkit/common/blob/shareable_file_reference.h"
20
21using content::BrowserThread;
22
23namespace drive {
24namespace internal {
25namespace {
26
27// Posts fileapi_internal::RunFileSystemCallback to UI thread.
28// This function must be called on IO thread.
29// The |on_error_callback| will be called (on error case) on IO thread.
30void PostFileSystemCallback(
31    const fileapi_internal::FileSystemGetter& file_system_getter,
32    const base::Callback<void(FileSystemInterface*)>& function,
33    const base::Closure& on_error_callback) {
34  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
35
36  BrowserThread::PostTask(
37      BrowserThread::UI,
38      FROM_HERE,
39      base::Bind(&fileapi_internal::RunFileSystemCallback,
40                 file_system_getter, function,
41                 on_error_callback.is_null() ?
42                 base::Closure() :
43                 base::Bind(&google_apis::RunTaskOnThread,
44                            base::MessageLoopProxy::current(),
45                            on_error_callback)));
46}
47
48// Runs CreateOrOpenFile callback based on the given |error| and |file|.
49void RunCreateOrOpenFileCallback(
50    const fileapi_internal::FileSystemGetter& file_system_getter,
51    const base::FilePath& file_path,
52    const AsyncFileUtil::CreateOrOpenCallback& callback,
53    base::File::Error error,
54    base::PlatformFile file,
55    const base::Closure& close_callback_on_ui_thread) {
56  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
57
58  // It is necessary to make a closure, which runs on file closing here.
59  // It will be provided as a FileSystem::OpenFileCallback's argument later.
60  // (crbug.com/259184).
61  callback.Run(
62      error, base::PassPlatformFile(&file),
63      base::Bind(&google_apis::RunTaskOnThread,
64                 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI),
65                 close_callback_on_ui_thread));
66}
67
68// Runs CreateOrOpenFile when the error happens.
69void RunCreateOrOpenFileCallbackOnError(
70    const AsyncFileUtil::CreateOrOpenCallback& callback,
71    base::File::Error error) {
72  // Because the |callback| takes PassPlatformFile as its argument, and
73  // it is necessary to guarantee the pointer passed to PassPlatformFile is
74  // alive during the |callback| invocation, here we prepare a thin adapter
75  // to have PlatformFile on stack frame.
76  base::PlatformFile file = base::kInvalidPlatformFileValue;
77  callback.Run(error, base::PassPlatformFile(&file), base::Closure());
78}
79
80// Runs EnsureFileExistsCallback based on the given |error|.
81void RunEnsureFileExistsCallback(
82    const AsyncFileUtil::EnsureFileExistsCallback& callback,
83    base::File::Error error) {
84  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
85
86  // Remember if the file is actually created or not.
87  bool created = (error == base::File::FILE_OK);
88
89  // File::FILE_ERROR_EXISTS is not an actual error here.
90  if (error == base::File::FILE_ERROR_EXISTS)
91    error = base::File::FILE_OK;
92
93  callback.Run(error, created);
94}
95
96// Runs |callback| with the arguments based on the given arguments.
97void RunCreateSnapshotFileCallback(
98    const AsyncFileUtil::CreateSnapshotFileCallback& callback,
99    base::File::Error error,
100    const base::File::Info& file_info,
101    const base::FilePath& local_path,
102    webkit_blob::ScopedFile::ScopeOutPolicy scope_out_policy) {
103  // ShareableFileReference is thread *unsafe* class. So it is necessary to
104  // create the instance (by invoking GetOrCreate) on IO thread, though
105  // most drive file system related operations run on UI thread.
106  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
107
108  scoped_refptr<webkit_blob::ShareableFileReference> file_reference =
109      webkit_blob::ShareableFileReference::GetOrCreate(webkit_blob::ScopedFile(
110          local_path,
111          scope_out_policy,
112          BrowserThread::GetBlockingPool()));
113  callback.Run(error, file_info, local_path, file_reference);
114}
115
116}  // namespace
117
118AsyncFileUtil::AsyncFileUtil() {
119}
120
121AsyncFileUtil::~AsyncFileUtil() {
122}
123
124void AsyncFileUtil::CreateOrOpen(
125    scoped_ptr<fileapi::FileSystemOperationContext> context,
126    const fileapi::FileSystemURL& url,
127    int file_flags,
128    const CreateOrOpenCallback& callback) {
129  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
130
131  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
132  if (file_path.empty()) {
133    base::PlatformFile platform_file = base::kInvalidPlatformFileValue;
134    callback.Run(base::File::FILE_ERROR_NOT_FOUND,
135                 base::PassPlatformFile(&platform_file),
136                 base::Closure());
137    return;
138  }
139
140  const fileapi_internal::FileSystemGetter getter =
141      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url);
142  PostFileSystemCallback(
143      getter,
144      base::Bind(&fileapi_internal::OpenFile,
145                 file_path, file_flags,
146                 google_apis::CreateRelayCallback(
147                     base::Bind(&RunCreateOrOpenFileCallback,
148                                getter, file_path, callback))),
149      base::Bind(&RunCreateOrOpenFileCallbackOnError,
150                 callback, base::File::FILE_ERROR_FAILED));
151}
152
153void AsyncFileUtil::EnsureFileExists(
154    scoped_ptr<fileapi::FileSystemOperationContext> context,
155    const fileapi::FileSystemURL& url,
156    const EnsureFileExistsCallback& callback) {
157  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
158
159  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
160  if (file_path.empty()) {
161    callback.Run(base::File::FILE_ERROR_NOT_FOUND, false);
162    return;
163  }
164
165  PostFileSystemCallback(
166      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
167      base::Bind(&fileapi_internal::CreateFile,
168                 file_path, true /* is_exlusive */,
169                 google_apis::CreateRelayCallback(
170                     base::Bind(&RunEnsureFileExistsCallback, callback))),
171      base::Bind(callback, base::File::FILE_ERROR_FAILED, false));
172}
173
174void AsyncFileUtil::CreateDirectory(
175    scoped_ptr<fileapi::FileSystemOperationContext> context,
176    const fileapi::FileSystemURL& url,
177    bool exclusive,
178    bool recursive,
179    const StatusCallback& callback) {
180  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
181
182  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
183  if (file_path.empty()) {
184    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
185    return;
186  }
187
188  PostFileSystemCallback(
189      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
190      base::Bind(&fileapi_internal::CreateDirectory,
191                 file_path, exclusive, recursive,
192                 google_apis::CreateRelayCallback(callback)),
193      base::Bind(callback, base::File::FILE_ERROR_FAILED));
194}
195
196void AsyncFileUtil::GetFileInfo(
197    scoped_ptr<fileapi::FileSystemOperationContext> context,
198    const fileapi::FileSystemURL& url,
199    const GetFileInfoCallback& callback) {
200  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
201
202  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
203  if (file_path.empty()) {
204    callback.Run(base::File::FILE_ERROR_NOT_FOUND, base::File::Info());
205    return;
206  }
207
208  PostFileSystemCallback(
209      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
210      base::Bind(&fileapi_internal::GetFileInfo,
211                 file_path, google_apis::CreateRelayCallback(callback)),
212      base::Bind(callback, base::File::FILE_ERROR_FAILED,
213                 base::File::Info()));
214}
215
216void AsyncFileUtil::ReadDirectory(
217    scoped_ptr<fileapi::FileSystemOperationContext> context,
218    const fileapi::FileSystemURL& url,
219    const ReadDirectoryCallback& callback) {
220  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
221
222  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
223  if (file_path.empty()) {
224    callback.Run(base::File::FILE_ERROR_NOT_FOUND, EntryList(), false);
225    return;
226  }
227
228  PostFileSystemCallback(
229      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
230      base::Bind(&fileapi_internal::ReadDirectory,
231                 file_path, google_apis::CreateRelayCallback(callback)),
232      base::Bind(callback, base::File::FILE_ERROR_FAILED,
233                 EntryList(), false));
234}
235
236void AsyncFileUtil::Touch(
237    scoped_ptr<fileapi::FileSystemOperationContext> context,
238    const fileapi::FileSystemURL& url,
239    const base::Time& last_access_time,
240    const base::Time& last_modified_time,
241    const StatusCallback& callback) {
242  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
243
244  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
245  if (file_path.empty()) {
246    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
247    return;
248  }
249
250  PostFileSystemCallback(
251      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
252      base::Bind(&fileapi_internal::TouchFile,
253                 file_path, last_access_time, last_modified_time,
254                 google_apis::CreateRelayCallback(callback)),
255      base::Bind(callback, base::File::FILE_ERROR_FAILED));
256}
257
258void AsyncFileUtil::Truncate(
259    scoped_ptr<fileapi::FileSystemOperationContext> context,
260    const fileapi::FileSystemURL& url,
261    int64 length,
262    const StatusCallback& callback) {
263  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
264
265  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
266  if (file_path.empty()) {
267    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
268    return;
269  }
270
271  PostFileSystemCallback(
272      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
273      base::Bind(&fileapi_internal::Truncate,
274                 file_path, length, google_apis::CreateRelayCallback(callback)),
275      base::Bind(callback, base::File::FILE_ERROR_FAILED));
276}
277
278void AsyncFileUtil::CopyFileLocal(
279    scoped_ptr<fileapi::FileSystemOperationContext> context,
280    const fileapi::FileSystemURL& src_url,
281    const fileapi::FileSystemURL& dest_url,
282    CopyOrMoveOption option,
283    const CopyFileProgressCallback& progress_callback,
284    const StatusCallback& callback) {
285  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
286
287  base::FilePath src_path = util::ExtractDrivePathFromFileSystemUrl(src_url);
288  base::FilePath dest_path = util::ExtractDrivePathFromFileSystemUrl(dest_url);
289  if (src_path.empty() || dest_path.empty()) {
290    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
291    return;
292  }
293
294  // TODO(kinaba): crbug.com/339794.
295  // Assumption here is that |src_url| and |dest_url| are always from the same
296  // profile. This indeed holds as long as we mount different profiles onto
297  // different mount point. Hence, using GetFileSystemFromUrl(dest_url) is safe.
298  // This will change after we introduce cross-profile sharing etc., and we
299  // need to deal with files from different profiles here.
300  PostFileSystemCallback(
301      base::Bind(&fileapi_internal::GetFileSystemFromUrl, dest_url),
302      base::Bind(
303          &fileapi_internal::Copy,
304          src_path, dest_path,
305          option == fileapi::FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED,
306          google_apis::CreateRelayCallback(callback)),
307      base::Bind(callback, base::File::FILE_ERROR_FAILED));
308}
309
310void AsyncFileUtil::MoveFileLocal(
311    scoped_ptr<fileapi::FileSystemOperationContext> context,
312    const fileapi::FileSystemURL& src_url,
313    const fileapi::FileSystemURL& dest_url,
314    CopyOrMoveOption option,
315    const StatusCallback& callback) {
316  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
317
318  base::FilePath src_path = util::ExtractDrivePathFromFileSystemUrl(src_url);
319  base::FilePath dest_path = util::ExtractDrivePathFromFileSystemUrl(dest_url);
320  if (src_path.empty() || dest_path.empty()) {
321    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
322    return;
323  }
324
325  // TODO(kinaba): see the comment in CopyFileLocal(). |src_url| and |dest_url|
326  // always return the same FileSystem by GetFileSystemFromUrl, but we need to
327  // change it in order to support cross-profile file sharing etc.
328  PostFileSystemCallback(
329      base::Bind(&fileapi_internal::GetFileSystemFromUrl, dest_url),
330      base::Bind(&fileapi_internal::Move,
331                 src_path, dest_path,
332                 google_apis::CreateRelayCallback(callback)),
333      base::Bind(callback, base::File::FILE_ERROR_FAILED));
334}
335
336void AsyncFileUtil::CopyInForeignFile(
337    scoped_ptr<fileapi::FileSystemOperationContext> context,
338    const base::FilePath& src_file_path,
339    const fileapi::FileSystemURL& dest_url,
340    const StatusCallback& callback) {
341  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
342
343  base::FilePath dest_path = util::ExtractDrivePathFromFileSystemUrl(dest_url);
344  if (dest_path.empty()) {
345    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
346    return;
347  }
348
349  PostFileSystemCallback(
350      base::Bind(&fileapi_internal::GetFileSystemFromUrl, dest_url),
351      base::Bind(&fileapi_internal::CopyInForeignFile,
352                 src_file_path, dest_path,
353                 google_apis::CreateRelayCallback(callback)),
354      base::Bind(callback, base::File::FILE_ERROR_FAILED));
355}
356
357void AsyncFileUtil::DeleteFile(
358    scoped_ptr<fileapi::FileSystemOperationContext> context,
359    const fileapi::FileSystemURL& url,
360    const StatusCallback& callback) {
361  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
362
363  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
364  if (file_path.empty()) {
365    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
366    return;
367  }
368
369  PostFileSystemCallback(
370      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
371      base::Bind(&fileapi_internal::Remove,
372                 file_path, false /* not recursive */,
373                 google_apis::CreateRelayCallback(callback)),
374      base::Bind(callback, base::File::FILE_ERROR_FAILED));
375}
376
377void AsyncFileUtil::DeleteDirectory(
378    scoped_ptr<fileapi::FileSystemOperationContext> context,
379    const fileapi::FileSystemURL& url,
380    const StatusCallback& callback) {
381  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
382
383  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
384  if (file_path.empty()) {
385    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
386    return;
387  }
388
389  PostFileSystemCallback(
390      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
391      base::Bind(&fileapi_internal::Remove,
392                 file_path, false /* not recursive */,
393                 google_apis::CreateRelayCallback(callback)),
394      base::Bind(callback, base::File::FILE_ERROR_FAILED));
395}
396
397void AsyncFileUtil::DeleteRecursively(
398    scoped_ptr<fileapi::FileSystemOperationContext> context,
399    const fileapi::FileSystemURL& url,
400    const StatusCallback& callback) {
401  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
402
403  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
404  if (file_path.empty()) {
405    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
406    return;
407  }
408
409  PostFileSystemCallback(
410      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
411      base::Bind(&fileapi_internal::Remove,
412                 file_path, true /* recursive */,
413                 google_apis::CreateRelayCallback(callback)),
414      base::Bind(callback, base::File::FILE_ERROR_FAILED));
415}
416
417void AsyncFileUtil::CreateSnapshotFile(
418    scoped_ptr<fileapi::FileSystemOperationContext> context,
419    const fileapi::FileSystemURL& url,
420    const CreateSnapshotFileCallback& callback) {
421  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
422
423  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
424  if (file_path.empty()) {
425    callback.Run(base::File::FILE_ERROR_NOT_FOUND,
426                 base::File::Info(),
427                 base::FilePath(),
428                 scoped_refptr<webkit_blob::ShareableFileReference>());
429    return;
430  }
431
432  PostFileSystemCallback(
433      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
434      base::Bind(&fileapi_internal::CreateSnapshotFile,
435                 file_path,
436                 google_apis::CreateRelayCallback(
437                     base::Bind(&RunCreateSnapshotFileCallback, callback))),
438      base::Bind(callback,
439                 base::File::FILE_ERROR_FAILED,
440                 base::File::Info(),
441                 base::FilePath(),
442                 scoped_refptr<webkit_blob::ShareableFileReference>()));
443}
444
445}  // namespace internal
446}  // namespace drive
447