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