root/chrome/browser/chromeos/drive/fileapi/async_file_util.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. PostFileSystemCallback
  2. RunCreateOrOpenFileCallback
  3. RunCreateOrOpenFileCallbackOnError
  4. RunEnsureFileExistsCallback
  5. RunCreateSnapshotFileCallback
  6. CreateOrOpen
  7. EnsureFileExists
  8. CreateDirectory
  9. GetFileInfo
  10. ReadDirectory
  11. Touch
  12. Truncate
  13. CopyFileLocal
  14. MoveFileLocal
  15. CopyInForeignFile
  16. DeleteFile
  17. DeleteDirectory
  18. DeleteRecursively
  19. CreateSnapshotFile

// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/drive/fileapi/async_file_util.h"

#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/platform_file.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/chromeos/drive/drive_integration_service.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/drive/fileapi/fileapi_worker.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/drive/task_util.h"
#include "webkit/browser/fileapi/file_system_operation_context.h"
#include "webkit/browser/fileapi/file_system_url.h"
#include "webkit/common/blob/shareable_file_reference.h"

using content::BrowserThread;

namespace drive {
namespace internal {
namespace {

// Posts fileapi_internal::RunFileSystemCallback to UI thread.
// This function must be called on IO thread.
// The |on_error_callback| will be called (on error case) on IO thread.
void PostFileSystemCallback(
    const fileapi_internal::FileSystemGetter& file_system_getter,
    const base::Callback<void(FileSystemInterface*)>& function,
    const base::Closure& on_error_callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  BrowserThread::PostTask(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(&fileapi_internal::RunFileSystemCallback,
                 file_system_getter, function,
                 on_error_callback.is_null() ?
                 base::Closure() :
                 base::Bind(&google_apis::RunTaskOnThread,
                            base::MessageLoopProxy::current(),
                            on_error_callback)));
}

// Runs CreateOrOpenFile callback based on the given |error| and |file|.
void RunCreateOrOpenFileCallback(
    const AsyncFileUtil::CreateOrOpenCallback& callback,
    base::File::Error error,
    base::PlatformFile file,
    const base::Closure& close_callback_on_ui_thread) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // It is necessary to make a closure, which runs on file closing here.
  // It will be provided as a FileSystem::OpenFileCallback's argument later.
  // (crbug.com/259184).
  callback.Run(
      error, base::PassPlatformFile(&file),
      base::Bind(&google_apis::RunTaskOnThread,
                 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI),
                 close_callback_on_ui_thread));
}

// Runs CreateOrOpenFile when the error happens.
void RunCreateOrOpenFileCallbackOnError(
    const AsyncFileUtil::CreateOrOpenCallback& callback,
    base::File::Error error) {
  // Because the |callback| takes PassPlatformFile as its argument, and
  // it is necessary to guarantee the pointer passed to PassPlatformFile is
  // alive during the |callback| invocation, here we prepare a thin adapter
  // to have PlatformFile on stack frame.
  base::PlatformFile file = base::kInvalidPlatformFileValue;
  callback.Run(error, base::PassPlatformFile(&file), base::Closure());
}

// Runs EnsureFileExistsCallback based on the given |error|.
void RunEnsureFileExistsCallback(
    const AsyncFileUtil::EnsureFileExistsCallback& callback,
    base::File::Error error) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  // Remember if the file is actually created or not.
  bool created = (error == base::File::FILE_OK);

  // File::FILE_ERROR_EXISTS is not an actual error here.
  if (error == base::File::FILE_ERROR_EXISTS)
    error = base::File::FILE_OK;

  callback.Run(error, created);
}

// Runs |callback| with the arguments based on the given arguments.
void RunCreateSnapshotFileCallback(
    const AsyncFileUtil::CreateSnapshotFileCallback& callback,
    base::File::Error error,
    const base::File::Info& file_info,
    const base::FilePath& local_path,
    webkit_blob::ScopedFile::ScopeOutPolicy scope_out_policy) {
  // ShareableFileReference is thread *unsafe* class. So it is necessary to
  // create the instance (by invoking GetOrCreate) on IO thread, though
  // most drive file system related operations run on UI thread.
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  scoped_refptr<webkit_blob::ShareableFileReference> file_reference =
      webkit_blob::ShareableFileReference::GetOrCreate(webkit_blob::ScopedFile(
          local_path,
          scope_out_policy,
          BrowserThread::GetBlockingPool()));
  callback.Run(error, file_info, local_path, file_reference);
}

}  // namespace

AsyncFileUtil::AsyncFileUtil() {
}

AsyncFileUtil::~AsyncFileUtil() {
}

void AsyncFileUtil::CreateOrOpen(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& url,
    int file_flags,
    const CreateOrOpenCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
  if (file_path.empty()) {
    base::PlatformFile platform_file = base::kInvalidPlatformFileValue;
    callback.Run(base::File::FILE_ERROR_NOT_FOUND,
                 base::PassPlatformFile(&platform_file),
                 base::Closure());
    return;
  }

  const fileapi_internal::FileSystemGetter getter =
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url);
  PostFileSystemCallback(
      getter,
      base::Bind(&fileapi_internal::OpenFile,
                 file_path, file_flags,
                 google_apis::CreateRelayCallback(
                     base::Bind(&RunCreateOrOpenFileCallback, callback))),
      base::Bind(&RunCreateOrOpenFileCallbackOnError,
                 callback, base::File::FILE_ERROR_FAILED));
}

void AsyncFileUtil::EnsureFileExists(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& url,
    const EnsureFileExistsCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
  if (file_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND, false);
    return;
  }

  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
      base::Bind(&fileapi_internal::CreateFile,
                 file_path, true /* is_exlusive */,
                 google_apis::CreateRelayCallback(
                     base::Bind(&RunEnsureFileExistsCallback, callback))),
      base::Bind(callback, base::File::FILE_ERROR_FAILED, false));
}

void AsyncFileUtil::CreateDirectory(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& url,
    bool exclusive,
    bool recursive,
    const StatusCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
  if (file_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
    return;
  }

  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
      base::Bind(&fileapi_internal::CreateDirectory,
                 file_path, exclusive, recursive,
                 google_apis::CreateRelayCallback(callback)),
      base::Bind(callback, base::File::FILE_ERROR_FAILED));
}

void AsyncFileUtil::GetFileInfo(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& url,
    const GetFileInfoCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
  if (file_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND, base::File::Info());
    return;
  }

  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
      base::Bind(&fileapi_internal::GetFileInfo,
                 file_path, google_apis::CreateRelayCallback(callback)),
      base::Bind(callback, base::File::FILE_ERROR_FAILED,
                 base::File::Info()));
}

void AsyncFileUtil::ReadDirectory(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& url,
    const ReadDirectoryCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
  if (file_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND, EntryList(), false);
    return;
  }

  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
      base::Bind(&fileapi_internal::ReadDirectory,
                 file_path, google_apis::CreateRelayCallback(callback)),
      base::Bind(callback, base::File::FILE_ERROR_FAILED,
                 EntryList(), false));
}

void AsyncFileUtil::Touch(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& url,
    const base::Time& last_access_time,
    const base::Time& last_modified_time,
    const StatusCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
  if (file_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
    return;
  }

  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
      base::Bind(&fileapi_internal::TouchFile,
                 file_path, last_access_time, last_modified_time,
                 google_apis::CreateRelayCallback(callback)),
      base::Bind(callback, base::File::FILE_ERROR_FAILED));
}

void AsyncFileUtil::Truncate(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& url,
    int64 length,
    const StatusCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
  if (file_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
    return;
  }

  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
      base::Bind(&fileapi_internal::Truncate,
                 file_path, length, google_apis::CreateRelayCallback(callback)),
      base::Bind(callback, base::File::FILE_ERROR_FAILED));
}

void AsyncFileUtil::CopyFileLocal(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& src_url,
    const fileapi::FileSystemURL& dest_url,
    CopyOrMoveOption option,
    const CopyFileProgressCallback& progress_callback,
    const StatusCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath src_path = util::ExtractDrivePathFromFileSystemUrl(src_url);
  base::FilePath dest_path = util::ExtractDrivePathFromFileSystemUrl(dest_url);
  if (src_path.empty() || dest_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
    return;
  }

  // TODO(kinaba): crbug.com/339794.
  // Assumption here is that |src_url| and |dest_url| are always from the same
  // profile. This indeed holds as long as we mount different profiles onto
  // different mount point. Hence, using GetFileSystemFromUrl(dest_url) is safe.
  // This will change after we introduce cross-profile sharing etc., and we
  // need to deal with files from different profiles here.
  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, dest_url),
      base::Bind(
          &fileapi_internal::Copy,
          src_path, dest_path,
          option == fileapi::FileSystemOperation::OPTION_PRESERVE_LAST_MODIFIED,
          google_apis::CreateRelayCallback(callback)),
      base::Bind(callback, base::File::FILE_ERROR_FAILED));
}

void AsyncFileUtil::MoveFileLocal(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& src_url,
    const fileapi::FileSystemURL& dest_url,
    CopyOrMoveOption option,
    const StatusCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath src_path = util::ExtractDrivePathFromFileSystemUrl(src_url);
  base::FilePath dest_path = util::ExtractDrivePathFromFileSystemUrl(dest_url);
  if (src_path.empty() || dest_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
    return;
  }

  // TODO(kinaba): see the comment in CopyFileLocal(). |src_url| and |dest_url|
  // always return the same FileSystem by GetFileSystemFromUrl, but we need to
  // change it in order to support cross-profile file sharing etc.
  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, dest_url),
      base::Bind(&fileapi_internal::Move,
                 src_path, dest_path,
                 google_apis::CreateRelayCallback(callback)),
      base::Bind(callback, base::File::FILE_ERROR_FAILED));
}

void AsyncFileUtil::CopyInForeignFile(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const base::FilePath& src_file_path,
    const fileapi::FileSystemURL& dest_url,
    const StatusCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath dest_path = util::ExtractDrivePathFromFileSystemUrl(dest_url);
  if (dest_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
    return;
  }

  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, dest_url),
      base::Bind(&fileapi_internal::CopyInForeignFile,
                 src_file_path, dest_path,
                 google_apis::CreateRelayCallback(callback)),
      base::Bind(callback, base::File::FILE_ERROR_FAILED));
}

void AsyncFileUtil::DeleteFile(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& url,
    const StatusCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
  if (file_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
    return;
  }

  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
      base::Bind(&fileapi_internal::Remove,
                 file_path, false /* not recursive */,
                 google_apis::CreateRelayCallback(callback)),
      base::Bind(callback, base::File::FILE_ERROR_FAILED));
}

void AsyncFileUtil::DeleteDirectory(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& url,
    const StatusCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
  if (file_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
    return;
  }

  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
      base::Bind(&fileapi_internal::Remove,
                 file_path, false /* not recursive */,
                 google_apis::CreateRelayCallback(callback)),
      base::Bind(callback, base::File::FILE_ERROR_FAILED));
}

void AsyncFileUtil::DeleteRecursively(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& url,
    const StatusCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
  if (file_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND);
    return;
  }

  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
      base::Bind(&fileapi_internal::Remove,
                 file_path, true /* recursive */,
                 google_apis::CreateRelayCallback(callback)),
      base::Bind(callback, base::File::FILE_ERROR_FAILED));
}

void AsyncFileUtil::CreateSnapshotFile(
    scoped_ptr<fileapi::FileSystemOperationContext> context,
    const fileapi::FileSystemURL& url,
    const CreateSnapshotFileCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));

  base::FilePath file_path = util::ExtractDrivePathFromFileSystemUrl(url);
  if (file_path.empty()) {
    callback.Run(base::File::FILE_ERROR_NOT_FOUND,
                 base::File::Info(),
                 base::FilePath(),
                 scoped_refptr<webkit_blob::ShareableFileReference>());
    return;
  }

  PostFileSystemCallback(
      base::Bind(&fileapi_internal::GetFileSystemFromUrl, url),
      base::Bind(&fileapi_internal::CreateSnapshotFile,
                 file_path,
                 google_apis::CreateRelayCallback(
                     base::Bind(&RunCreateSnapshotFileCallback, callback))),
      base::Bind(callback,
                 base::File::FILE_ERROR_FAILED,
                 base::File::Info(),
                 base::FilePath(),
                 scoped_refptr<webkit_blob::ShareableFileReference>()));
}

}  // namespace internal
}  // namespace drive

/* [<][>][^][v][top][bottom][index][help] */