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