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.
4
5#include "webkit/browser/fileapi/file_system_operation_impl.h"
6
7#include "base/bind.h"
8#include "base/single_thread_task_runner.h"
9#include "base/strings/utf_string_conversions.h"
10#include "base/time/time.h"
11#include "net/base/escape.h"
12#include "net/url_request/url_request.h"
13#include "webkit/browser/fileapi/async_file_util.h"
14#include "webkit/browser/fileapi/copy_or_move_operation_delegate.h"
15#include "webkit/browser/fileapi/file_observers.h"
16#include "webkit/browser/fileapi/file_system_backend.h"
17#include "webkit/browser/fileapi/file_system_context.h"
18#include "webkit/browser/fileapi/file_system_file_util.h"
19#include "webkit/browser/fileapi/file_system_operation_context.h"
20#include "webkit/browser/fileapi/file_system_url.h"
21#include "webkit/browser/fileapi/file_writer_delegate.h"
22#include "webkit/browser/fileapi/remove_operation_delegate.h"
23#include "webkit/browser/fileapi/sandbox_file_stream_writer.h"
24#include "webkit/browser/quota/quota_manager.h"
25#include "webkit/common/blob/shareable_file_reference.h"
26#include "webkit/common/fileapi/file_system_types.h"
27#include "webkit/common/fileapi/file_system_util.h"
28#include "webkit/common/quota/quota_types.h"
29
30using webkit_blob::ScopedFile;
31
32namespace fileapi {
33
34FileSystemOperationImpl::FileSystemOperationImpl(
35    const FileSystemURL& url,
36    FileSystemContext* file_system_context,
37    scoped_ptr<FileSystemOperationContext> operation_context)
38    : file_system_context_(file_system_context),
39      operation_context_(operation_context.Pass()),
40      async_file_util_(NULL),
41      peer_handle_(base::kNullProcessHandle),
42      pending_operation_(kOperationNone) {
43  DCHECK(operation_context_.get());
44  operation_context_->DetachUserDataThread();
45  async_file_util_ = file_system_context_->GetAsyncFileUtil(url.type());
46  DCHECK(async_file_util_);
47}
48
49FileSystemOperationImpl::~FileSystemOperationImpl() {
50}
51
52void FileSystemOperationImpl::CreateFile(const FileSystemURL& url,
53                                         bool exclusive,
54                                         const StatusCallback& callback) {
55  DCHECK(SetPendingOperationType(kOperationCreateFile));
56  GetUsageAndQuotaThenRunTask(
57      url,
58      base::Bind(&FileSystemOperationImpl::DoCreateFile,
59                 AsWeakPtr(), url, callback, exclusive),
60      base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED));
61}
62
63void FileSystemOperationImpl::CreateDirectory(const FileSystemURL& url,
64                                              bool exclusive,
65                                              bool recursive,
66                                              const StatusCallback& callback) {
67  DCHECK(SetPendingOperationType(kOperationCreateDirectory));
68  GetUsageAndQuotaThenRunTask(
69      url,
70      base::Bind(&FileSystemOperationImpl::DoCreateDirectory,
71                 AsWeakPtr(), url, callback, exclusive, recursive),
72      base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED));
73}
74
75void FileSystemOperationImpl::Copy(const FileSystemURL& src_url,
76                                   const FileSystemURL& dest_url,
77                                   const StatusCallback& callback) {
78  DCHECK(SetPendingOperationType(kOperationCopy));
79  DCHECK(!recursive_operation_delegate_);
80  recursive_operation_delegate_.reset(
81      new CopyOrMoveOperationDelegate(
82          file_system_context(),
83          src_url, dest_url,
84          CopyOrMoveOperationDelegate::OPERATION_COPY,
85          base::Bind(&FileSystemOperationImpl::DidFinishOperation,
86                     AsWeakPtr(), callback)));
87  recursive_operation_delegate_->RunRecursively();
88}
89
90void FileSystemOperationImpl::Move(const FileSystemURL& src_url,
91                                   const FileSystemURL& dest_url,
92                                   const StatusCallback& callback) {
93  DCHECK(SetPendingOperationType(kOperationMove));
94  DCHECK(!recursive_operation_delegate_);
95  recursive_operation_delegate_.reset(
96      new CopyOrMoveOperationDelegate(
97          file_system_context(),
98          src_url, dest_url,
99          CopyOrMoveOperationDelegate::OPERATION_MOVE,
100          base::Bind(&FileSystemOperationImpl::DidFinishOperation,
101                     AsWeakPtr(), callback)));
102  recursive_operation_delegate_->RunRecursively();
103}
104
105void FileSystemOperationImpl::DirectoryExists(const FileSystemURL& url,
106                                              const StatusCallback& callback) {
107  DCHECK(SetPendingOperationType(kOperationDirectoryExists));
108  async_file_util_->GetFileInfo(
109      operation_context_.Pass(), url,
110      base::Bind(&FileSystemOperationImpl::DidDirectoryExists,
111                 AsWeakPtr(), callback));
112}
113
114void FileSystemOperationImpl::FileExists(const FileSystemURL& url,
115                                         const StatusCallback& callback) {
116  DCHECK(SetPendingOperationType(kOperationFileExists));
117  async_file_util_->GetFileInfo(
118      operation_context_.Pass(), url,
119      base::Bind(&FileSystemOperationImpl::DidFileExists,
120                 AsWeakPtr(), callback));
121}
122
123void FileSystemOperationImpl::GetMetadata(
124    const FileSystemURL& url, const GetMetadataCallback& callback) {
125  DCHECK(SetPendingOperationType(kOperationGetMetadata));
126  async_file_util_->GetFileInfo(operation_context_.Pass(), url, callback);
127}
128
129void FileSystemOperationImpl::ReadDirectory(
130    const FileSystemURL& url, const ReadDirectoryCallback& callback) {
131  DCHECK(SetPendingOperationType(kOperationReadDirectory));
132  async_file_util_->ReadDirectory(
133      operation_context_.Pass(), url, callback);
134}
135
136void FileSystemOperationImpl::Remove(const FileSystemURL& url,
137                                     bool recursive,
138                                     const StatusCallback& callback) {
139  DCHECK(SetPendingOperationType(kOperationRemove));
140  DCHECK(!recursive_operation_delegate_);
141
142  if (recursive) {
143    // For recursive removal, try to delegate the operation to AsyncFileUtil
144    // first. If not supported, it is delegated to RemoveOperationDelegate
145    // in DidDeleteRecursively.
146    async_file_util_->DeleteRecursively(
147        operation_context_.Pass(), url,
148        base::Bind(&FileSystemOperationImpl::DidDeleteRecursively,
149                   AsWeakPtr(), url, callback));
150    return;
151  }
152
153  recursive_operation_delegate_.reset(
154      new RemoveOperationDelegate(
155          file_system_context(), url,
156          base::Bind(&FileSystemOperationImpl::DidFinishOperation,
157                     AsWeakPtr(), callback)));
158  recursive_operation_delegate_->Run();
159}
160
161void FileSystemOperationImpl::Write(
162    const FileSystemURL& url,
163    scoped_ptr<FileWriterDelegate> writer_delegate,
164    scoped_ptr<net::URLRequest> blob_request,
165    const WriteCallback& callback) {
166  DCHECK(SetPendingOperationType(kOperationWrite));
167  file_writer_delegate_ = writer_delegate.Pass();
168  file_writer_delegate_->Start(
169      blob_request.Pass(),
170      base::Bind(&FileSystemOperationImpl::DidWrite, AsWeakPtr(),
171                 url, callback));
172}
173
174void FileSystemOperationImpl::Truncate(const FileSystemURL& url, int64 length,
175                                       const StatusCallback& callback) {
176  DCHECK(SetPendingOperationType(kOperationTruncate));
177  GetUsageAndQuotaThenRunTask(
178      url,
179      base::Bind(&FileSystemOperationImpl::DoTruncate,
180                 AsWeakPtr(), url, callback, length),
181      base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED));
182}
183
184void FileSystemOperationImpl::TouchFile(const FileSystemURL& url,
185                                        const base::Time& last_access_time,
186                                        const base::Time& last_modified_time,
187                                        const StatusCallback& callback) {
188  DCHECK(SetPendingOperationType(kOperationTouchFile));
189  async_file_util_->Touch(
190      operation_context_.Pass(), url,
191      last_access_time, last_modified_time,
192      base::Bind(&FileSystemOperationImpl::DidFinishOperation,
193                 AsWeakPtr(), callback));
194}
195
196void FileSystemOperationImpl::OpenFile(const FileSystemURL& url,
197                                       int file_flags,
198                                       base::ProcessHandle peer_handle,
199                                       const OpenFileCallback& callback) {
200  DCHECK(SetPendingOperationType(kOperationOpenFile));
201  peer_handle_ = peer_handle;
202
203  if (file_flags & (
204      (base::PLATFORM_FILE_ENUMERATE | base::PLATFORM_FILE_TEMPORARY |
205       base::PLATFORM_FILE_HIDDEN))) {
206    callback.Run(base::PLATFORM_FILE_ERROR_FAILED,
207                 base::kInvalidPlatformFileValue,
208                 base::Closure(),
209                 base::kNullProcessHandle);
210    return;
211  }
212  GetUsageAndQuotaThenRunTask(
213      url,
214      base::Bind(&FileSystemOperationImpl::DoOpenFile,
215                 AsWeakPtr(),
216                 url, callback, file_flags),
217      base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED,
218                 base::kInvalidPlatformFileValue,
219                 base::Closure(),
220                 base::kNullProcessHandle));
221}
222
223// We can only get here on a write or truncate that's not yet completed.
224// We don't support cancelling any other operation at this time.
225void FileSystemOperationImpl::Cancel(const StatusCallback& cancel_callback) {
226  DCHECK(cancel_callback_.is_null());
227  cancel_callback_ = cancel_callback;
228
229  if (file_writer_delegate_.get()) {
230    DCHECK_EQ(kOperationWrite, pending_operation_);
231    // This will call DidWrite() with ABORT status code.
232    file_writer_delegate_->Cancel();
233  } else {
234    // For truncate we have no way to cancel the inflight operation (for now).
235    // Let it just run and dispatch cancel callback later.
236    DCHECK_EQ(kOperationTruncate, pending_operation_);
237  }
238}
239
240FileSystemOperationImpl* FileSystemOperationImpl::AsFileSystemOperationImpl() {
241  return this;
242}
243
244base::PlatformFileError FileSystemOperationImpl::SyncGetPlatformPath(
245    const FileSystemURL& url,
246    base::FilePath* platform_path) {
247  DCHECK(SetPendingOperationType(kOperationGetLocalPath));
248  FileSystemFileUtil* file_util = file_system_context()->GetFileUtil(
249      url.type());
250  if (!file_util)
251    return base::PLATFORM_FILE_ERROR_INVALID_OPERATION;
252  file_util->GetLocalFilePath(operation_context_.get(), url, platform_path);
253  return base::PLATFORM_FILE_OK;
254}
255
256void FileSystemOperationImpl::CreateSnapshotFile(
257    const FileSystemURL& url,
258    const SnapshotFileCallback& callback) {
259  DCHECK(SetPendingOperationType(kOperationCreateSnapshotFile));
260  async_file_util_->CreateSnapshotFile(
261      operation_context_.Pass(), url, callback);
262}
263
264void FileSystemOperationImpl::CopyInForeignFile(
265    const base::FilePath& src_local_disk_file_path,
266    const FileSystemURL& dest_url,
267    const StatusCallback& callback) {
268  DCHECK(SetPendingOperationType(kOperationCopyInForeignFile));
269  GetUsageAndQuotaThenRunTask(
270      dest_url,
271      base::Bind(&FileSystemOperationImpl::DoCopyInForeignFile,
272                 AsWeakPtr(), src_local_disk_file_path, dest_url,
273                 callback),
274      base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED));
275}
276
277void FileSystemOperationImpl::RemoveFile(
278    const FileSystemURL& url,
279    const StatusCallback& callback) {
280  DCHECK(SetPendingOperationType(kOperationRemove));
281  async_file_util_->DeleteFile(
282      operation_context_.Pass(), url,
283      base::Bind(&FileSystemOperationImpl::DidFinishOperation,
284                 AsWeakPtr(), callback));
285}
286
287void FileSystemOperationImpl::RemoveDirectory(
288    const FileSystemURL& url,
289    const StatusCallback& callback) {
290  DCHECK(SetPendingOperationType(kOperationRemove));
291  async_file_util_->DeleteDirectory(
292      operation_context_.Pass(), url,
293      base::Bind(&FileSystemOperationImpl::DidFinishOperation,
294                 AsWeakPtr(), callback));
295}
296
297void FileSystemOperationImpl::CopyFileLocal(
298    const FileSystemURL& src_url,
299    const FileSystemURL& dest_url,
300    const StatusCallback& callback) {
301  DCHECK(SetPendingOperationType(kOperationCopy));
302  DCHECK(src_url.IsInSameFileSystem(dest_url));
303  GetUsageAndQuotaThenRunTask(
304      dest_url,
305      base::Bind(&FileSystemOperationImpl::DoCopyFileLocal,
306                 AsWeakPtr(), src_url, dest_url, callback),
307      base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED));
308}
309
310void FileSystemOperationImpl::MoveFileLocal(
311    const FileSystemURL& src_url,
312    const FileSystemURL& dest_url,
313    const StatusCallback& callback) {
314  DCHECK(SetPendingOperationType(kOperationMove));
315  DCHECK(src_url.IsInSameFileSystem(dest_url));
316  GetUsageAndQuotaThenRunTask(
317      dest_url,
318      base::Bind(&FileSystemOperationImpl::DoMoveFileLocal,
319                 AsWeakPtr(), src_url, dest_url, callback),
320      base::Bind(callback, base::PLATFORM_FILE_ERROR_FAILED));
321}
322
323void FileSystemOperationImpl::GetUsageAndQuotaThenRunTask(
324    const FileSystemURL& url,
325    const base::Closure& task,
326    const base::Closure& error_callback) {
327  quota::QuotaManagerProxy* quota_manager_proxy =
328      file_system_context()->quota_manager_proxy();
329  if (!quota_manager_proxy ||
330      !file_system_context()->GetQuotaUtil(url.type())) {
331    // If we don't have the quota manager or the requested filesystem type
332    // does not support quota, we should be able to let it go.
333    operation_context_->set_allowed_bytes_growth(kint64max);
334    task.Run();
335    return;
336  }
337
338  DCHECK(quota_manager_proxy);
339  DCHECK(quota_manager_proxy->quota_manager());
340  quota_manager_proxy->quota_manager()->GetUsageAndQuota(
341      url.origin(),
342      FileSystemTypeToQuotaStorageType(url.type()),
343      base::Bind(&FileSystemOperationImpl::DidGetUsageAndQuotaAndRunTask,
344                 AsWeakPtr(), task, error_callback));
345}
346
347void FileSystemOperationImpl::DidGetUsageAndQuotaAndRunTask(
348    const base::Closure& task,
349    const base::Closure& error_callback,
350    quota::QuotaStatusCode status,
351    int64 usage, int64 quota) {
352  if (status != quota::kQuotaStatusOk) {
353    LOG(WARNING) << "Got unexpected quota error : " << status;
354    error_callback.Run();
355    return;
356  }
357
358  operation_context_->set_allowed_bytes_growth(quota - usage);
359  task.Run();
360}
361
362void FileSystemOperationImpl::DoCreateFile(
363    const FileSystemURL& url,
364    const StatusCallback& callback,
365    bool exclusive) {
366  async_file_util_->EnsureFileExists(
367      operation_context_.Pass(), url,
368      base::Bind(
369          exclusive ?
370              &FileSystemOperationImpl::DidEnsureFileExistsExclusive :
371              &FileSystemOperationImpl::DidEnsureFileExistsNonExclusive,
372          AsWeakPtr(), callback));
373}
374
375void FileSystemOperationImpl::DoCreateDirectory(
376    const FileSystemURL& url,
377    const StatusCallback& callback,
378    bool exclusive, bool recursive) {
379  async_file_util_->CreateDirectory(
380      operation_context_.Pass(),
381      url, exclusive, recursive,
382      base::Bind(&FileSystemOperationImpl::DidFinishOperation,
383                 AsWeakPtr(), callback));
384}
385
386void FileSystemOperationImpl::DoCopyFileLocal(
387    const FileSystemURL& src_url,
388    const FileSystemURL& dest_url,
389    const StatusCallback& callback) {
390  async_file_util_->CopyFileLocal(
391      operation_context_.Pass(), src_url, dest_url,
392      base::Bind(&FileSystemOperationImpl::DidFinishOperation,
393                 AsWeakPtr(), callback));
394}
395
396void FileSystemOperationImpl::DoMoveFileLocal(
397    const FileSystemURL& src_url,
398    const FileSystemURL& dest_url,
399    const StatusCallback& callback) {
400  async_file_util_->MoveFileLocal(
401      operation_context_.Pass(), src_url, dest_url,
402      base::Bind(&FileSystemOperationImpl::DidFinishOperation,
403                 AsWeakPtr(), callback));
404}
405
406void FileSystemOperationImpl::DoCopyInForeignFile(
407    const base::FilePath& src_local_disk_file_path,
408    const FileSystemURL& dest_url,
409    const StatusCallback& callback) {
410  async_file_util_->CopyInForeignFile(
411      operation_context_.Pass(),
412      src_local_disk_file_path, dest_url,
413      base::Bind(&FileSystemOperationImpl::DidFinishOperation,
414                 AsWeakPtr(), callback));
415}
416
417void FileSystemOperationImpl::DoTruncate(const FileSystemURL& url,
418                                         const StatusCallback& callback,
419                                         int64 length) {
420  async_file_util_->Truncate(
421      operation_context_.Pass(), url, length,
422      base::Bind(&FileSystemOperationImpl::DidFinishOperation,
423                 AsWeakPtr(), callback));
424}
425
426void FileSystemOperationImpl::DoOpenFile(const FileSystemURL& url,
427                                         const OpenFileCallback& callback,
428                                         int file_flags) {
429  async_file_util_->CreateOrOpen(
430      operation_context_.Pass(), url, file_flags,
431      base::Bind(&FileSystemOperationImpl::DidOpenFile,
432                 AsWeakPtr(), callback));
433}
434
435void FileSystemOperationImpl::DidEnsureFileExistsExclusive(
436    const StatusCallback& callback,
437    base::PlatformFileError rv, bool created) {
438  if (rv == base::PLATFORM_FILE_OK && !created) {
439    callback.Run(base::PLATFORM_FILE_ERROR_EXISTS);
440  } else {
441    DidFinishOperation(callback, rv);
442  }
443}
444
445void FileSystemOperationImpl::DidEnsureFileExistsNonExclusive(
446    const StatusCallback& callback,
447    base::PlatformFileError rv, bool /* created */) {
448  DidFinishOperation(callback, rv);
449}
450
451void FileSystemOperationImpl::DidFinishOperation(
452    const StatusCallback& callback,
453    base::PlatformFileError rv) {
454  if (!cancel_callback_.is_null()) {
455    DCHECK_EQ(kOperationTruncate, pending_operation_);
456
457    StatusCallback cancel_callback = cancel_callback_;
458    callback.Run(base::PLATFORM_FILE_ERROR_ABORT);
459    cancel_callback.Run(base::PLATFORM_FILE_OK);
460  } else {
461    callback.Run(rv);
462  }
463}
464
465void FileSystemOperationImpl::DidDirectoryExists(
466    const StatusCallback& callback,
467    base::PlatformFileError rv,
468    const base::PlatformFileInfo& file_info) {
469  if (rv == base::PLATFORM_FILE_OK && !file_info.is_directory)
470    rv = base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY;
471  callback.Run(rv);
472}
473
474void FileSystemOperationImpl::DidFileExists(
475    const StatusCallback& callback,
476    base::PlatformFileError rv,
477    const base::PlatformFileInfo& file_info) {
478  if (rv == base::PLATFORM_FILE_OK && file_info.is_directory)
479    rv = base::PLATFORM_FILE_ERROR_NOT_A_FILE;
480  callback.Run(rv);
481}
482
483void FileSystemOperationImpl::DidDeleteRecursively(
484    const FileSystemURL& url,
485    const StatusCallback& callback,
486    base::PlatformFileError rv) {
487  if (rv == base::PLATFORM_FILE_ERROR_INVALID_OPERATION) {
488    // Recursive removal is not supported on this platform.
489    DCHECK(!recursive_operation_delegate_);
490    recursive_operation_delegate_.reset(
491        new RemoveOperationDelegate(
492            file_system_context(), url,
493            base::Bind(&FileSystemOperationImpl::DidFinishOperation,
494                       AsWeakPtr(), callback)));
495    recursive_operation_delegate_->RunRecursively();
496    return;
497  }
498
499  callback.Run(rv);
500}
501
502void FileSystemOperationImpl::DidWrite(
503    const FileSystemURL& url,
504    const WriteCallback& write_callback,
505    base::PlatformFileError rv,
506    int64 bytes,
507    FileWriterDelegate::WriteProgressStatus write_status) {
508  const bool complete = (
509      write_status != FileWriterDelegate::SUCCESS_IO_PENDING);
510  if (complete && write_status != FileWriterDelegate::ERROR_WRITE_NOT_STARTED) {
511    DCHECK(operation_context_);
512    operation_context_->change_observers()->Notify(
513        &FileChangeObserver::OnModifyFile, MakeTuple(url));
514  }
515
516  StatusCallback cancel_callback = cancel_callback_;
517  write_callback.Run(rv, bytes, complete);
518  if (!cancel_callback.is_null())
519    cancel_callback.Run(base::PLATFORM_FILE_OK);
520}
521
522void FileSystemOperationImpl::DidOpenFile(
523    const OpenFileCallback& callback,
524    base::PlatformFileError rv,
525    base::PassPlatformFile file,
526    const base::Closure& on_close_callback) {
527  if (rv == base::PLATFORM_FILE_OK)
528    CHECK_NE(base::kNullProcessHandle, peer_handle_);
529  callback.Run(rv, file.ReleaseValue(), on_close_callback, peer_handle_);
530}
531
532bool FileSystemOperationImpl::SetPendingOperationType(OperationType type) {
533  if (pending_operation_ != kOperationNone)
534    return false;
535  pending_operation_ = type;
536  return true;
537}
538
539}  // namespace fileapi
540