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 "storage/browser/fileapi/file_system_operation_context.h"
17#include "storage/browser/fileapi/file_system_url.h"
18#include "storage/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    storage::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<storage::ShareableFileReference> file_reference =
118      storage::ShareableFileReference::GetOrCreate(storage::ScopedFile(
119          local_path, scope_out_policy, BrowserThread::GetBlockingPool()));
120  callback.Run(error, file_info, local_path, file_reference);
121}
122
123}  // namespace
124
125AsyncFileUtil::AsyncFileUtil() {
126}
127
128AsyncFileUtil::~AsyncFileUtil() {
129}
130
131void AsyncFileUtil::CreateOrOpen(
132    scoped_ptr<storage::FileSystemOperationContext> context,
133    const storage::FileSystemURL& url,
134    int file_flags,
135    const CreateOrOpenCallback& callback) {
136  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
137
138  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
139  if (file_path.empty()) {
140    callback.Run(base::File(base::File::FILE_ERROR_NOT_FOUND), base::Closure());
141    return;
142  }
143
144  const fileapi_internal::FileSystemGetter getter =
145      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url);
146  PostFileSystemCallback(
147      getter,
148      base::Bind(&fileapi_internal::OpenFile,
149                 file_path, file_flags,
150                 google_apis::CreateRelayCallback(
151                     base::Bind(&RunCreateOrOpenFileCallback, callback))),
152      base::Bind(&RunCreateOrOpenFileCallbackOnError,
153                 callback, base::File::FILE_ERROR_FAILED));
154}
155
156void AsyncFileUtil::EnsureFileExists(
157    scoped_ptr<storage::FileSystemOperationContext> context,
158    const storage::FileSystemURL& url,
159    const EnsureFileExistsCallback& callback) {
160  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
161
162  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
163  if (file_path.empty()) {
164    callback.Run(base::File::FILE_ERROR_NOT_FOUND, false);
165    return;
166  }
167
168  PostFileSystemCallback(
169      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
170      base::Bind(&fileapi_internal::CreateFile,
171                 file_path, true /* is_exlusive */,
172                 google_apis::CreateRelayCallback(
173                     base::Bind(&RunEnsureFileExistsCallback, callback))),
174      base::Bind(callback, base::File::FILE_ERROR_FAILED, false));
175}
176
177void AsyncFileUtil::CreateDirectory(
178    scoped_ptr<storage::FileSystemOperationContext> context,
179    const storage::FileSystemURL& url,
180    bool exclusive,
181    bool recursive,
182    const StatusCallback& callback) {
183  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
184
185  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
186  if (file_path.empty()) {
187    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
188    return;
189  }
190
191  PostFileSystemCallback(
192      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
193      base::Bind(&fileapi_internal::CreateDirectory,
194                 file_path, exclusive, recursive,
195                 google_apis::CreateRelayCallback(callback)),
196      base::Bind(callback, base::File::FILE_ERROR_FAILED));
197}
198
199void AsyncFileUtil::GetFileInfo(
200    scoped_ptr<storage::FileSystemOperationContext> context,
201    const storage::FileSystemURL& url,
202    const GetFileInfoCallback& callback) {
203  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
204
205  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
206  if (file_path.empty()) {
207    callback.Run(base::File::FILE_ERROR_NOT_FOUND, base::File::Info());
208    return;
209  }
210
211  PostFileSystemCallback(
212      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
213      base::Bind(&fileapi_internal::GetFileInfo,
214                 file_path, google_apis::CreateRelayCallback(callback)),
215      base::Bind(callback, base::File::FILE_ERROR_FAILED,
216                 base::File::Info()));
217}
218
219void AsyncFileUtil::ReadDirectory(
220    scoped_ptr<storage::FileSystemOperationContext> context,
221    const storage::FileSystemURL& url,
222    const ReadDirectoryCallback& callback) {
223  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
224
225  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
226  if (file_path.empty()) {
227    callback.Run(base::File::FILE_ERROR_NOT_FOUND, EntryList(), false);
228    return;
229  }
230
231  PostFileSystemCallback(
232      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
233      base::Bind(&fileapi_internal::ReadDirectory,
234                 file_path, google_apis::CreateRelayCallback(callback)),
235      base::Bind(callback, base::File::FILE_ERROR_FAILED,
236                 EntryList(), false));
237}
238
239void AsyncFileUtil::Touch(
240    scoped_ptr<storage::FileSystemOperationContext> context,
241    const storage::FileSystemURL& url,
242    const base::Time& last_access_time,
243    const base::Time& last_modified_time,
244    const StatusCallback& callback) {
245  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
246
247  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
248  if (file_path.empty()) {
249    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
250    return;
251  }
252
253  PostFileSystemCallback(
254      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
255      base::Bind(&fileapi_internal::TouchFile,
256                 file_path, last_access_time, last_modified_time,
257                 google_apis::CreateRelayCallback(callback)),
258      base::Bind(callback, base::File::FILE_ERROR_FAILED));
259}
260
261void AsyncFileUtil::Truncate(
262    scoped_ptr<storage::FileSystemOperationContext> context,
263    const storage::FileSystemURL& url,
264    int64 length,
265    const StatusCallback& callback) {
266  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
267
268  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
269  if (file_path.empty()) {
270    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
271    return;
272  }
273
274  PostFileSystemCallback(
275      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
276      base::Bind(&fileapi_internal::Truncate,
277                 file_path, length, google_apis::CreateRelayCallback(callback)),
278      base::Bind(callback, base::File::FILE_ERROR_FAILED));
279}
280
281void AsyncFileUtil::CopyFileLocal(
282    scoped_ptr<storage::FileSystemOperationContext> context,
283    const storage::FileSystemURL& src_url,
284    const storage::FileSystemURL& dest_url,
285    CopyOrMoveOption option,
286    const CopyFileProgressCallback& progress_callback,
287    const StatusCallback& callback) {
288  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
289
290  base::FilePath src_path = util::ExtractDrivePathFromFileSystemUrl(src_url);
291  base::FilePath dest_path = util::ExtractDrivePathFromFileSystemUrl(dest_url);
292  if (src_path.empty() || dest_path.empty()) {
293    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
294    return;
295  }
296
297  // TODO(kinaba): crbug.com/339794.
298  // Assumption here is that |src_url| and |dest_url| are always from the same
299  // profile. This indeed holds as long as we mount different profiles onto
300  // different mount point. Hence, using GetFileSystemFromUrl(dest_url) is safe.
301  // This will change after we introduce cross-profile sharing etc., and we
302  // need to deal with files from different profiles here.
303  PostFileSystemCallback(
304      base::Bind(&fileapi_internal::GetFileSystemFromUrl, dest_url),
305      base::Bind(
306          &fileapi_internal::Copy,
307          src_path,
308          dest_path,
309          option == storage::FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED,
310          google_apis::CreateRelayCallback(callback)),
311      base::Bind(callback, base::File::FILE_ERROR_FAILED));
312}
313
314void AsyncFileUtil::MoveFileLocal(
315    scoped_ptr<storage::FileSystemOperationContext> context,
316    const storage::FileSystemURL& src_url,
317    const storage::FileSystemURL& dest_url,
318    CopyOrMoveOption option,
319    const StatusCallback& callback) {
320  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
321
322  base::FilePath src_path = util::ExtractDrivePathFromFileSystemUrl(src_url);
323  base::FilePath dest_path = util::ExtractDrivePathFromFileSystemUrl(dest_url);
324  if (src_path.empty() || dest_path.empty()) {
325    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
326    return;
327  }
328
329  // TODO(kinaba): see the comment in CopyFileLocal(). |src_url| and |dest_url|
330  // always return the same FileSystem by GetFileSystemFromUrl, but we need to
331  // change it in order to support cross-profile file sharing etc.
332  PostFileSystemCallback(
333      base::Bind(&fileapi_internal::GetFileSystemFromUrl, dest_url),
334      base::Bind(&fileapi_internal::Move,
335                 src_path, dest_path,
336                 google_apis::CreateRelayCallback(callback)),
337      base::Bind(callback, base::File::FILE_ERROR_FAILED));
338}
339
340void AsyncFileUtil::CopyInForeignFile(
341    scoped_ptr<storage::FileSystemOperationContext> context,
342    const base::FilePath& src_file_path,
343    const storage::FileSystemURL& dest_url,
344    const StatusCallback& callback) {
345  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
346
347  base::FilePath dest_path = util::ExtractDrivePathFromFileSystemUrl(dest_url);
348  if (dest_path.empty()) {
349    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
350    return;
351  }
352
353  PostFileSystemCallback(
354      base::Bind(&fileapi_internal::GetFileSystemFromUrl, dest_url),
355      base::Bind(&fileapi_internal::CopyInForeignFile,
356                 src_file_path, dest_path,
357                 google_apis::CreateRelayCallback(callback)),
358      base::Bind(callback, base::File::FILE_ERROR_FAILED));
359}
360
361void AsyncFileUtil::DeleteFile(
362    scoped_ptr<storage::FileSystemOperationContext> context,
363    const storage::FileSystemURL& url,
364    const StatusCallback& callback) {
365  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
366
367  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
368  if (file_path.empty()) {
369    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
370    return;
371  }
372
373  PostFileSystemCallback(
374      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
375      base::Bind(&fileapi_internal::Remove,
376                 file_path, false /* not recursive */,
377                 google_apis::CreateRelayCallback(callback)),
378      base::Bind(callback, base::File::FILE_ERROR_FAILED));
379}
380
381void AsyncFileUtil::DeleteDirectory(
382    scoped_ptr<storage::FileSystemOperationContext> context,
383    const storage::FileSystemURL& url,
384    const StatusCallback& callback) {
385  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
386
387  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
388  if (file_path.empty()) {
389    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
390    return;
391  }
392
393  PostFileSystemCallback(
394      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
395      base::Bind(&fileapi_internal::Remove,
396                 file_path, false /* not recursive */,
397                 google_apis::CreateRelayCallback(callback)),
398      base::Bind(callback, base::File::FILE_ERROR_FAILED));
399}
400
401void AsyncFileUtil::DeleteRecursively(
402    scoped_ptr<storage::FileSystemOperationContext> context,
403    const storage::FileSystemURL& url,
404    const StatusCallback& callback) {
405  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
406
407  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
408  if (file_path.empty()) {
409    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
410    return;
411  }
412
413  PostFileSystemCallback(
414      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
415      base::Bind(&fileapi_internal::Remove,
416                 file_path, true /* recursive */,
417                 google_apis::CreateRelayCallback(callback)),
418      base::Bind(callback, base::File::FILE_ERROR_FAILED));
419}
420
421void AsyncFileUtil::CreateSnapshotFile(
422    scoped_ptr<storage::FileSystemOperationContext> context,
423    const storage::FileSystemURL& url,
424    const CreateSnapshotFileCallback& callback) {
425  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
426
427  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
428  if (file_path.empty()) {
429    callback.Run(base::File::FILE_ERROR_NOT_FOUND,
430                 base::File::Info(),
431                 base::FilePath(),
432                 scoped_refptr<storage::ShareableFileReference>());
433    return;
434  }
435
436  PostFileSystemCallback(
437      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
438      base::Bind(&fileapi_internal::CreateSnapshotFile,
439                 file_path,
440                 google_apis::CreateRelayCallback(
441                     base::Bind(&RunCreateSnapshotFileCallback, callback))),
442      base::Bind(callback,
443                 base::File::FILE_ERROR_FAILED,
444                 base::File::Info(),
445                 base::FilePath(),
446                 scoped_refptr<storage::ShareableFileReference>()));
447}
448
449}  // namespace internal
450}  // namespace drive
451