diff options
Diffstat (limited to 'components/drive/file_system/download_operation.cc')
-rw-r--r-- | components/drive/file_system/download_operation.cc | 540 |
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(¶ms)), + 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(¶ms), + 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 |