summaryrefslogtreecommitdiffstats
path: root/components/drive/file_system/download_operation.cc
diff options
context:
space:
mode:
Diffstat (limited to 'components/drive/file_system/download_operation.cc')
-rw-r--r--components/drive/file_system/download_operation.cc540
1 files changed, 540 insertions, 0 deletions
diff --git a/components/drive/file_system/download_operation.cc b/components/drive/file_system/download_operation.cc
new file mode 100644
index 0000000..150009c
--- /dev/null
+++ b/components/drive/file_system/download_operation.cc
@@ -0,0 +1,540 @@
+// Copyright 2013 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 "components/drive/file_system/download_operation.h"
+
+#include "base/callback_helpers.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/task_runner_util.h"
+#include "components/drive/drive.pb.h"
+#include "components/drive/file_cache.h"
+#include "components/drive/file_change.h"
+#include "components/drive/file_errors.h"
+#include "components/drive/file_system/operation_delegate.h"
+#include "components/drive/file_system_core_util.h"
+#include "components/drive/job_scheduler.h"
+#include "components/drive/resource_metadata.h"
+#include "google_apis/drive/drive_api_error_codes.h"
+
+namespace drive {
+namespace file_system {
+namespace {
+
+// Generates an unused file path with |extension| to |out_path|, as a descendant
+// of |dir|, with its parent directory created.
+bool GeneratesUniquePathWithExtension(
+ const base::FilePath& dir,
+ const base::FilePath::StringType& extension,
+ base::FilePath* out_path) {
+ base::FilePath subdir;
+ if (!base::CreateTemporaryDirInDir(dir, base::FilePath::StringType(),
+ &subdir)) {
+ return false;
+ }
+ *out_path = subdir.Append(FILE_PATH_LITERAL("tmp") + extension);
+ return true;
+}
+
+// Prepares for downloading the file. Allocates the enough space for the file
+// in the cache.
+// If succeeded, returns FILE_ERROR_OK with |temp_download_file| storing the
+// path to the file in the cache.
+FileError PrepareForDownloadFile(internal::FileCache* cache,
+ int64 expected_file_size,
+ const base::FilePath& temporary_file_directory,
+ base::FilePath* temp_download_file) {
+ DCHECK(cache);
+ DCHECK(temp_download_file);
+
+ // Ensure enough space in the cache.
+ if (!cache->FreeDiskSpaceIfNeededFor(expected_file_size))
+ return FILE_ERROR_NO_LOCAL_SPACE;
+
+ return base::CreateTemporaryFileInDir(
+ temporary_file_directory,
+ temp_download_file) ? FILE_ERROR_OK : FILE_ERROR_FAILED;
+}
+
+// If the resource is a hosted document, creates a JSON file representing the
+// resource locally, and returns FILE_ERROR_OK with |cache_file_path| storing
+// the path to the JSON file.
+// If the resource is a regular file and its local cache is available,
+// returns FILE_ERROR_OK with |cache_file_path| storing the path to the
+// cache file.
+// If the resource is a regular file but its local cache is NOT available,
+// returns FILE_ERROR_OK, but |cache_file_path| is kept empty.
+// Otherwise returns error code.
+FileError CheckPreConditionForEnsureFileDownloaded(
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& temporary_file_directory,
+ const std::string& local_id,
+ ResourceEntry* entry,
+ base::FilePath* cache_file_path,
+ base::FilePath* temp_download_file_path) {
+ DCHECK(metadata);
+ DCHECK(cache);
+ DCHECK(cache_file_path);
+
+ FileError error = metadata->GetResourceEntryById(local_id, entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ if (entry->file_info().is_directory())
+ return FILE_ERROR_NOT_A_FILE;
+
+ // For a hosted document, we create a special JSON file to represent the
+ // document instead of fetching the document content in one of the exported
+ // formats. The JSON file contains the edit URL and resource ID of the
+ // document.
+ if (entry->file_specific_info().is_hosted_document()) {
+ base::FilePath::StringType extension = base::FilePath::FromUTF8Unsafe(
+ entry->file_specific_info().document_extension()).value();
+ base::FilePath gdoc_file_path;
+ base::File::Info file_info;
+ // We add the gdoc file extension in the temporary file, so that in cross
+ // profile drag-and-drop between Drive folders, the destination profiles's
+ // CopyOperation can detect the special JSON file only by the path.
+ if (!GeneratesUniquePathWithExtension(temporary_file_directory,
+ extension,
+ &gdoc_file_path) ||
+ !util::CreateGDocFile(gdoc_file_path,
+ GURL(entry->file_specific_info().alternate_url()),
+ entry->resource_id()) ||
+ !base::GetFileInfo(gdoc_file_path,
+ reinterpret_cast<base::File::Info*>(&file_info)))
+ return FILE_ERROR_FAILED;
+
+ *cache_file_path = gdoc_file_path;
+ entry->mutable_file_info()->set_size(file_info.size);
+ return FILE_ERROR_OK;
+ }
+
+ if (!entry->file_specific_info().cache_state().is_present()) {
+ // This file has no cache file.
+ if (!entry->resource_id().empty()) {
+ // This entry exists on the server, leave |cache_file_path| empty to
+ // start download.
+ return PrepareForDownloadFile(cache, entry->file_info().size(),
+ temporary_file_directory,
+ temp_download_file_path);
+ }
+
+ // This entry does not exist on the server, store an empty file and mark it
+ // as dirty.
+ base::FilePath empty_file;
+ if (!base::CreateTemporaryFileInDir(temporary_file_directory, &empty_file))
+ return FILE_ERROR_FAILED;
+ error = cache->Store(local_id, std::string(), empty_file,
+ internal::FileCache::FILE_OPERATION_MOVE);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ error = metadata->GetResourceEntryById(local_id, entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ }
+
+ // Leave |cache_file_path| empty when the stored file is obsolete and has no
+ // local modification.
+ if (!entry->file_specific_info().cache_state().is_dirty() &&
+ entry->file_specific_info().md5() !=
+ entry->file_specific_info().cache_state().md5()) {
+ return PrepareForDownloadFile(cache, entry->file_info().size(),
+ temporary_file_directory,
+ temp_download_file_path);
+ }
+
+ // Fill |cache_file_path| with the path to the cached file.
+ error = cache->GetFile(local_id, cache_file_path);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ // If the cache file is to be returned as the download result, the file info
+ // of the cache needs to be returned via |entry|.
+ // TODO(kinaba): crbug.com/246469. The logic below is similar to that in
+ // drive::FileSystem::CheckLocalModificationAndRun. We should merge them.
+ base::File::Info file_info;
+ if (base::GetFileInfo(*cache_file_path, &file_info))
+ entry->mutable_file_info()->set_size(file_info.size);
+
+ return FILE_ERROR_OK;
+}
+
+struct CheckPreconditionForEnsureFileDownloadedParams {
+ internal::ResourceMetadata* metadata;
+ internal::FileCache* cache;
+ base::FilePath temporary_file_directory;
+};
+
+// Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
+// the given ID. Also fills |drive_file_path| with the path of the entry.
+FileError CheckPreConditionForEnsureFileDownloadedByLocalId(
+ const CheckPreconditionForEnsureFileDownloadedParams& params,
+ const std::string& local_id,
+ base::FilePath* drive_file_path,
+ base::FilePath* cache_file_path,
+ base::FilePath* temp_download_file_path,
+ ResourceEntry* entry) {
+ FileError error = params.metadata->GetFilePath(local_id, drive_file_path);
+ if (error != FILE_ERROR_OK)
+ return error;
+ return CheckPreConditionForEnsureFileDownloaded(
+ params.metadata, params.cache, params.temporary_file_directory, local_id,
+ entry, cache_file_path, temp_download_file_path);
+}
+
+// Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
+// the given file path.
+FileError CheckPreConditionForEnsureFileDownloadedByPath(
+ const CheckPreconditionForEnsureFileDownloadedParams& params,
+ const base::FilePath& file_path,
+ base::FilePath* cache_file_path,
+ base::FilePath* temp_download_file_path,
+ ResourceEntry* entry) {
+ std::string local_id;
+ FileError error = params.metadata->GetIdByPath(file_path, &local_id);
+ if (error != FILE_ERROR_OK)
+ return error;
+ return CheckPreConditionForEnsureFileDownloaded(
+ params.metadata, params.cache, params.temporary_file_directory, local_id,
+ entry, cache_file_path, temp_download_file_path);
+}
+
+// Stores the downloaded file at |downloaded_file_path| into |cache|.
+// If succeeded, returns FILE_ERROR_OK with |cache_file_path| storing the
+// path to the cache file.
+// If failed, returns an error code with deleting |downloaded_file_path|.
+FileError UpdateLocalStateForDownloadFile(
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const ResourceEntry& entry_before_download,
+ google_apis::DriveApiErrorCode gdata_error,
+ const base::FilePath& downloaded_file_path,
+ ResourceEntry* entry_after_update,
+ base::FilePath* cache_file_path) {
+ DCHECK(cache);
+
+ // Downloaded file should be deleted on errors.
+ base::ScopedClosureRunner file_deleter(base::Bind(
+ base::IgnoreResult(&base::DeleteFile),
+ downloaded_file_path, false /* recursive */));
+
+ FileError error = GDataToFileError(gdata_error);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ const std::string& local_id = entry_before_download.local_id();
+
+ // Do not overwrite locally edited file with server side contents.
+ ResourceEntry entry;
+ error = metadata->GetResourceEntryById(local_id, &entry);
+ if (error != FILE_ERROR_OK)
+ return error;
+ if (entry.file_specific_info().cache_state().is_dirty())
+ return FILE_ERROR_IN_USE;
+
+ // Here the download is completed successfully, so store it into the cache.
+ error = cache->Store(local_id,
+ entry_before_download.file_specific_info().md5(),
+ downloaded_file_path,
+ internal::FileCache::FILE_OPERATION_MOVE);
+ if (error != FILE_ERROR_OK)
+ return error;
+ base::Closure unused_file_deleter_closure = file_deleter.Release();
+
+ error = metadata->GetResourceEntryById(local_id, entry_after_update);
+ if (error != FILE_ERROR_OK)
+ return error;
+
+ return cache->GetFile(local_id, cache_file_path);
+}
+
+} // namespace
+
+class DownloadOperation::DownloadParams {
+ public:
+ DownloadParams(
+ const GetFileContentInitializedCallback initialized_callback,
+ const google_apis::GetContentCallback get_content_callback,
+ const GetFileCallback completion_callback,
+ scoped_ptr<ResourceEntry> entry)
+ : initialized_callback_(initialized_callback),
+ get_content_callback_(get_content_callback),
+ completion_callback_(completion_callback),
+ entry_(entry.Pass()),
+ was_cancelled_(false),
+ weak_ptr_factory_(this) {
+ DCHECK(!completion_callback_.is_null());
+ DCHECK(entry_);
+ }
+
+ base::Closure GetCancelClosure() {
+ return base::Bind(&DownloadParams::Cancel, weak_ptr_factory_.GetWeakPtr());
+ }
+
+ void OnCacheFileFound(const base::FilePath& cache_file_path) {
+ if (!initialized_callback_.is_null()) {
+ initialized_callback_.Run(FILE_ERROR_OK, cache_file_path,
+ make_scoped_ptr(new ResourceEntry(*entry_)));
+ }
+ completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry_.Pass());
+ }
+
+ void OnStartDownloading(const base::Closure& cancel_download_closure) {
+ cancel_download_closure_ = cancel_download_closure;
+ if (initialized_callback_.is_null()) {
+ return;
+ }
+
+ DCHECK(entry_);
+ initialized_callback_.Run(FILE_ERROR_OK, base::FilePath(),
+ make_scoped_ptr(new ResourceEntry(*entry_)));
+ }
+
+ void OnError(FileError error) const {
+ completion_callback_.Run(
+ error, base::FilePath(), scoped_ptr<ResourceEntry>());
+ }
+
+ void OnDownloadCompleted(const base::FilePath& cache_file_path,
+ scoped_ptr<ResourceEntry> entry) const {
+ completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry.Pass());
+ }
+
+ const google_apis::GetContentCallback& get_content_callback() const {
+ return get_content_callback_;
+ }
+
+ const ResourceEntry& entry() const { return *entry_; }
+
+ bool was_cancelled() const { return was_cancelled_; }
+
+ private:
+ void Cancel() {
+ was_cancelled_ = true;
+ if (!cancel_download_closure_.is_null())
+ cancel_download_closure_.Run();
+ }
+
+ const GetFileContentInitializedCallback initialized_callback_;
+ const google_apis::GetContentCallback get_content_callback_;
+ const GetFileCallback completion_callback_;
+
+ scoped_ptr<ResourceEntry> entry_;
+ base::Closure cancel_download_closure_;
+ bool was_cancelled_;
+
+ base::WeakPtrFactory<DownloadParams> weak_ptr_factory_;
+ DISALLOW_COPY_AND_ASSIGN(DownloadParams);
+};
+
+DownloadOperation::DownloadOperation(
+ base::SequencedTaskRunner* blocking_task_runner,
+ OperationDelegate* delegate,
+ JobScheduler* scheduler,
+ internal::ResourceMetadata* metadata,
+ internal::FileCache* cache,
+ const base::FilePath& temporary_file_directory)
+ : blocking_task_runner_(blocking_task_runner),
+ delegate_(delegate),
+ scheduler_(scheduler),
+ metadata_(metadata),
+ cache_(cache),
+ temporary_file_directory_(temporary_file_directory),
+ weak_ptr_factory_(this) {
+}
+
+DownloadOperation::~DownloadOperation() {
+}
+
+base::Closure DownloadOperation::EnsureFileDownloadedByLocalId(
+ const std::string& local_id,
+ const ClientContext& context,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const GetFileCallback& completion_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!completion_callback.is_null());
+
+ CheckPreconditionForEnsureFileDownloadedParams params;
+ params.metadata = metadata_;
+ params.cache = cache_;
+ params.temporary_file_directory = temporary_file_directory_;
+ base::FilePath* drive_file_path = new base::FilePath;
+ base::FilePath* cache_file_path = new base::FilePath;
+ base::FilePath* temp_download_file_path = new base::FilePath;
+ ResourceEntry* entry = new ResourceEntry;
+ scoped_ptr<DownloadParams> download_params(new DownloadParams(
+ initialized_callback, get_content_callback, completion_callback,
+ make_scoped_ptr(entry)));
+ base::Closure cancel_closure = download_params->GetCancelClosure();
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&CheckPreConditionForEnsureFileDownloadedByLocalId,
+ params,
+ local_id,
+ drive_file_path,
+ cache_file_path,
+ temp_download_file_path,
+ entry),
+ base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&download_params),
+ context,
+ base::Owned(drive_file_path),
+ base::Owned(cache_file_path),
+ base::Owned(temp_download_file_path)));
+ return cancel_closure;
+}
+
+base::Closure DownloadOperation::EnsureFileDownloadedByPath(
+ const base::FilePath& file_path,
+ const ClientContext& context,
+ const GetFileContentInitializedCallback& initialized_callback,
+ const google_apis::GetContentCallback& get_content_callback,
+ const GetFileCallback& completion_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!completion_callback.is_null());
+
+ CheckPreconditionForEnsureFileDownloadedParams params;
+ params.metadata = metadata_;
+ params.cache = cache_;
+ params.temporary_file_directory = temporary_file_directory_;
+ base::FilePath* drive_file_path = new base::FilePath(file_path);
+ base::FilePath* cache_file_path = new base::FilePath;
+ base::FilePath* temp_download_file_path = new base::FilePath;
+ ResourceEntry* entry = new ResourceEntry;
+ scoped_ptr<DownloadParams> download_params(new DownloadParams(
+ initialized_callback, get_content_callback, completion_callback,
+ make_scoped_ptr(entry)));
+ base::Closure cancel_closure = download_params->GetCancelClosure();
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&CheckPreConditionForEnsureFileDownloadedByPath,
+ params,
+ file_path,
+ cache_file_path,
+ temp_download_file_path,
+ entry),
+ base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&download_params),
+ context,
+ base::Owned(drive_file_path),
+ base::Owned(cache_file_path),
+ base::Owned(temp_download_file_path)));
+ return cancel_closure;
+}
+
+void DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition(
+ scoped_ptr<DownloadParams> params,
+ const ClientContext& context,
+ base::FilePath* drive_file_path,
+ base::FilePath* cache_file_path,
+ base::FilePath* temp_download_file_path,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(params);
+ DCHECK(drive_file_path);
+ DCHECK(cache_file_path);
+
+ if (error != FILE_ERROR_OK) {
+ // During precondition check, an error is found.
+ params->OnError(error);
+ return;
+ }
+
+ if (!cache_file_path->empty()) {
+ // The cache file is found.
+ params->OnCacheFileFound(*cache_file_path);
+ return;
+ }
+
+ if (params->was_cancelled()) {
+ params->OnError(FILE_ERROR_ABORT);
+ return;
+ }
+
+ DCHECK(!params->entry().resource_id().empty());
+ DownloadParams* params_ptr = params.get();
+ JobID id = scheduler_->DownloadFile(
+ *drive_file_path,
+ params_ptr->entry().file_info().size(),
+ *temp_download_file_path,
+ params_ptr->entry().resource_id(),
+ context,
+ base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile,
+ weak_ptr_factory_.GetWeakPtr(),
+ *drive_file_path,
+ base::Passed(&params)),
+ params_ptr->get_content_callback());
+
+ // Notify via |initialized_callback| if necessary.
+ params_ptr->OnStartDownloading(
+ base::Bind(&DownloadOperation::CancelJob,
+ weak_ptr_factory_.GetWeakPtr(), id));
+}
+
+void DownloadOperation::EnsureFileDownloadedAfterDownloadFile(
+ const base::FilePath& drive_file_path,
+ scoped_ptr<DownloadParams> params,
+ google_apis::DriveApiErrorCode gdata_error,
+ const base::FilePath& downloaded_file_path) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DownloadParams* params_ptr = params.get();
+ ResourceEntry* entry_after_update = new ResourceEntry;
+ base::FilePath* cache_file_path = new base::FilePath;
+ base::PostTaskAndReplyWithResult(
+ blocking_task_runner_.get(),
+ FROM_HERE,
+ base::Bind(&UpdateLocalStateForDownloadFile,
+ metadata_,
+ cache_,
+ params_ptr->entry(),
+ gdata_error,
+ downloaded_file_path,
+ entry_after_update,
+ cache_file_path),
+ base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState,
+ weak_ptr_factory_.GetWeakPtr(),
+ drive_file_path,
+ base::Passed(&params),
+ base::Passed(make_scoped_ptr(entry_after_update)),
+ base::Owned(cache_file_path)));
+}
+
+void DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState(
+ const base::FilePath& file_path,
+ scoped_ptr<DownloadParams> params,
+ scoped_ptr<ResourceEntry> entry_after_update,
+ base::FilePath* cache_file_path,
+ FileError error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (error != FILE_ERROR_OK) {
+ params->OnError(error);
+ return;
+ }
+ DCHECK(!entry_after_update->file_info().is_directory());
+
+ FileChange changed_files;
+ changed_files.Update(file_path, FileChange::FILE_TYPE_FILE,
+ FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
+ // Storing to cache changes the "offline available" status, hence notify.
+ delegate_->OnFileChangedByOperation(changed_files);
+ params->OnDownloadCompleted(*cache_file_path, entry_after_update.Pass());
+}
+
+void DownloadOperation::CancelJob(JobID job_id) {
+ scheduler_->CancelJob(job_id);
+}
+
+} // namespace file_system
+} // namespace drive