| // Copyright (c) 2012 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/gdata/gdata_file_system.h" |
| |
| #include <set> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/file_util.h" |
| #include "base/json/json_file_value_serializer.h" |
| #include "base/message_loop.h" |
| #include "base/message_loop_proxy.h" |
| #include "base/metrics/histogram.h" |
| #include "base/platform_file.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "base/values.h" |
| #include "chrome/browser/chromeos/gdata/drive.pb.h" |
| #include "chrome/browser/chromeos/gdata/drive_api_parser.h" |
| #include "chrome/browser/chromeos/gdata/drive_files.h" |
| #include "chrome/browser/chromeos/gdata/drive_service_interface.h" |
| #include "chrome/browser/chromeos/gdata/drive_webapps_registry.h" |
| #include "chrome/browser/chromeos/gdata/gdata_download_observer.h" |
| #include "chrome/browser/chromeos/gdata/gdata_protocol_handler.h" |
| #include "chrome/browser/chromeos/gdata/gdata_system_service.h" |
| #include "chrome/browser/chromeos/gdata/gdata_uploader.h" |
| #include "chrome/browser/chromeos/gdata/gdata_util.h" |
| #include "chrome/browser/chromeos/gdata/task_util.h" |
| #include "chrome/browser/prefs/pref_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/chrome_notification_types.h" |
| #include "chrome/common/pref_names.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_details.h" |
| #include "net/base/mime_util.h" |
| |
| using content::BrowserThread; |
| |
| namespace gdata { |
| namespace { |
| |
| const char kMimeTypeJson[] = "application/json"; |
| const char kMimeTypeOctetStream[] = "application/octet-stream"; |
| |
| const char kEmptyFilePath[] = "/dev/null"; |
| |
| // GData update check interval (in seconds). |
| #ifndef NDEBUG |
| const int kGDataUpdateCheckIntervalInSec = 5; |
| #else |
| const int kGDataUpdateCheckIntervalInSec = 60; |
| #endif |
| |
| //================================ Helper functions ============================ |
| |
| // Runs GetFileCallback with pointers dereferenced. |
| // Used for PostTaskAndReply(). |
| void RunGetFileCallbackHelper(const GetFileCallback& callback, |
| DriveFileError* error, |
| FilePath* file_path, |
| std::string* mime_type, |
| DriveFileType* file_type) { |
| DCHECK(error); |
| DCHECK(file_path); |
| DCHECK(mime_type); |
| DCHECK(file_type); |
| |
| if (!callback.is_null()) |
| callback.Run(*error, *file_path, *mime_type, *file_type); |
| } |
| |
| // Ditto for FileOperationCallback |
| void RunFileOperationCallbackHelper( |
| const FileOperationCallback& callback, |
| DriveFileError* error) { |
| DCHECK(error); |
| |
| if (!callback.is_null()) |
| callback.Run(*error); |
| } |
| |
| // Callback for cache file operations invoked by AddUploadedFileOnUIThread. |
| void OnCacheUpdatedForAddUploadedFile( |
| const base::Closure& callback, |
| DriveFileError /* error */, |
| const std::string& /* resource_id */, |
| const std::string& /* md5 */) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (!callback.is_null()) |
| callback.Run(); |
| } |
| |
| // Helper function called upon completion of AddUploadFile invoked by |
| // OnTransferCompleted. |
| void OnAddUploadFileCompleted( |
| const FileOperationCallback& callback, |
| DriveFileError error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (!callback.is_null()) |
| callback.Run(error); |
| } |
| |
| // The class to wait for the initial load of root feed and runs the callback |
| // after the initialization. |
| class InitialLoadObserver : public GDataFileSystemInterface::Observer { |
| public: |
| InitialLoadObserver(GDataFileSystemInterface* file_system, |
| const base::Closure& callback) |
| : file_system_(file_system), callback_(callback) {} |
| |
| virtual void OnInitialLoadFinished() OVERRIDE { |
| if (!callback_.is_null()) |
| base::MessageLoopProxy::current()->PostTask(FROM_HERE, callback_); |
| file_system_->RemoveObserver(this); |
| base::MessageLoopProxy::current()->DeleteSoon(FROM_HERE, this); |
| } |
| |
| private: |
| GDataFileSystemInterface* file_system_; |
| base::Closure callback_; |
| }; |
| |
| // Gets the file size of |local_file|. |
| void GetLocalFileSizeOnBlockingPool(const FilePath& local_file, |
| DriveFileError* error, |
| int64* file_size) { |
| DCHECK(error); |
| DCHECK(file_size); |
| |
| *file_size = 0; |
| *error = file_util::GetFileSize(local_file, file_size) ? |
| DRIVE_FILE_OK : |
| DRIVE_FILE_ERROR_NOT_FOUND; |
| } |
| |
| // Gets the file size and the content type of |local_file|. |
| void GetLocalFileInfoOnBlockingPool( |
| const FilePath& local_file, |
| DriveFileError* error, |
| int64* file_size, |
| std::string* content_type) { |
| DCHECK(error); |
| DCHECK(file_size); |
| DCHECK(content_type); |
| |
| if (!net::GetMimeTypeFromExtension(local_file.Extension(), content_type)) |
| *content_type = kMimeTypeOctetStream; |
| |
| *file_size = 0; |
| *error = file_util::GetFileSize(local_file, file_size) ? |
| DRIVE_FILE_OK : |
| DRIVE_FILE_ERROR_NOT_FOUND; |
| } |
| |
| // Checks if a local file at |local_file_path| is a JSON file referencing a |
| // hosted document on blocking pool, and if so, gets the resource ID of the |
| // document. |
| void GetDocumentResourceIdOnBlockingPool( |
| const FilePath& local_file_path, |
| std::string* resource_id) { |
| DCHECK(resource_id); |
| |
| if (DocumentEntry::HasHostedDocumentExtension(local_file_path)) { |
| std::string error; |
| DictionaryValue* dict_value = NULL; |
| JSONFileValueSerializer serializer(local_file_path); |
| scoped_ptr<Value> value(serializer.Deserialize(NULL, &error)); |
| if (value.get() && value->GetAsDictionary(&dict_value)) |
| dict_value->GetString("resource_id", resource_id); |
| } |
| } |
| |
| // Creates a temporary JSON file representing a document with |edit_url| |
| // and |resource_id| under |document_dir| on blocking pool. |
| void CreateDocumentJsonFileOnBlockingPool( |
| const FilePath& document_dir, |
| const GURL& edit_url, |
| const std::string& resource_id, |
| DriveFileError* error, |
| FilePath* temp_file_path, |
| std::string* mime_type, |
| DriveFileType* file_type) { |
| DCHECK(error); |
| DCHECK(temp_file_path); |
| DCHECK(mime_type); |
| DCHECK(file_type); |
| |
| *error = DRIVE_FILE_ERROR_FAILED; |
| |
| if (file_util::CreateTemporaryFileInDir(document_dir, temp_file_path)) { |
| std::string document_content = base::StringPrintf( |
| "{\"url\": \"%s\", \"resource_id\": \"%s\"}", |
| edit_url.spec().c_str(), resource_id.c_str()); |
| int document_size = static_cast<int>(document_content.size()); |
| if (file_util::WriteFile(*temp_file_path, document_content.data(), |
| document_size) == document_size) { |
| *error = DRIVE_FILE_OK; |
| } |
| } |
| |
| *mime_type = kMimeTypeJson; |
| *file_type = HOSTED_DOCUMENT; |
| if (*error != DRIVE_FILE_OK) |
| temp_file_path->clear(); |
| } |
| |
| // Gets the information of the file at local path |path|. The information is |
| // filled in |file_info|, and if it fails |result| will be assigned false. |
| void GetFileInfoOnBlockingPool(const FilePath& path, |
| base::PlatformFileInfo* file_info, |
| bool* result) { |
| *result = file_util::GetFileInfo(path, file_info); |
| } |
| |
| // Copies a file from |src_file_path| to |dest_file_path| on the local |
| // file system using file_util::CopyFile. |error| is set to |
| // DRIVE_FILE_OK on success or DRIVE_FILE_ERROR_FAILED |
| // otherwise. |
| void CopyLocalFileOnBlockingPool( |
| const FilePath& src_file_path, |
| const FilePath& dest_file_path, |
| DriveFileError* error) { |
| DCHECK(error); |
| |
| *error = file_util::CopyFile(src_file_path, dest_file_path) ? |
| DRIVE_FILE_OK : DRIVE_FILE_ERROR_FAILED; |
| } |
| |
| // Callback for GetEntryByResourceIdAsync. |
| // Adds |entry| to |results|. Runs |callback| with |results| when |
| // |run_callback| is true. When |entry| is not present in our local file system |
| // snapshot, it is not added to |results|. Instead, |entry_skipped_callback| is |
| // called. |
| void AddEntryToSearchResults( |
| std::vector<SearchResultInfo>* results, |
| const SearchCallback& callback, |
| const base::Closure& entry_skipped_callback, |
| DriveFileError error, |
| bool run_callback, |
| const GURL& next_feed, |
| DriveEntry* entry) { |
| // If a result is not present in our local file system snapshot, invoke |
| // |entry_skipped_callback| and refreshes the snapshot with delta feed. |
| // For example, this may happen if the entry has recently been added to the |
| // drive (and we still haven't received its delta feed). |
| if (entry) { |
| const bool is_directory = entry->AsDriveDirectory() != NULL; |
| results->push_back(SearchResultInfo(entry->GetFilePath(), is_directory)); |
| } else { |
| if (!entry_skipped_callback.is_null()) |
| entry_skipped_callback.Run(); |
| } |
| |
| if (run_callback) { |
| scoped_ptr<std::vector<SearchResultInfo> > result_vec(results); |
| if (!callback.is_null()) |
| callback.Run(error, next_feed, result_vec.Pass()); |
| } |
| } |
| |
| // Helper function for binding |path| to GetEntryInfoWithFilePathCallback and |
| // create GetEntryInfoCallback. |
| void RunGetEntryInfoWithFilePathCallback( |
| const GetEntryInfoWithFilePathCallback& callback, |
| const FilePath& path, |
| DriveFileError error, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| if (!callback.is_null()) |
| callback.Run(error, path, entry_proto.Pass()); |
| } |
| |
| } // namespace |
| |
| // GDataFileSystem::CreateDirectoryParams struct implementation. |
| struct GDataFileSystem::CreateDirectoryParams { |
| CreateDirectoryParams(const FilePath& created_directory_path, |
| const FilePath& target_directory_path, |
| bool is_exclusive, |
| bool is_recursive, |
| const FileOperationCallback& callback); |
| ~CreateDirectoryParams(); |
| |
| const FilePath created_directory_path; |
| const FilePath target_directory_path; |
| const bool is_exclusive; |
| const bool is_recursive; |
| FileOperationCallback callback; |
| }; |
| |
| GDataFileSystem::CreateDirectoryParams::CreateDirectoryParams( |
| const FilePath& created_directory_path, |
| const FilePath& target_directory_path, |
| bool is_exclusive, |
| bool is_recursive, |
| const FileOperationCallback& callback) |
| : created_directory_path(created_directory_path), |
| target_directory_path(target_directory_path), |
| is_exclusive(is_exclusive), |
| is_recursive(is_recursive), |
| callback(callback) { |
| } |
| |
| GDataFileSystem::CreateDirectoryParams::~CreateDirectoryParams() { |
| } |
| |
| // GDataFileSystem::GetFileCompleteForOpenParams struct implementation. |
| struct GDataFileSystem::GetFileCompleteForOpenParams { |
| GetFileCompleteForOpenParams(const std::string& resource_id, |
| const std::string& md5); |
| ~GetFileCompleteForOpenParams(); |
| std::string resource_id; |
| std::string md5; |
| }; |
| |
| GDataFileSystem::GetFileCompleteForOpenParams::GetFileCompleteForOpenParams( |
| const std::string& resource_id, |
| const std::string& md5) |
| : resource_id(resource_id), |
| md5(md5) { |
| } |
| |
| GDataFileSystem::GetFileCompleteForOpenParams::~GetFileCompleteForOpenParams() { |
| } |
| |
| // GDataFileSystem::GetFileFromCacheParams struct implementation. |
| struct GDataFileSystem::GetFileFromCacheParams { |
| GetFileFromCacheParams( |
| const FilePath& virtual_file_path, |
| const FilePath& local_tmp_path, |
| const GURL& content_url, |
| const std::string& resource_id, |
| const std::string& md5, |
| const std::string& mime_type, |
| const GetFileCallback& get_file_callback, |
| const GetContentCallback& get_content_callback); |
| ~GetFileFromCacheParams(); |
| |
| FilePath virtual_file_path; |
| FilePath local_tmp_path; |
| GURL content_url; |
| std::string resource_id; |
| std::string md5; |
| std::string mime_type; |
| const GetFileCallback get_file_callback; |
| const GetContentCallback get_content_callback; |
| }; |
| |
| GDataFileSystem::GetFileFromCacheParams::GetFileFromCacheParams( |
| const FilePath& virtual_file_path, |
| const FilePath& local_tmp_path, |
| const GURL& content_url, |
| const std::string& resource_id, |
| const std::string& md5, |
| const std::string& mime_type, |
| const GetFileCallback& get_file_callback, |
| const GetContentCallback& get_content_callback) |
| : virtual_file_path(virtual_file_path), |
| local_tmp_path(local_tmp_path), |
| content_url(content_url), |
| resource_id(resource_id), |
| md5(md5), |
| mime_type(mime_type), |
| get_file_callback(get_file_callback), |
| get_content_callback(get_content_callback) { |
| } |
| |
| GDataFileSystem::GetFileFromCacheParams::~GetFileFromCacheParams() { |
| } |
| |
| // GDataFileSystem::StartFileUploadParams implementation. |
| struct GDataFileSystem::StartFileUploadParams { |
| StartFileUploadParams(const FilePath& in_local_file_path, |
| const FilePath& in_remote_file_path, |
| const FileOperationCallback& in_callback) |
| : local_file_path(in_local_file_path), |
| remote_file_path(in_remote_file_path), |
| callback(in_callback) {} |
| |
| const FilePath local_file_path; |
| const FilePath remote_file_path; |
| const FileOperationCallback callback; |
| }; |
| |
| // GDataFileSystem::AddUploadedFileParams implementation. |
| struct GDataFileSystem::AddUploadedFileParams { |
| AddUploadedFileParams(UploadMode upload_mode, |
| DriveDirectory* parent_dir, |
| scoped_ptr<DriveEntry> new_entry, |
| const FilePath& file_content_path, |
| DriveCache::FileOperationType cache_operation, |
| const base::Closure& callback) |
| : upload_mode(upload_mode), |
| parent_dir(parent_dir), |
| new_entry(new_entry.Pass()), |
| file_content_path(file_content_path), |
| cache_operation(cache_operation), |
| callback(callback) { |
| } |
| |
| UploadMode upload_mode; |
| DriveDirectory* parent_dir; |
| scoped_ptr<DriveEntry> new_entry; |
| FilePath file_content_path; |
| DriveCache::FileOperationType cache_operation; |
| base::Closure callback; |
| std::string resource_id; |
| std::string md5; |
| }; |
| |
| |
| // GDataFileSystem class implementation. |
| |
| GDataFileSystem::GDataFileSystem( |
| Profile* profile, |
| DriveCache* cache, |
| DriveServiceInterface* drive_service, |
| GDataUploaderInterface* uploader, |
| DriveWebAppsRegistryInterface* webapps_registry, |
| base::SequencedTaskRunner* blocking_task_runner) |
| : profile_(profile), |
| cache_(cache), |
| uploader_(uploader), |
| drive_service_(drive_service), |
| webapps_registry_(webapps_registry), |
| update_timer_(true /* retain_user_task */, true /* is_repeating */), |
| hide_hosted_docs_(false), |
| blocking_task_runner_(blocking_task_runner), |
| ui_weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
| ui_weak_ptr_(ui_weak_ptr_factory_.GetWeakPtr()) { |
| // Should be created from the file browser extension API on UI thread. |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| } |
| |
| void GDataFileSystem::Initialize() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| drive_service_->Initialize(profile_); |
| |
| resource_metadata_.reset(new DriveResourceMetadata); |
| feed_loader_.reset(new GDataWapiFeedLoader(resource_metadata_.get(), |
| drive_service_, |
| webapps_registry_, |
| cache_, |
| blocking_task_runner_)); |
| feed_loader_->AddObserver(this); |
| |
| PrefService* pref_service = profile_->GetPrefs(); |
| hide_hosted_docs_ = pref_service->GetBoolean(prefs::kDisableGDataHostedFiles); |
| |
| InitializePreferenceObserver(); |
| } |
| |
| void GDataFileSystem::CheckForUpdates() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| ContentOrigin initial_origin = resource_metadata_->origin(); |
| if (initial_origin == FROM_SERVER) { |
| resource_metadata_->set_origin(REFRESHING); |
| feed_loader_->ReloadFromServerIfNeeded( |
| initial_origin, |
| resource_metadata_->largest_changestamp(), |
| base::Bind(&GDataFileSystem::OnUpdateChecked, |
| ui_weak_ptr_, |
| initial_origin)); |
| } |
| } |
| |
| void GDataFileSystem::OnUpdateChecked(ContentOrigin initial_origin, |
| DriveFileError error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (error != DRIVE_FILE_OK) |
| resource_metadata_->set_origin(initial_origin); |
| } |
| |
| GDataFileSystem::~GDataFileSystem() { |
| // This should be called from UI thread, from GDataSystemService shutdown. |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| feed_loader_->RemoveObserver(this); |
| |
| // Cancel all the in-flight operations. |
| // This asynchronously cancels the URL fetch operations. |
| drive_service_->CancelAll(); |
| } |
| |
| void GDataFileSystem::AddObserver( |
| GDataFileSystemInterface::Observer* observer) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| observers_.AddObserver(observer); |
| } |
| |
| void GDataFileSystem::RemoveObserver( |
| GDataFileSystemInterface::Observer* observer) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| observers_.RemoveObserver(observer); |
| } |
| |
| void GDataFileSystem::StartUpdates() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DCHECK(!update_timer_.IsRunning()); |
| update_timer_.Start(FROM_HERE, |
| base::TimeDelta::FromSeconds( |
| kGDataUpdateCheckIntervalInSec), |
| base::Bind(&GDataFileSystem::CheckForUpdates, |
| ui_weak_ptr_)); |
| } |
| |
| void GDataFileSystem::StopUpdates() { |
| // If unmount request comes from filesystem side, this method may be called |
| // twice. First is just after unmounting on filesystem, second is after |
| // unmounting on filemanager on JS. In other words, if this is called from |
| // GDataSystemService::RemoveDriveMountPoint(), this will be called again from |
| // FileBrowserEventRouter::HandleRemoteUpdateRequestOnUIThread(). |
| // We choose to stopping updates asynchronous without waiting for filemanager, |
| // rather than waiting for completion of unmounting on filemanager. |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (update_timer_.IsRunning()) |
| update_timer_.Stop(); |
| } |
| |
| void GDataFileSystem::GetEntryInfoByResourceId( |
| const std::string& resource_id, |
| const GetEntryInfoWithFilePathCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK(!callback.is_null()); |
| |
| RunTaskOnUIThread( |
| base::Bind(&GDataFileSystem::GetEntryInfoByResourceIdOnUIThread, |
| ui_weak_ptr_, |
| resource_id, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::GetEntryInfoByResourceIdOnUIThread( |
| const std::string& resource_id, |
| const GetEntryInfoWithFilePathCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| resource_metadata_->GetEntryByResourceIdAsync(resource_id, |
| base::Bind(&GDataFileSystem::GetEntryInfoByEntryOnUIThread, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::GetEntryInfoByEntryOnUIThread( |
| const GetEntryInfoWithFilePathCallback& callback, |
| DriveEntry* entry) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (entry) { |
| scoped_ptr<DriveEntryProto> entry_proto(new DriveEntryProto); |
| entry->ToProtoFull(entry_proto.get()); |
| CheckLocalModificationAndRun( |
| entry_proto.Pass(), |
| base::Bind(&RunGetEntryInfoWithFilePathCallback, |
| callback, entry->GetFilePath())); |
| } else { |
| callback.Run(DRIVE_FILE_ERROR_NOT_FOUND, |
| FilePath(), |
| scoped_ptr<DriveEntryProto>()); |
| } |
| } |
| |
| void GDataFileSystem::LoadFeedIfNeeded(const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (resource_metadata_->origin() == INITIALIZING) { |
| // If root feed is not initialized but the initialization process has |
| // already started, add an observer to execute the remaining task after |
| // the end of the initialization. |
| AddObserver(new InitialLoadObserver(this, |
| base::Bind(callback, DRIVE_FILE_OK))); |
| return; |
| } else if (resource_metadata_->origin() == UNINITIALIZED) { |
| // Load root feed from this disk cache. Upon completion, kick off server |
| // fetching. |
| resource_metadata_->set_origin(INITIALIZING); |
| feed_loader_->LoadFromCache( |
| true, // should_load_from_server |
| base::Bind(&GDataFileSystem::NotifyInitialLoadFinishedAndRun, |
| ui_weak_ptr_, |
| callback)); |
| return; |
| } |
| |
| // The feed has already been loaded, so we have nothing to do, but post a |
| // task to the same thread, rather than calling it here, as |
| // LoadFeedIfNeeded() is asynchronous. |
| base::MessageLoopProxy::current()->PostTask( |
| FROM_HERE, |
| base::Bind(callback, DRIVE_FILE_OK)); |
| } |
| |
| void GDataFileSystem::TransferFileFromRemoteToLocal( |
| const FilePath& remote_src_file_path, |
| const FilePath& local_dest_file_path, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| GetFileByPath(remote_src_file_path, |
| base::Bind(&GDataFileSystem::OnGetFileCompleteForTransferFile, |
| ui_weak_ptr_, |
| local_dest_file_path, |
| callback), |
| GetContentCallback()); |
| } |
| |
| void GDataFileSystem::TransferFileFromLocalToRemote( |
| const FilePath& local_src_file_path, |
| const FilePath& remote_dest_file_path, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| // Make sure the destination directory exists. |
| resource_metadata_->GetEntryInfoByPath( |
| remote_dest_file_path.DirName(), |
| base::Bind( |
| &GDataFileSystem::TransferFileFromLocalToRemoteAfterGetEntryInfo, |
| ui_weak_ptr_, |
| local_src_file_path, |
| remote_dest_file_path, |
| callback)); |
| } |
| |
| void GDataFileSystem::TransferFileFromLocalToRemoteAfterGetEntryInfo( |
| const FilePath& local_src_file_path, |
| const FilePath& remote_dest_file_path, |
| const FileOperationCallback& callback, |
| DriveFileError error, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| DCHECK(entry_proto.get()); |
| if (!entry_proto->file_info().is_directory()) { |
| // The parent of |remote_dest_file_path| is not a directory. |
| callback.Run(DRIVE_FILE_ERROR_NOT_A_DIRECTORY); |
| return; |
| } |
| |
| std::string* resource_id = new std::string; |
| util::PostBlockingPoolSequencedTaskAndReply( |
| FROM_HERE, |
| blocking_task_runner_, |
| base::Bind(&GetDocumentResourceIdOnBlockingPool, |
| local_src_file_path, |
| resource_id), |
| base::Bind(&GDataFileSystem::TransferFileForResourceId, |
| ui_weak_ptr_, |
| local_src_file_path, |
| remote_dest_file_path, |
| callback, |
| base::Owned(resource_id))); |
| } |
| |
| void GDataFileSystem::TransferFileForResourceId( |
| const FilePath& local_file_path, |
| const FilePath& remote_dest_file_path, |
| const FileOperationCallback& callback, |
| std::string* resource_id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(resource_id); |
| DCHECK(!callback.is_null()); |
| |
| if (resource_id->empty()) { |
| // If |resource_id| is empty, upload the local file as a regular file. |
| TransferRegularFile(local_file_path, remote_dest_file_path, callback); |
| return; |
| } |
| |
| // Otherwise, copy the document on the server side and add the new copy |
| // to the destination directory (collection). |
| CopyDocumentToDirectory( |
| remote_dest_file_path.DirName(), |
| *resource_id, |
| // Drop the document extension, which should not be |
| // in the document title. |
| remote_dest_file_path.BaseName().RemoveExtension().value(), |
| callback); |
| } |
| |
| void GDataFileSystem::TransferRegularFile( |
| const FilePath& local_file_path, |
| const FilePath& remote_dest_file_path, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DriveFileError* error = |
| new DriveFileError(DRIVE_FILE_OK); |
| int64* file_size = new int64; |
| std::string* content_type = new std::string; |
| util::PostBlockingPoolSequencedTaskAndReply( |
| FROM_HERE, |
| blocking_task_runner_, |
| base::Bind(&GetLocalFileInfoOnBlockingPool, |
| local_file_path, |
| error, |
| file_size, |
| content_type), |
| base::Bind(&GDataFileSystem::StartFileUploadOnUIThread, |
| ui_weak_ptr_, |
| StartFileUploadParams(local_file_path, |
| remote_dest_file_path, |
| callback), |
| base::Owned(error), |
| base::Owned(file_size), |
| base::Owned(content_type))); |
| } |
| |
| void GDataFileSystem::StartFileUploadOnUIThread( |
| const StartFileUploadParams& params, |
| DriveFileError* error, |
| int64* file_size, |
| std::string* content_type) { |
| // This method needs to run on the UI thread as required by |
| // GDataUploader::UploadNewFile(). |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(error); |
| DCHECK(file_size); |
| DCHECK(content_type); |
| |
| if (*error != DRIVE_FILE_OK) { |
| if (!params.callback.is_null()) |
| params.callback.Run(*error); |
| |
| return; |
| } |
| |
| // Make sure the destination directory exists. |
| resource_metadata_->GetEntryInfoByPath( |
| params.remote_file_path.DirName(), |
| base::Bind( |
| &GDataFileSystem::StartFileUploadOnUIThreadAfterGetEntryInfo, |
| ui_weak_ptr_, |
| params, |
| *file_size, |
| *content_type)); |
| } |
| |
| void GDataFileSystem::StartFileUploadOnUIThreadAfterGetEntryInfo( |
| const StartFileUploadParams& params, |
| int64 file_size, |
| std::string content_type, |
| DriveFileError error, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (entry_proto.get() && !entry_proto->file_info().is_directory()) |
| error = DRIVE_FILE_ERROR_NOT_A_DIRECTORY; |
| |
| if (error != DRIVE_FILE_OK) { |
| if (!params.callback.is_null()) |
| params.callback.Run(error); |
| return; |
| } |
| DCHECK(entry_proto.get()); |
| |
| // Fill in values of UploadFileInfo. |
| scoped_ptr<UploadFileInfo> upload_file_info(new UploadFileInfo); |
| upload_file_info->file_path = params.local_file_path; |
| upload_file_info->file_size = file_size; |
| upload_file_info->gdata_path = params.remote_file_path; |
| // Use the file name as the title. |
| upload_file_info->title = params.remote_file_path.BaseName().value(); |
| upload_file_info->content_length = file_size; |
| upload_file_info->all_bytes_present = true; |
| upload_file_info->content_type = content_type; |
| upload_file_info->initial_upload_location = GURL(entry_proto->upload_url()); |
| |
| upload_file_info->completion_callback = |
| base::Bind(&GDataFileSystem::OnTransferCompleted, |
| ui_weak_ptr_, |
| params.callback); |
| |
| uploader_->UploadNewFile(upload_file_info.Pass()); |
| } |
| |
| void GDataFileSystem::OnTransferCompleted( |
| const FileOperationCallback& callback, |
| DriveFileError error, |
| scoped_ptr<UploadFileInfo> upload_file_info) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(upload_file_info.get()); |
| |
| if (error == DRIVE_FILE_OK && upload_file_info->entry.get()) { |
| AddUploadedFile(UPLOAD_NEW_FILE, |
| upload_file_info->gdata_path.DirName(), |
| upload_file_info->entry.Pass(), |
| upload_file_info->file_path, |
| DriveCache::FILE_OPERATION_COPY, |
| base::Bind(&OnAddUploadFileCompleted, callback, error)); |
| } else if (!callback.is_null()) { |
| callback.Run(error); |
| } |
| } |
| |
| void GDataFileSystem::Copy(const FilePath& src_file_path, |
| const FilePath& dest_file_path, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK(!callback.is_null()); |
| |
| RunTaskOnUIThread(base::Bind(&GDataFileSystem::CopyOnUIThread, |
| ui_weak_ptr_, |
| src_file_path, |
| dest_file_path, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::CopyOnUIThread(const FilePath& src_file_path, |
| const FilePath& dest_file_path, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| resource_metadata_->GetEntryInfoPairByPaths( |
| src_file_path, |
| dest_file_path.DirName(), |
| base::Bind(&GDataFileSystem::CopyOnUIThreadAfterGetEntryInfoPair, |
| ui_weak_ptr_, |
| dest_file_path, |
| callback)); |
| } |
| |
| void GDataFileSystem::CopyOnUIThreadAfterGetEntryInfoPair( |
| const FilePath& dest_file_path, |
| const FileOperationCallback& callback, |
| scoped_ptr<EntryInfoPairResult> result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| DCHECK(result.get()); |
| |
| if (result->first.error != DRIVE_FILE_OK) { |
| callback.Run(result->first.error); |
| return; |
| } else if (result->second.error != DRIVE_FILE_OK) { |
| callback.Run(result->second.error); |
| return; |
| } |
| |
| scoped_ptr<DriveEntryProto> src_file_proto = result->first.proto.Pass(); |
| scoped_ptr<DriveEntryProto> dest_parent_proto = result->second.proto.Pass(); |
| |
| if (!dest_parent_proto->file_info().is_directory()) { |
| callback.Run(DRIVE_FILE_ERROR_NOT_A_DIRECTORY); |
| return; |
| } else if (src_file_proto->file_info().is_directory()) { |
| // TODO(kochi): Implement copy for directories. In the interim, |
| // we handle recursive directory copy in the file manager. |
| // crbug.com/141596 |
| callback.Run(DRIVE_FILE_ERROR_INVALID_OPERATION); |
| return; |
| } |
| |
| if (src_file_proto->file_specific_info().is_hosted_document()) { |
| CopyDocumentToDirectory(dest_file_path.DirName(), |
| src_file_proto->resource_id(), |
| // Drop the document extension, which should not be |
| // in the document title. |
| dest_file_path.BaseName().RemoveExtension().value(), |
| callback); |
| return; |
| } |
| |
| // TODO(kochi): Reimplement this once the server API supports |
| // copying of regular files directly on the server side. crbug.com/138273 |
| const FilePath& src_file_path = result->first.path; |
| GetFileByPath(src_file_path, |
| base::Bind(&GDataFileSystem::OnGetFileCompleteForCopy, |
| ui_weak_ptr_, |
| dest_file_path, |
| callback), |
| GetContentCallback()); |
| } |
| |
| void GDataFileSystem::OnGetFileCompleteForCopy( |
| const FilePath& remote_dest_file_path, |
| const FileOperationCallback& callback, |
| DriveFileError error, |
| const FilePath& local_file_path, |
| const std::string& unused_mime_type, |
| DriveFileType file_type) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| // This callback is only triggered for a regular file via Copy(). |
| DCHECK_EQ(REGULAR_FILE, file_type); |
| TransferRegularFile(local_file_path, remote_dest_file_path, callback); |
| } |
| |
| void GDataFileSystem::OnGetFileCompleteForTransferFile( |
| const FilePath& local_dest_file_path, |
| const FileOperationCallback& callback, |
| DriveFileError error, |
| const FilePath& local_file_path, |
| const std::string& unused_mime_type, |
| DriveFileType file_type) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| // GetFileByPath downloads the file from gdata to a local cache, which is then |
| // copied to the actual destination path on the local file system using |
| // CopyLocalFileOnBlockingPool. |
| DriveFileError* copy_file_error = |
| new DriveFileError(DRIVE_FILE_OK); |
| util::PostBlockingPoolSequencedTaskAndReply( |
| FROM_HERE, |
| blocking_task_runner_, |
| base::Bind(&CopyLocalFileOnBlockingPool, |
| local_file_path, |
| local_dest_file_path, |
| copy_file_error), |
| base::Bind(&RunFileOperationCallbackHelper, |
| callback, |
| base::Owned(copy_file_error))); |
| } |
| |
| void GDataFileSystem::CopyDocumentToDirectory( |
| const FilePath& dir_path, |
| const std::string& resource_id, |
| const FilePath::StringType& new_name, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| drive_service_->CopyDocument(resource_id, new_name, |
| base::Bind(&GDataFileSystem::OnCopyDocumentCompleted, |
| ui_weak_ptr_, |
| dir_path, |
| callback)); |
| } |
| |
| void GDataFileSystem::Rename(const FilePath& file_path, |
| const FilePath::StringType& new_name, |
| const FileMoveCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| // It is a no-op if the file is renamed to the same name. |
| if (file_path.BaseName().value() == new_name) { |
| callback.Run(DRIVE_FILE_OK, file_path); |
| return; |
| } |
| |
| // Get the edit URL of an entry at |file_path|. |
| resource_metadata_->GetEntryInfoByPath( |
| file_path, |
| base::Bind( |
| &GDataFileSystem::RenameAfterGetEntryInfo, |
| ui_weak_ptr_, |
| file_path, |
| new_name, |
| callback)); |
| } |
| |
| void GDataFileSystem::RenameAfterGetEntryInfo( |
| const FilePath& file_path, |
| const FilePath::StringType& new_name, |
| const FileMoveCallback& callback, |
| DriveFileError error, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (error != DRIVE_FILE_OK) { |
| if (!callback.is_null()) |
| callback.Run(error, file_path); |
| return; |
| } |
| DCHECK(entry_proto.get()); |
| |
| // Drop the .g<something> extension from |new_name| if the file being |
| // renamed is a hosted document and |new_name| has the same .g<something> |
| // extension as the file. |
| FilePath::StringType file_name = new_name; |
| if (entry_proto->has_file_specific_info() && |
| entry_proto->file_specific_info().is_hosted_document()) { |
| FilePath new_file(file_name); |
| if (new_file.Extension() == |
| entry_proto->file_specific_info().document_extension()) { |
| file_name = new_file.RemoveExtension().value(); |
| } |
| } |
| |
| drive_service_->RenameResource( |
| GURL(entry_proto->edit_url()), |
| file_name, |
| base::Bind(&GDataFileSystem::RenameEntryLocally, |
| ui_weak_ptr_, |
| file_path, |
| file_name, |
| callback)); |
| } |
| |
| void GDataFileSystem::Move(const FilePath& src_file_path, |
| const FilePath& dest_file_path, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK(!callback.is_null()); |
| |
| RunTaskOnUIThread(base::Bind(&GDataFileSystem::MoveOnUIThread, |
| ui_weak_ptr_, |
| src_file_path, |
| dest_file_path, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::MoveOnUIThread(const FilePath& src_file_path, |
| const FilePath& dest_file_path, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| resource_metadata_->GetEntryInfoPairByPaths( |
| src_file_path, |
| dest_file_path.DirName(), |
| base::Bind(&GDataFileSystem::MoveOnUIThreadAfterGetEntryInfoPair, |
| ui_weak_ptr_, |
| dest_file_path, |
| callback)); |
| } |
| |
| void GDataFileSystem::MoveOnUIThreadAfterGetEntryInfoPair( |
| const FilePath& dest_file_path, |
| const FileOperationCallback& callback, |
| scoped_ptr<EntryInfoPairResult> result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| DCHECK(result.get()); |
| |
| if (result->first.error != DRIVE_FILE_OK) { |
| callback.Run(result->first.error); |
| return; |
| } else if (result->second.error != DRIVE_FILE_OK) { |
| callback.Run(result->second.error); |
| return; |
| } |
| |
| scoped_ptr<DriveEntryProto> dest_parent_proto = result->second.proto.Pass(); |
| if (!dest_parent_proto->file_info().is_directory()) { |
| callback.Run(DRIVE_FILE_ERROR_NOT_A_DIRECTORY); |
| return; |
| } |
| |
| // If the file/directory is moved to the same directory, just rename it. |
| const FilePath& src_file_path = result->first.path; |
| const FilePath& dest_parent_path = result->second.path; |
| if (src_file_path.DirName() == dest_parent_path) { |
| FileMoveCallback final_file_path_update_callback = |
| base::Bind(&GDataFileSystem::OnFilePathUpdated, |
| ui_weak_ptr_, |
| callback); |
| |
| Rename(src_file_path, dest_file_path.BaseName().value(), |
| final_file_path_update_callback); |
| return; |
| } |
| |
| // Otherwise, the move operation involves three steps: |
| // 1. Renames the file at |src_file_path| to basename(|dest_file_path|) |
| // within the same directory. The rename operation is a no-op if |
| // basename(|src_file_path|) equals to basename(|dest_file_path|). |
| // 2. Removes the file from its parent directory (the file is not deleted), |
| // which effectively moves the file to the root directory. |
| // 3. Adds the file to the parent directory of |dest_file_path|, which |
| // effectively moves the file from the root directory to the parent |
| // directory of |dest_file_path|. |
| const FileMoveCallback add_file_to_directory_callback = |
| base::Bind(&GDataFileSystem::MoveEntryFromRootDirectory, |
| ui_weak_ptr_, |
| dest_file_path.DirName(), |
| callback); |
| |
| const FileMoveCallback remove_file_from_directory_callback = |
| base::Bind(&GDataFileSystem::RemoveEntryFromNonRootDirectory, |
| ui_weak_ptr_, |
| add_file_to_directory_callback); |
| |
| Rename(src_file_path, dest_file_path.BaseName().value(), |
| remove_file_from_directory_callback); |
| } |
| |
| void GDataFileSystem::MoveEntryFromRootDirectory( |
| const FilePath& dir_path, |
| const FileOperationCallback& callback, |
| DriveFileError error, |
| const FilePath& file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| DCHECK_EQ(kDriveRootDirectory, file_path.DirName().value()); |
| |
| // Return if there is an error or |dir_path| is the root directory. |
| if (error != DRIVE_FILE_OK || dir_path == FilePath(kDriveRootDirectory)) { |
| callback.Run(error); |
| return; |
| } |
| |
| resource_metadata_->GetEntryInfoPairByPaths( |
| file_path, |
| dir_path, |
| base::Bind( |
| &GDataFileSystem::MoveEntryFromRootDirectoryAfterGetEntryInfoPair, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::MoveEntryFromRootDirectoryAfterGetEntryInfoPair( |
| const FileOperationCallback& callback, |
| scoped_ptr<EntryInfoPairResult> result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| DCHECK(result.get()); |
| |
| if (result->first.error != DRIVE_FILE_OK) { |
| callback.Run(result->first.error); |
| return; |
| } else if (result->second.error != DRIVE_FILE_OK) { |
| callback.Run(result->second.error); |
| return; |
| } |
| |
| scoped_ptr<DriveEntryProto> src_proto = result->first.proto.Pass(); |
| scoped_ptr<DriveEntryProto> dir_proto = result->second.proto.Pass(); |
| |
| if (!dir_proto->file_info().is_directory()) { |
| callback.Run(DRIVE_FILE_ERROR_NOT_A_DIRECTORY); |
| return; |
| } |
| |
| const FilePath& file_path = result->first.path; |
| const FilePath& dir_path = result->second.path; |
| drive_service_->AddResourceToDirectory( |
| GURL(dir_proto->content_url()), |
| GURL(src_proto->edit_url()), |
| base::Bind(&GDataFileSystem::OnMoveEntryFromRootDirectoryCompleted, |
| ui_weak_ptr_, |
| callback, |
| file_path, |
| dir_path)); |
| } |
| |
| void GDataFileSystem::RemoveEntryFromNonRootDirectory( |
| const FileMoveCallback& callback, |
| DriveFileError error, |
| const FilePath& file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| const FilePath dir_path = file_path.DirName(); |
| // Return if there is an error or |dir_path| is the root directory. |
| if (error != DRIVE_FILE_OK || dir_path == FilePath(kDriveRootDirectory)) { |
| callback.Run(error, file_path); |
| return; |
| } |
| |
| resource_metadata_->GetEntryInfoPairByPaths( |
| file_path, |
| dir_path, |
| base::Bind( |
| &GDataFileSystem::RemoveEntryFromNonRootDirectoryAfterEntryInfoPair, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::RemoveEntryFromNonRootDirectoryAfterEntryInfoPair( |
| const FileMoveCallback& callback, |
| scoped_ptr<EntryInfoPairResult> result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| DCHECK(result.get()); |
| |
| const FilePath& file_path = result->first.path; |
| const FilePath& dir_path = result->second.path; |
| if (result->first.error != DRIVE_FILE_OK) { |
| callback.Run(result->first.error, file_path); |
| return; |
| } else if (result->second.error != DRIVE_FILE_OK) { |
| callback.Run(result->second.error, file_path); |
| return; |
| } |
| |
| scoped_ptr<DriveEntryProto> entry_proto = result->first.proto.Pass(); |
| scoped_ptr<DriveEntryProto> dir_proto = result->second.proto.Pass(); |
| |
| if (!dir_proto->file_info().is_directory()) { |
| callback.Run(DRIVE_FILE_ERROR_NOT_A_DIRECTORY, file_path); |
| return; |
| } |
| |
| drive_service_->RemoveResourceFromDirectory( |
| GURL(dir_proto->content_url()), |
| GURL(entry_proto->edit_url()), |
| entry_proto->resource_id(), |
| base::Bind(&GDataFileSystem::MoveEntryToRootDirectoryLocally, |
| ui_weak_ptr_, |
| callback, |
| file_path, |
| dir_path)); |
| } |
| |
| void GDataFileSystem::Remove(const FilePath& file_path, |
| bool is_recursive, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| RunTaskOnUIThread(base::Bind(&GDataFileSystem::RemoveOnUIThread, |
| ui_weak_ptr_, |
| file_path, |
| is_recursive, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::RemoveOnUIThread( |
| const FilePath& file_path, |
| bool is_recursive, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Get the edit URL of an entry at |file_path|. |
| resource_metadata_->GetEntryInfoByPath( |
| file_path, |
| base::Bind( |
| &GDataFileSystem::RemoveOnUIThreadAfterGetEntryInfo, |
| ui_weak_ptr_, |
| file_path, |
| is_recursive, |
| callback)); |
| } |
| |
| void GDataFileSystem::RemoveOnUIThreadAfterGetEntryInfo( |
| const FilePath& file_path, |
| bool /* is_recursive */, |
| const FileOperationCallback& callback, |
| DriveFileError error, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (error != DRIVE_FILE_OK) { |
| if (!callback.is_null()) { |
| base::MessageLoopProxy::current()->PostTask( |
| FROM_HERE, base::Bind(callback, error)); |
| } |
| return; |
| } |
| |
| DCHECK(entry_proto.get()); |
| drive_service_->DeleteDocument( |
| GURL(entry_proto->edit_url()), |
| base::Bind(&GDataFileSystem::OnRemovedDocument, |
| ui_weak_ptr_, |
| callback, |
| file_path)); |
| } |
| |
| void GDataFileSystem::CreateDirectory( |
| const FilePath& directory_path, |
| bool is_exclusive, |
| bool is_recursive, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| RunTaskOnUIThread(base::Bind(&GDataFileSystem::CreateDirectoryOnUIThread, |
| ui_weak_ptr_, |
| directory_path, |
| is_exclusive, |
| is_recursive, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::CreateDirectoryOnUIThread( |
| const FilePath& directory_path, |
| bool is_exclusive, |
| bool is_recursive, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| FilePath last_parent_dir_path; |
| FilePath first_missing_path; |
| GURL last_parent_dir_url; |
| FindMissingDirectoryResult result = |
| FindFirstMissingParentDirectory(directory_path, |
| &last_parent_dir_url, |
| &first_missing_path); |
| switch (result) { |
| case FOUND_INVALID: { |
| if (!callback.is_null()) { |
| MessageLoop::current()->PostTask(FROM_HERE, |
| base::Bind(callback, DRIVE_FILE_ERROR_NOT_FOUND)); |
| } |
| |
| return; |
| } |
| case DIRECTORY_ALREADY_PRESENT: { |
| if (!callback.is_null()) { |
| MessageLoop::current()->PostTask(FROM_HERE, |
| base::Bind(callback, |
| is_exclusive ? DRIVE_FILE_ERROR_EXISTS : |
| DRIVE_FILE_OK)); |
| } |
| |
| return; |
| } |
| case FOUND_MISSING: { |
| // There is a missing folder to be created here, move on with the rest of |
| // this function. |
| break; |
| } |
| default: { |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| // Do we have a parent directory here as well? We can't then create target |
| // directory if this is not a recursive operation. |
| if (directory_path != first_missing_path && !is_recursive) { |
| if (!callback.is_null()) { |
| MessageLoop::current()->PostTask(FROM_HERE, |
| base::Bind(callback, DRIVE_FILE_ERROR_NOT_FOUND)); |
| } |
| return; |
| } |
| |
| drive_service_->CreateDirectory( |
| last_parent_dir_url, |
| first_missing_path.BaseName().value(), |
| base::Bind(&GDataFileSystem::OnCreateDirectoryCompleted, |
| ui_weak_ptr_, |
| CreateDirectoryParams( |
| first_missing_path, |
| directory_path, |
| is_exclusive, |
| is_recursive, |
| callback))); |
| } |
| |
| void GDataFileSystem::CreateFile(const FilePath& file_path, |
| bool is_exclusive, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK(!callback.is_null()); |
| |
| RunTaskOnUIThread(base::Bind(&GDataFileSystem::CreateFileOnUIThread, |
| ui_weak_ptr_, |
| file_path, |
| is_exclusive, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::CreateFileOnUIThread( |
| const FilePath& file_path, |
| bool is_exclusive, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| // First, checks the existence of a file at |file_path|. |
| resource_metadata_->GetEntryInfoByPath( |
| file_path, |
| base::Bind(&GDataFileSystem::OnGetEntryInfoForCreateFile, |
| ui_weak_ptr_, |
| file_path, |
| is_exclusive, |
| callback)); |
| } |
| |
| void GDataFileSystem::OnGetEntryInfoForCreateFile( |
| const FilePath& file_path, |
| bool is_exclusive, |
| const FileOperationCallback& callback, |
| DriveFileError result, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| // The |file_path| is invalid. It is an error. |
| if (result != DRIVE_FILE_ERROR_NOT_FOUND && |
| result != DRIVE_FILE_OK) { |
| callback.Run(result); |
| return; |
| } |
| |
| // An entry already exists at |file_path|. |
| if (result == DRIVE_FILE_OK) { |
| DCHECK(entry_proto.get()); |
| // If an exclusive mode is requested, or the entry is not a regular file, |
| // it is an error. |
| if (is_exclusive || |
| entry_proto->file_info().is_directory() || |
| entry_proto->file_specific_info().is_hosted_document()) { |
| callback.Run(DRIVE_FILE_ERROR_EXISTS); |
| return; |
| } |
| |
| // Otherwise nothing more to do. Succeeded. |
| callback.Run(DRIVE_FILE_OK); |
| return; |
| } |
| |
| // No entry found at |file_path|. Let's create a brand new file. |
| // For now, it is implemented by uploading an empty file (/dev/null). |
| // TODO(kinaba): http://crbug.com/135143. Implement in a nicer way. |
| TransferRegularFile(FilePath(kEmptyFilePath), file_path, callback); |
| } |
| |
| void GDataFileSystem::GetFileByPath( |
| const FilePath& file_path, |
| const GetFileCallback& get_file_callback, |
| const GetContentCallback& get_content_callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| RunTaskOnUIThread( |
| base::Bind(&GDataFileSystem::GetFileByPathOnUIThread, |
| ui_weak_ptr_, |
| file_path, |
| CreateRelayCallback(get_file_callback), |
| CreateRelayCallback(get_content_callback))); |
| } |
| |
| void GDataFileSystem::GetFileByPathOnUIThread( |
| const FilePath& file_path, |
| const GetFileCallback& get_file_callback, |
| const GetContentCallback& get_content_callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| resource_metadata_->GetEntryInfoByPath( |
| file_path, |
| base::Bind(&GDataFileSystem::OnGetEntryInfoCompleteForGetFileByPath, |
| ui_weak_ptr_, |
| file_path, |
| CreateRelayCallback(get_file_callback), |
| CreateRelayCallback(get_content_callback))); |
| } |
| |
| void GDataFileSystem::OnGetEntryInfoCompleteForGetFileByPath( |
| const FilePath& file_path, |
| const GetFileCallback& get_file_callback, |
| const GetContentCallback& get_content_callback, |
| DriveFileError error, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // If |error| == PLATFORM_FILE_OK then |entry_proto| must be valid. |
| DCHECK(error != DRIVE_FILE_OK || |
| (entry_proto.get() && !entry_proto->resource_id().empty())); |
| GetResolvedFileByPath(file_path, |
| get_file_callback, |
| get_content_callback, |
| error, |
| entry_proto.get()); |
| } |
| |
| void GDataFileSystem::GetResolvedFileByPath( |
| const FilePath& file_path, |
| const GetFileCallback& get_file_callback, |
| const GetContentCallback& get_content_callback, |
| DriveFileError error, |
| const DriveEntryProto* entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (entry_proto && !entry_proto->has_file_specific_info()) |
| error = DRIVE_FILE_ERROR_NOT_FOUND; |
| |
| if (error != DRIVE_FILE_OK) { |
| if (!get_file_callback.is_null()) { |
| MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(get_file_callback, |
| DRIVE_FILE_ERROR_NOT_FOUND, |
| FilePath(), |
| std::string(), |
| REGULAR_FILE)); |
| } |
| return; |
| } |
| |
| // 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_proto->file_specific_info().is_hosted_document()) { |
| DriveFileError* error = |
| new DriveFileError(DRIVE_FILE_OK); |
| FilePath* temp_file_path = new FilePath; |
| std::string* mime_type = new std::string; |
| DriveFileType* file_type = new DriveFileType(REGULAR_FILE); |
| util::PostBlockingPoolSequencedTaskAndReply( |
| FROM_HERE, |
| blocking_task_runner_, |
| base::Bind(&CreateDocumentJsonFileOnBlockingPool, |
| cache_->GetCacheDirectoryPath( |
| DriveCache::CACHE_TYPE_TMP_DOCUMENTS), |
| GURL(entry_proto->file_specific_info().alternate_url()), |
| entry_proto->resource_id(), |
| error, |
| temp_file_path, |
| mime_type, |
| file_type), |
| base::Bind(&RunGetFileCallbackHelper, |
| get_file_callback, |
| base::Owned(error), |
| base::Owned(temp_file_path), |
| base::Owned(mime_type), |
| base::Owned(file_type))); |
| return; |
| } |
| |
| // Returns absolute path of the file if it were cached or to be cached. |
| FilePath local_tmp_path = cache_->GetCacheFilePath( |
| entry_proto->resource_id(), |
| entry_proto->file_specific_info().file_md5(), |
| DriveCache::CACHE_TYPE_TMP, |
| DriveCache::CACHED_FILE_FROM_SERVER); |
| cache_->GetFileOnUIThread( |
| entry_proto->resource_id(), |
| entry_proto->file_specific_info().file_md5(), |
| base::Bind( |
| &GDataFileSystem::OnGetFileFromCache, |
| ui_weak_ptr_, |
| GetFileFromCacheParams( |
| file_path, |
| local_tmp_path, |
| GURL(entry_proto->content_url()), |
| entry_proto->resource_id(), |
| entry_proto->file_specific_info().file_md5(), |
| entry_proto->file_specific_info().content_mime_type(), |
| get_file_callback, |
| get_content_callback))); |
| } |
| |
| void GDataFileSystem::GetFileByResourceId( |
| const std::string& resource_id, |
| const GetFileCallback& get_file_callback, |
| const GetContentCallback& get_content_callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| RunTaskOnUIThread( |
| base::Bind(&GDataFileSystem::GetFileByResourceIdOnUIThread, |
| ui_weak_ptr_, |
| resource_id, |
| CreateRelayCallback(get_file_callback), |
| CreateRelayCallback(get_content_callback))); |
| } |
| |
| void GDataFileSystem::GetFileByResourceIdOnUIThread( |
| const std::string& resource_id, |
| const GetFileCallback& get_file_callback, |
| const GetContentCallback& get_content_callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| resource_metadata_->GetEntryByResourceIdAsync(resource_id, |
| base::Bind(&GDataFileSystem::GetFileByEntryOnUIThread, |
| ui_weak_ptr_, |
| get_file_callback, |
| get_content_callback)); |
| } |
| |
| void GDataFileSystem::GetFileByEntryOnUIThread( |
| const GetFileCallback& get_file_callback, |
| const GetContentCallback& get_content_callback, |
| DriveEntry* entry) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| FilePath file_path; |
| if (entry) { |
| DriveFile* file = entry->AsDriveFile(); |
| if (file) |
| file_path = file->GetFilePath(); |
| } |
| |
| // Report an error immediately if the file for the resource ID is not |
| // found. |
| if (file_path.empty()) { |
| if (!get_file_callback.is_null()) { |
| base::MessageLoopProxy::current()->PostTask( |
| FROM_HERE, |
| base::Bind(get_file_callback, |
| DRIVE_FILE_ERROR_NOT_FOUND, |
| FilePath(), |
| std::string(), |
| REGULAR_FILE)); |
| } |
| return; |
| } |
| |
| GetFileByPath(file_path, get_file_callback, get_content_callback); |
| } |
| |
| void GDataFileSystem::OnGetFileFromCache(const GetFileFromCacheParams& params, |
| DriveFileError error, |
| const std::string& resource_id, |
| const std::string& md5, |
| const FilePath& cache_file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Have we found the file in cache? If so, return it back to the caller. |
| if (error == DRIVE_FILE_OK) { |
| if (!params.get_file_callback.is_null()) { |
| params.get_file_callback.Run(error, |
| cache_file_path, |
| params.mime_type, |
| REGULAR_FILE); |
| } |
| return; |
| } |
| |
| // If cache file is not found, try to download the file from the server |
| // instead. This logic is rather complicated but here's how this works: |
| // |
| // Retrieve fresh file metadata from server. We will extract file size and |
| // content url from there (we want to make sure used content url is not |
| // stale). |
| // |
| // Check if we have enough space, based on the expected file size. |
| // - if we don't have enough space, try to free up the disk space |
| // - if we still don't have enough space, return "no space" error |
| // - if we have enough space, start downloading the file from the server |
| drive_service_->GetDocumentEntry( |
| resource_id, |
| base::Bind(&GDataFileSystem::OnGetDocumentEntry, |
| ui_weak_ptr_, |
| cache_file_path, |
| GetFileFromCacheParams(params.virtual_file_path, |
| params.local_tmp_path, |
| params.content_url, |
| params.resource_id, |
| params.md5, |
| params.mime_type, |
| params.get_file_callback, |
| params.get_content_callback))); |
| } |
| |
| void GDataFileSystem::OnGetDocumentEntry(const FilePath& cache_file_path, |
| const GetFileFromCacheParams& params, |
| GDataErrorCode status, |
| scoped_ptr<base::Value> data) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DriveFileError error = util::GDataToDriveFileError(status); |
| |
| scoped_ptr<DriveEntry> fresh_entry; |
| if (error == DRIVE_FILE_OK) { |
| scoped_ptr<DocumentEntry> doc_entry(DocumentEntry::ExtractAndParse(*data)); |
| if (doc_entry.get()) |
| fresh_entry.reset(resource_metadata_->FromDocumentEntry(*doc_entry)); |
| if (!fresh_entry.get() || !fresh_entry->AsDriveFile()) { |
| LOG(ERROR) << "Got invalid entry from server for " << params.resource_id; |
| error = DRIVE_FILE_ERROR_FAILED; |
| } |
| } |
| |
| if (error != DRIVE_FILE_OK) { |
| if (!params.get_file_callback.is_null()) { |
| params.get_file_callback.Run(error, |
| cache_file_path, |
| params.mime_type, |
| REGULAR_FILE); |
| } |
| return; |
| } |
| |
| GURL content_url = fresh_entry->content_url(); |
| int64 file_size = fresh_entry->file_info().size; |
| |
| DCHECK_EQ(params.resource_id, fresh_entry->resource_id()); |
| scoped_ptr<DriveFile> fresh_entry_as_file( |
| fresh_entry.release()->AsDriveFile()); |
| resource_metadata_->RefreshFile(fresh_entry_as_file.Pass()); |
| |
| bool* has_enough_space = new bool(false); |
| util::PostBlockingPoolSequencedTaskAndReply( |
| FROM_HERE, |
| blocking_task_runner_, |
| base::Bind(&DriveCache::FreeDiskSpaceIfNeededFor, |
| base::Unretained(cache_), |
| file_size, |
| has_enough_space), |
| base::Bind(&GDataFileSystem::StartDownloadFileIfEnoughSpace, |
| ui_weak_ptr_, |
| params, |
| content_url, |
| cache_file_path, |
| base::Owned(has_enough_space))); |
| } |
| |
| void GDataFileSystem::StartDownloadFileIfEnoughSpace( |
| const GetFileFromCacheParams& params, |
| const GURL& content_url, |
| const FilePath& cache_file_path, |
| bool* has_enough_space) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (!*has_enough_space) { |
| // If no enough space, return PLATFORM_FILE_ERROR_NO_SPACE. |
| if (!params.get_file_callback.is_null()) { |
| params.get_file_callback.Run(DRIVE_FILE_ERROR_NO_SPACE, |
| cache_file_path, |
| params.mime_type, |
| REGULAR_FILE); |
| } |
| return; |
| } |
| |
| // We have enough disk space. Start downloading the file. |
| drive_service_->DownloadFile( |
| params.virtual_file_path, |
| params.local_tmp_path, |
| content_url, |
| base::Bind(&GDataFileSystem::OnFileDownloaded, |
| ui_weak_ptr_, |
| params), |
| params.get_content_callback); |
| } |
| |
| void GDataFileSystem::GetEntryInfoByPath(const FilePath& file_path, |
| const GetEntryInfoCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK(!callback.is_null()); |
| |
| RunTaskOnUIThread( |
| base::Bind(&GDataFileSystem::GetEntryInfoByPathOnUIThread, |
| ui_weak_ptr_, |
| file_path, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::GetEntryInfoByPathOnUIThread( |
| const FilePath& file_path, |
| const GetEntryInfoCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| LoadFeedIfNeeded( |
| base::Bind(&GDataFileSystem::GetEntryInfoByPathOnUIThreadAfterLoad, |
| ui_weak_ptr_, |
| file_path, |
| callback)); |
| } |
| |
| void GDataFileSystem::GetEntryInfoByPathOnUIThreadAfterLoad( |
| const FilePath& file_path, |
| const GetEntryInfoCallback& callback, |
| DriveFileError error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error, scoped_ptr<DriveEntryProto>()); |
| return; |
| } |
| |
| resource_metadata_->GetEntryInfoByPath( |
| file_path, |
| base::Bind(&GDataFileSystem::GetEntryInfoByPathOnUIThreadAfterGetEntry, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::GetEntryInfoByPathOnUIThreadAfterGetEntry( |
| const GetEntryInfoCallback& callback, |
| DriveFileError error, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error, scoped_ptr<DriveEntryProto>()); |
| return; |
| } |
| DCHECK(entry_proto.get()); |
| |
| CheckLocalModificationAndRun(entry_proto.Pass(), callback); |
| } |
| |
| void GDataFileSystem::ReadDirectoryByPath( |
| const FilePath& file_path, |
| const ReadDirectoryWithSettingCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK(!callback.is_null()); |
| |
| RunTaskOnUIThread( |
| base::Bind(&GDataFileSystem::ReadDirectoryByPathOnUIThread, |
| ui_weak_ptr_, |
| file_path, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::ReadDirectoryByPathOnUIThread( |
| const FilePath& file_path, |
| const ReadDirectoryWithSettingCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| LoadFeedIfNeeded( |
| base::Bind(&GDataFileSystem::ReadDirectoryByPathOnUIThreadAfterLoad, |
| ui_weak_ptr_, |
| file_path, |
| callback)); |
| } |
| |
| void GDataFileSystem::ReadDirectoryByPathOnUIThreadAfterLoad( |
| const FilePath& file_path, |
| const ReadDirectoryWithSettingCallback& callback, |
| DriveFileError error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error, |
| hide_hosted_docs_, |
| scoped_ptr<DriveEntryProtoVector>()); |
| return; |
| } |
| |
| resource_metadata_->ReadDirectoryByPath( |
| file_path, |
| base::Bind(&GDataFileSystem::ReadDirectoryByPathOnUIThreadAfterRead, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::ReadDirectoryByPathOnUIThreadAfterRead( |
| const ReadDirectoryWithSettingCallback& callback, |
| DriveFileError error, |
| scoped_ptr<DriveEntryProtoVector> entries) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error, |
| hide_hosted_docs_, |
| scoped_ptr<DriveEntryProtoVector>()); |
| return; |
| } |
| DCHECK(entries.get()); // This is valid for emptry directories too. |
| |
| callback.Run(DRIVE_FILE_OK, hide_hosted_docs_, entries.Pass()); |
| } |
| |
| void GDataFileSystem::RequestDirectoryRefresh(const FilePath& file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| RunTaskOnUIThread( |
| base::Bind(&GDataFileSystem::RequestDirectoryRefreshOnUIThread, |
| ui_weak_ptr_, |
| file_path)); |
| } |
| |
| void GDataFileSystem::RequestDirectoryRefreshOnUIThread( |
| const FilePath& file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Make sure the destination directory exists. |
| resource_metadata_->GetEntryInfoByPath( |
| file_path, |
| base::Bind( |
| &GDataFileSystem::RequestDirectoryRefreshOnUIThreadAfterGetEntryInfo, |
| ui_weak_ptr_, |
| file_path)); |
| } |
| |
| void GDataFileSystem::RequestDirectoryRefreshOnUIThreadAfterGetEntryInfo( |
| const FilePath& file_path, |
| DriveFileError error, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (error != DRIVE_FILE_OK || |
| !entry_proto->file_info().is_directory()) { |
| LOG(ERROR) << "Directory entry not found: " << file_path.value(); |
| return; |
| } |
| |
| feed_loader_->LoadDirectoryFromServer( |
| resource_metadata_->origin(), |
| entry_proto->resource_id(), |
| base::Bind(&GDataFileSystem::OnRequestDirectoryRefresh, |
| ui_weak_ptr_, |
| file_path)); |
| } |
| |
| void GDataFileSystem::OnRequestDirectoryRefresh( |
| const FilePath& directory_path, |
| GetDocumentsParams* params, |
| DriveFileError error) { |
| DCHECK(params); |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (error != DRIVE_FILE_OK) { |
| LOG(ERROR) << "Failed to refresh directory: " << directory_path.value() |
| << ": " << error; |
| return; |
| } |
| |
| int64 unused_delta_feed_changestamp = 0; |
| FeedToFileResourceMapUmaStats unused_uma_stats; |
| FileResourceIdMap file_map; |
| GDataWapiFeedProcessor feed_processor(resource_metadata_.get()); |
| error = feed_processor.FeedToFileResourceMap( |
| *params->feed_list, |
| &file_map, |
| &unused_delta_feed_changestamp, |
| &unused_uma_stats); |
| if (error != DRIVE_FILE_OK) { |
| LOG(ERROR) << "Failed to convert feed: " << directory_path.value() |
| << ": " << error; |
| return; |
| } |
| |
| resource_metadata_->RefreshDirectory( |
| params->directory_resource_id, |
| file_map, |
| base::Bind(&GDataFileSystem::OnDirectoryChangeFileMoveCallback, |
| ui_weak_ptr_)); |
| } |
| |
| void GDataFileSystem::UpdateFileByResourceId( |
| const std::string& resource_id, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK(!callback.is_null()); |
| |
| RunTaskOnUIThread( |
| base::Bind(&GDataFileSystem::UpdateFileByResourceIdOnUIThread, |
| ui_weak_ptr_, |
| resource_id, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::UpdateFileByResourceIdOnUIThread( |
| const std::string& resource_id, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| // TODO(satorux): GetEntryInfoByResourceId() is called twice for |
| // UpdateFileByResourceIdOnUIThread(). crbug.com/143873 |
| resource_metadata_->GetEntryInfoByResourceId( |
| resource_id, |
| base::Bind(&GDataFileSystem::UpdateFileByEntryInfo, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::UpdateFileByEntryInfo( |
| const FileOperationCallback& callback, |
| DriveFileError error, |
| const FilePath& /* dive_file_path */, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| DCHECK(entry_proto.get()); |
| if (entry_proto->file_info().is_directory()) { |
| callback.Run(DRIVE_FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| |
| cache_->GetFileOnUIThread( |
| entry_proto->resource_id(), |
| entry_proto->file_specific_info().file_md5(), |
| base::Bind(&GDataFileSystem::OnGetFileCompleteForUpdateFile, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::OnGetFileCompleteForUpdateFile( |
| const FileOperationCallback& callback, |
| DriveFileError error, |
| const std::string& resource_id, |
| const std::string& /* md5 */, |
| const FilePath& cache_file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| // Gets the size of the cache file. Since the file is locally modified, the |
| // file size information stored in DriveEntry is not correct. |
| DriveFileError* get_size_error = new DriveFileError(DRIVE_FILE_ERROR_FAILED); |
| int64* file_size = new int64(-1); |
| util::PostBlockingPoolSequencedTaskAndReply( |
| FROM_HERE, |
| blocking_task_runner_, |
| base::Bind(&GetLocalFileSizeOnBlockingPool, |
| cache_file_path, |
| get_size_error, |
| file_size), |
| base::Bind(&GDataFileSystem::OnGetFileSizeCompleteForUpdateFile, |
| ui_weak_ptr_, |
| callback, |
| resource_id, |
| cache_file_path, |
| base::Owned(get_size_error), |
| base::Owned(file_size))); |
| } |
| |
| void GDataFileSystem::OnGetFileSizeCompleteForUpdateFile( |
| const FileOperationCallback& callback, |
| const std::string& resource_id, |
| const FilePath& cache_file_path, |
| DriveFileError* error, |
| int64* file_size) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (*error != DRIVE_FILE_OK) { |
| callback.Run(*error); |
| return; |
| } |
| |
| // TODO(satorux): GetEntryInfoByResourceId() is called twice for |
| // UpdateFileByResourceIdOnUIThread(). crbug.com/143873 |
| resource_metadata_->GetEntryInfoByResourceId( |
| resource_id, |
| base::Bind(&GDataFileSystem::OnGetFileCompleteForUpdateFileByEntry, |
| ui_weak_ptr_, |
| callback, |
| *file_size, |
| cache_file_path)); |
| } |
| |
| void GDataFileSystem::OnGetFileCompleteForUpdateFileByEntry( |
| const FileOperationCallback& callback, |
| int64 file_size, |
| const FilePath& cache_file_path, |
| DriveFileError error, |
| const FilePath& drive_file_path, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| DCHECK(entry_proto.get()); |
| if (entry_proto->file_info().is_directory()) { |
| callback.Run(DRIVE_FILE_ERROR_NOT_FOUND); |
| return; |
| } |
| |
| uploader_->UploadExistingFile( |
| GURL(entry_proto->upload_url()), |
| drive_file_path, |
| cache_file_path, |
| file_size, |
| entry_proto->file_specific_info().content_mime_type(), |
| base::Bind(&GDataFileSystem::OnUpdatedFileUploaded, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::OnUpdatedFileUploaded( |
| const FileOperationCallback& callback, |
| DriveFileError error, |
| scoped_ptr<UploadFileInfo> upload_file_info) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(upload_file_info.get()); |
| |
| if (error != DRIVE_FILE_OK) { |
| if (!callback.is_null()) |
| callback.Run(error); |
| return; |
| } |
| |
| AddUploadedFile(UPLOAD_EXISTING_FILE, |
| upload_file_info->gdata_path.DirName(), |
| upload_file_info->entry.Pass(), |
| upload_file_info->file_path, |
| DriveCache::FILE_OPERATION_MOVE, |
| base::Bind(&OnAddUploadFileCompleted, callback, error)); |
| } |
| |
| void GDataFileSystem::GetAvailableSpace( |
| const GetAvailableSpaceCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| RunTaskOnUIThread(base::Bind(&GDataFileSystem::GetAvailableSpaceOnUIThread, |
| ui_weak_ptr_, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::GetAvailableSpaceOnUIThread( |
| const GetAvailableSpaceCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| drive_service_->GetAccountMetadata( |
| gdata::util::IsDriveV2ApiEnabled() ? |
| base::Bind(&GDataFileSystem::OnGetAboutResource, |
| ui_weak_ptr_, |
| callback) : |
| base::Bind(&GDataFileSystem::OnGetAvailableSpace, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::OnGetAvailableSpace( |
| const GetAvailableSpaceCallback& callback, |
| GDataErrorCode status, |
| scoped_ptr<base::Value> data) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| DriveFileError error = util::GDataToDriveFileError(status); |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error, -1, -1); |
| return; |
| } |
| |
| scoped_ptr<AccountMetadataFeed> feed; |
| if (data.get()) |
| feed = AccountMetadataFeed::CreateFrom(*data); |
| if (!feed.get()) { |
| callback.Run(DRIVE_FILE_ERROR_FAILED, -1, -1); |
| return; |
| } |
| |
| callback.Run(DRIVE_FILE_OK, |
| feed->quota_bytes_total(), |
| feed->quota_bytes_used()); |
| } |
| |
| void GDataFileSystem::OnGetAboutResource( |
| const GetAvailableSpaceCallback& callback, |
| GDataErrorCode status, |
| scoped_ptr<base::Value> resource_json) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| DriveFileError error = util::GDataToDriveFileError(status); |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error, -1, -1); |
| return; |
| } |
| |
| scoped_ptr<AboutResource> about; |
| if (resource_json.get()) |
| about = AboutResource::CreateFrom(*resource_json); |
| |
| if (!about.get()) { |
| callback.Run(DRIVE_FILE_ERROR_FAILED, -1, -1); |
| return; |
| } |
| |
| callback.Run(DRIVE_FILE_OK, |
| about->quota_bytes_total(), |
| about->quota_bytes_used()); |
| } |
| |
| void GDataFileSystem::OnCreateDirectoryCompleted( |
| const CreateDirectoryParams& params, |
| GDataErrorCode status, |
| scoped_ptr<base::Value> data) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DriveFileError error = util::GDataToDriveFileError(status); |
| if (error != DRIVE_FILE_OK) { |
| if (!params.callback.is_null()) |
| params.callback.Run(error); |
| |
| return; |
| } |
| |
| base::DictionaryValue* dict_value = NULL; |
| base::Value* created_entry = NULL; |
| if (data.get() && data->GetAsDictionary(&dict_value) && dict_value) |
| dict_value->Get("entry", &created_entry); |
| error = AddNewDirectory(params.created_directory_path.DirName(), |
| created_entry); |
| |
| if (error != DRIVE_FILE_OK) { |
| if (!params.callback.is_null()) |
| params.callback.Run(error); |
| |
| return; |
| } |
| |
| // Not done yet with recursive directory creation? |
| if (params.target_directory_path != params.created_directory_path && |
| params.is_recursive) { |
| CreateDirectory(params.target_directory_path, |
| params.is_exclusive, |
| params.is_recursive, |
| params.callback); |
| return; |
| } |
| |
| if (!params.callback.is_null()) { |
| // Finally done with the create request. |
| params.callback.Run(DRIVE_FILE_OK); |
| } |
| } |
| |
| void GDataFileSystem::OnSearch(const SearchCallback& callback, |
| GetDocumentsParams* params, |
| DriveFileError error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (error != DRIVE_FILE_OK) { |
| if (!callback.is_null()) |
| callback.Run(error, GURL(), scoped_ptr<std::vector<SearchResultInfo> >()); |
| return; |
| } |
| |
| // The search results will be returned using virtual directory. |
| // The directory is not really part of the file system, so it has no parent or |
| // root. |
| std::vector<SearchResultInfo>* results(new std::vector<SearchResultInfo>()); |
| |
| DCHECK_EQ(1u, params->feed_list->size()); |
| DocumentFeed* feed = params->feed_list->at(0); |
| |
| // TODO(tbarzic): Limit total number of returned results for the query. |
| GURL next_feed; |
| feed->GetNextFeedURL(&next_feed); |
| |
| if (feed->entries().empty()) { |
| scoped_ptr<std::vector<SearchResultInfo> > result_vec(results); |
| if (!callback.is_null()) |
| callback.Run(error, next_feed, result_vec.Pass()); |
| return; |
| } |
| |
| // Go through all entires generated by the feed and add them to the search |
| // result directory. |
| for (size_t i = 0; i < feed->entries().size(); ++i) { |
| DocumentEntry* doc = const_cast<DocumentEntry*>(feed->entries()[i]); |
| scoped_ptr<DriveEntry> entry(resource_metadata_->FromDocumentEntry(*doc)); |
| |
| if (!entry.get()) |
| continue; |
| |
| DCHECK_EQ(doc->resource_id(), entry->resource_id()); |
| DCHECK(!entry->is_deleted()); |
| |
| std::string entry_resource_id = entry->resource_id(); |
| |
| // This will do nothing if the entry is not already present in file system. |
| if (entry->AsDriveFile()) { |
| scoped_ptr<DriveFile> entry_as_file(entry.release()->AsDriveFile()); |
| resource_metadata_->RefreshFile(entry_as_file.Pass()); |
| // We shouldn't use entry object after this point. |
| DCHECK(!entry.get()); |
| } |
| |
| // We will need information about result entry to create info for callback. |
| // We can't use |entry| anymore, so we have to refetch entry from file |
| // system. Also, |entry| doesn't have file path set before |RefreshFile| |
| // call, so we can't get file path from there. |
| resource_metadata_->GetEntryByResourceIdAsync(entry_resource_id, |
| base::Bind(&AddEntryToSearchResults, |
| results, |
| callback, |
| base::Bind(&GDataFileSystem::CheckForUpdates, ui_weak_ptr_), |
| error, |
| i+1 == feed->entries().size(), |
| next_feed)); |
| } |
| } |
| |
| void GDataFileSystem::Search(const std::string& search_query, |
| const GURL& next_feed, |
| const SearchCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| RunTaskOnUIThread(base::Bind(&GDataFileSystem::SearchAsyncOnUIThread, |
| ui_weak_ptr_, |
| search_query, |
| next_feed, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::SearchAsyncOnUIThread( |
| const std::string& search_query, |
| const GURL& next_feed, |
| const SearchCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| feed_loader_->SearchFromServer( |
| resource_metadata_->origin(), |
| search_query, |
| next_feed, |
| base::Bind(&GDataFileSystem::OnSearch, ui_weak_ptr_, callback)); |
| } |
| |
| void GDataFileSystem::OnDirectoryChanged(const FilePath& directory_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| FOR_EACH_OBSERVER(GDataFileSystemInterface::Observer, observers_, |
| OnDirectoryChanged(directory_path)); |
| } |
| |
| void GDataFileSystem::OnDocumentFeedFetched(int num_accumulated_entries) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| FOR_EACH_OBSERVER(GDataFileSystemInterface::Observer, observers_, |
| OnDocumentFeedFetched(num_accumulated_entries)); |
| } |
| |
| void GDataFileSystem::OnFeedFromServerLoaded() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| FOR_EACH_OBSERVER(GDataFileSystemInterface::Observer, observers_, |
| OnFeedFromServerLoaded()); |
| } |
| |
| void GDataFileSystem::LoadRootFeedFromCacheForTesting() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| feed_loader_->LoadFromCache( |
| false, // should_load_from_server. |
| FileOperationCallback()); |
| } |
| |
| DriveFileError GDataFileSystem::UpdateFromFeedForTesting( |
| const std::vector<DocumentFeed*>& feed_list, |
| int64 start_changestamp, |
| int64 root_feed_changestamp) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| return feed_loader_->UpdateFromFeed(feed_list, |
| start_changestamp, |
| root_feed_changestamp); |
| } |
| |
| void GDataFileSystem::OnFilePathUpdated(const FileOperationCallback& callback, |
| DriveFileError error, |
| const FilePath& /* file_path */) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (!callback.is_null()) |
| callback.Run(error); |
| } |
| |
| void GDataFileSystem::OnCopyDocumentCompleted( |
| const FilePath& dir_path, |
| const FileOperationCallback& callback, |
| GDataErrorCode status, |
| scoped_ptr<base::Value> data) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| DriveFileError error = util::GDataToDriveFileError(status); |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| scoped_ptr<DocumentEntry> doc_entry(DocumentEntry::ExtractAndParse(*data)); |
| if (!doc_entry.get()) { |
| callback.Run(DRIVE_FILE_ERROR_FAILED); |
| return; |
| } |
| |
| DriveEntry* entry = resource_metadata_->FromDocumentEntry(*doc_entry); |
| if (!entry) { |
| callback.Run(DRIVE_FILE_ERROR_FAILED); |
| return; |
| } |
| |
| // |entry| was added in the root directory on the server, so we should |
| // first add it to |root_| to mirror the state and then move it to the |
| // destination directory by MoveEntryFromRootDirectory(). |
| resource_metadata_->AddEntryToDirectory( |
| resource_metadata_->root(), |
| entry, |
| base::Bind(&GDataFileSystem::MoveEntryFromRootDirectory, |
| ui_weak_ptr_, |
| dir_path, |
| callback)); |
| } |
| |
| void GDataFileSystem::OnMoveEntryFromRootDirectoryCompleted( |
| const FileOperationCallback& callback, |
| const FilePath& file_path, |
| const FilePath& dir_path, |
| GDataErrorCode status, |
| const GURL& document_url) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| DriveFileError error = util::GDataToDriveFileError(status); |
| if (error == DRIVE_FILE_OK) { |
| DriveEntry* entry = resource_metadata_->FindEntryByPathSync(file_path); |
| if (entry) { |
| DCHECK_EQ(resource_metadata_->root(), entry->parent()); |
| resource_metadata_->MoveEntryToDirectory( |
| dir_path, |
| entry, |
| base::Bind( |
| &GDataFileSystem::NotifyAndRunFileOperationCallback, |
| ui_weak_ptr_, |
| callback)); |
| return; |
| } else { |
| error = DRIVE_FILE_ERROR_NOT_FOUND; |
| } |
| } |
| |
| callback.Run(error); |
| } |
| |
| void GDataFileSystem::OnRemovedDocument( |
| const FileOperationCallback& callback, |
| const FilePath& file_path, |
| GDataErrorCode status, |
| const GURL& document_url) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DriveFileError error = util::GDataToDriveFileError(status); |
| |
| if (error == DRIVE_FILE_OK) |
| error = RemoveEntryAndCacheLocally(file_path); |
| |
| if (!callback.is_null()) { |
| callback.Run(error); |
| } |
| } |
| |
| void GDataFileSystem::OnFileDownloaded( |
| const GetFileFromCacheParams& params, |
| GDataErrorCode status, |
| const GURL& content_url, |
| const FilePath& downloaded_file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // If user cancels download of a pinned-but-not-fetched file, mark file as |
| // unpinned so that we do not sync the file again. |
| if (status == GDATA_CANCELLED) { |
| cache_->GetCacheEntryOnUIThread( |
| params.resource_id, |
| params.md5, |
| base::Bind(&GDataFileSystem::UnpinIfPinned, |
| ui_weak_ptr_, |
| params.resource_id, |
| params.md5)); |
| } |
| |
| // At this point, the disk can be full or nearly full for several reasons: |
| // - The expected file size was incorrect and the file was larger |
| // - There was an in-flight download operation and it used up space |
| // - The disk became full for some user actions we cannot control |
| // (ex. the user might have downloaded a large file from a regular web site) |
| // |
| // If we don't have enough space, we return PLATFORM_FILE_ERROR_NO_SPACE, |
| // and try to free up space, even if the file was downloaded successfully. |
| bool* has_enough_space = new bool(false); |
| util::PostBlockingPoolSequencedTaskAndReply( |
| FROM_HERE, |
| blocking_task_runner_, |
| base::Bind(&DriveCache::FreeDiskSpaceIfNeededFor, |
| base::Unretained(cache_), |
| 0, |
| has_enough_space), |
| base::Bind(&GDataFileSystem::OnFileDownloadedAndSpaceChecked, |
| ui_weak_ptr_, |
| params, |
| status, |
| content_url, |
| downloaded_file_path, |
| base::Owned(has_enough_space))); |
| } |
| |
| void GDataFileSystem::UnpinIfPinned( |
| const std::string& resource_id, |
| const std::string& md5, |
| bool success, |
| const DriveCacheEntry& cache_entry) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| // TODO(hshi): http://crbug.com/127138 notify when file properties change. |
| // This allows file manager to clear the "Available offline" checkbox. |
| if (success && cache_entry.is_pinned()) |
| cache_->UnpinOnUIThread(resource_id, md5, CacheOperationCallback()); |
| } |
| |
| void GDataFileSystem::OnFileDownloadedAndSpaceChecked( |
| const GetFileFromCacheParams& params, |
| GDataErrorCode status, |
| const GURL& content_url, |
| const FilePath& downloaded_file_path, |
| bool* has_enough_space) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DriveFileError error = util::GDataToDriveFileError(status); |
| |
| // Make sure that downloaded file is properly stored in cache. We don't have |
| // to wait for this operation to finish since the user can already use the |
| // downloaded file. |
| if (error == DRIVE_FILE_OK) { |
| if (*has_enough_space) { |
| cache_->StoreOnUIThread( |
| params.resource_id, |
| params.md5, |
| downloaded_file_path, |
| DriveCache::FILE_OPERATION_MOVE, |
| base::Bind(&GDataFileSystem::OnDownloadStoredToCache, |
| ui_weak_ptr_)); |
| } else { |
| // If we don't have enough space, remove the downloaded file, and |
| // report "no space" error. |
| util::PostBlockingPoolSequencedTask( |
| FROM_HERE, |
| blocking_task_runner_, |
| base::Bind(base::IgnoreResult(&file_util::Delete), |
| downloaded_file_path, |
| false /* recursive*/)); |
| error = DRIVE_FILE_ERROR_NO_SPACE; |
| } |
| } |
| |
| if (!params.get_file_callback.is_null()) { |
| params.get_file_callback.Run(error, |
| downloaded_file_path, |
| params.mime_type, |
| REGULAR_FILE); |
| } |
| } |
| |
| void GDataFileSystem::OnDownloadStoredToCache(DriveFileError error, |
| const std::string& resource_id, |
| const std::string& md5) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| // Nothing much to do here for now. |
| } |
| |
| void GDataFileSystem::RenameEntryLocally( |
| const FilePath& file_path, |
| const FilePath::StringType& new_name, |
| const FileMoveCallback& callback, |
| GDataErrorCode status, |
| const GURL& document_url) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| const DriveFileError error = util::GDataToDriveFileError(status); |
| if (error != DRIVE_FILE_OK) { |
| if (!callback.is_null()) |
| callback.Run(error, FilePath()); |
| return; |
| } |
| |
| DriveEntry* entry = resource_metadata_->FindEntryByPathSync(file_path); |
| if (!entry) { |
| if (!callback.is_null()) |
| callback.Run(DRIVE_FILE_ERROR_NOT_FOUND, FilePath()); |
| return; |
| } |
| |
| DCHECK(entry->parent()); |
| entry->set_title(new_name); |
| // After changing the title of the entry, call MoveEntryToDirectory() to |
| // remove the entry from its parent directory and then add it back in order to |
| // go through the file name de-duplication. |
| // TODO(achuith/satorux/zel): This code is fragile. The title has been |
| // changed, but not the file_name. MoveEntryToDirectory calls RemoveChild to |
| // remove the child based on the old file_name, and then re-adds the child by |
| // first assigning the new title to file_name. http://crbug.com/30157 |
| resource_metadata_->MoveEntryToDirectory( |
| entry->parent()->GetFilePath(), |
| entry, |
| base::Bind(&GDataFileSystem::NotifyAndRunFileMoveCallback, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::MoveEntryToRootDirectoryLocally( |
| const FileMoveCallback& callback, |
| const FilePath& file_path, |
| const FilePath& dir_path, |
| GDataErrorCode status, |
| const GURL& document_url) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| const DriveFileError error = util::GDataToDriveFileError(status); |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error, FilePath()); |
| return; |
| } |
| |
| DriveEntry* entry = resource_metadata_->FindEntryByPathSync(file_path); |
| if (!entry) { |
| callback.Run(DRIVE_FILE_ERROR_NOT_FOUND, FilePath()); |
| return; |
| } |
| |
| resource_metadata_->MoveEntryToDirectory( |
| resource_metadata_->root()->GetFilePath(), |
| entry, |
| base::Bind(&GDataFileSystem::NotifyAndRunFileMoveCallback, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::NotifyAndRunFileMoveCallback( |
| const FileMoveCallback& callback, |
| DriveFileError error, |
| const FilePath& moved_file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (error == DRIVE_FILE_OK) |
| OnDirectoryChanged(moved_file_path.DirName()); |
| |
| if (!callback.is_null()) |
| callback.Run(error, moved_file_path); |
| } |
| |
| void GDataFileSystem::NotifyAndRunFileOperationCallback( |
| const FileOperationCallback& callback, |
| DriveFileError error, |
| const FilePath& moved_file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (error == DRIVE_FILE_OK) |
| OnDirectoryChanged(moved_file_path.DirName()); |
| |
| callback.Run(error); |
| } |
| |
| void GDataFileSystem::OnDirectoryChangeFileMoveCallback( |
| DriveFileError error, |
| const FilePath& directory_path) { |
| if (error == DRIVE_FILE_OK) |
| OnDirectoryChanged(directory_path); |
| } |
| |
| DriveFileError GDataFileSystem::RemoveEntryAndCacheLocally( |
| const FilePath& file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| std::string resource_id; |
| DriveFileError error = RemoveEntryLocally(file_path, &resource_id); |
| if (error != DRIVE_FILE_OK) |
| return error; |
| |
| // If resource_id is not empty, remove its corresponding file from cache. |
| if (!resource_id.empty()) |
| cache_->RemoveOnUIThread(resource_id, CacheOperationCallback()); |
| |
| return DRIVE_FILE_OK; |
| } |
| |
| void GDataFileSystem::RemoveStaleEntryOnUpload( |
| const std::string& resource_id, |
| DriveDirectory* parent_dir, |
| const FileMoveCallback& callback, |
| DriveEntry* existing_entry) { |
| if (existing_entry && |
| // This should always match, but just in case. |
| existing_entry->parent() == parent_dir) { |
| resource_metadata_->RemoveEntryFromParent(existing_entry, callback); |
| } else { |
| callback.Run(DRIVE_FILE_ERROR_NOT_FOUND, FilePath()); |
| LOG(ERROR) << "Entry for the existing file not found: " << resource_id; |
| } |
| } |
| |
| void GDataFileSystem::NotifyFileSystemMounted() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DVLOG(1) << "File System is mounted"; |
| // Notify the observers that the file system is mounted. |
| FOR_EACH_OBSERVER(GDataFileSystemInterface::Observer, observers_, |
| OnFileSystemMounted()); |
| } |
| |
| void GDataFileSystem::NotifyFileSystemToBeUnmounted() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| DVLOG(1) << "File System is to be unmounted"; |
| // Notify the observers that the file system is being unmounted. |
| FOR_EACH_OBSERVER(GDataFileSystemInterface::Observer, observers_, |
| OnFileSystemBeingUnmounted()); |
| } |
| |
| void GDataFileSystem::NotifyInitialLoadFinishedAndRun( |
| const FileOperationCallback& callback, |
| DriveFileError error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| // Notify the observers that root directory has been initialized. |
| FOR_EACH_OBSERVER(GDataFileSystemInterface::Observer, observers_, |
| OnInitialLoadFinished()); |
| |
| callback.Run(error); |
| } |
| |
| DriveFileError GDataFileSystem::AddNewDirectory( |
| const FilePath& directory_path, base::Value* entry_value) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (!entry_value) |
| return DRIVE_FILE_ERROR_FAILED; |
| |
| scoped_ptr<DocumentEntry> doc_entry(DocumentEntry::CreateFrom(*entry_value)); |
| |
| if (!doc_entry.get()) |
| return DRIVE_FILE_ERROR_FAILED; |
| |
| // Find parent directory element within the cached file system snapshot. |
| DriveEntry* entry = resource_metadata_->FindEntryByPathSync(directory_path); |
| if (!entry) |
| return DRIVE_FILE_ERROR_FAILED; |
| |
| // Check if parent is a directory since in theory since this is a callback |
| // something could in the meantime have nuked the parent dir and created a |
| // file with the exact same name. |
| DriveDirectory* parent_dir = entry->AsDriveDirectory(); |
| if (!parent_dir) |
| return DRIVE_FILE_ERROR_FAILED; |
| |
| DriveEntry* new_entry = |
| resource_metadata_->FromDocumentEntry(*doc_entry); |
| if (!new_entry) |
| return DRIVE_FILE_ERROR_FAILED; |
| |
| resource_metadata_->AddEntryToDirectory( |
| parent_dir, |
| new_entry, |
| base::Bind(&GDataFileSystem::NotifyAndRunFileMoveCallback, |
| ui_weak_ptr_, |
| FileMoveCallback())); |
| return DRIVE_FILE_OK; |
| } |
| |
| GDataFileSystem::FindMissingDirectoryResult |
| GDataFileSystem::FindFirstMissingParentDirectory( |
| const FilePath& directory_path, |
| GURL* last_dir_content_url, |
| FilePath* first_missing_parent_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Let's find which how deep is the existing directory structure and |
| // get the first element that's missing. |
| std::vector<FilePath::StringType> path_parts; |
| directory_path.GetComponents(&path_parts); |
| FilePath current_path; |
| |
| for (std::vector<FilePath::StringType>::const_iterator iter = |
| path_parts.begin(); |
| iter != path_parts.end(); ++iter) { |
| current_path = current_path.Append(*iter); |
| DriveEntry* entry = resource_metadata_->FindEntryByPathSync(current_path); |
| if (entry) { |
| if (entry->file_info().is_directory) { |
| *last_dir_content_url = entry->content_url(); |
| } else { |
| // Huh, the segment found is a file not a directory? |
| return FOUND_INVALID; |
| } |
| } else { |
| *first_missing_parent_path = current_path; |
| return FOUND_MISSING; |
| } |
| } |
| return DIRECTORY_ALREADY_PRESENT; |
| } |
| |
| DriveFileError GDataFileSystem::RemoveEntryLocally( |
| const FilePath& file_path, std::string* resource_id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| resource_id->clear(); |
| |
| // Find directory element within the cached file system snapshot. |
| DriveEntry* entry = resource_metadata_->FindEntryByPathSync(file_path); |
| |
| if (!entry) |
| return DRIVE_FILE_ERROR_NOT_FOUND; |
| |
| // You can't remove root element. |
| if (!entry->parent()) |
| return DRIVE_FILE_ERROR_ACCESS_DENIED; |
| |
| // If it's a file (only files have resource id), get its resource id so that |
| // we can remove it after releasing the auto lock. |
| if (entry->AsDriveFile()) |
| *resource_id = entry->AsDriveFile()->resource_id(); |
| |
| resource_metadata_->RemoveEntryFromParent( |
| entry, |
| base::Bind(&GDataFileSystem::OnDirectoryChangeFileMoveCallback, |
| ui_weak_ptr_)); |
| return DRIVE_FILE_OK; |
| } |
| |
| void GDataFileSystem::AddUploadedFile( |
| UploadMode upload_mode, |
| const FilePath& virtual_dir_path, |
| scoped_ptr<DocumentEntry> entry, |
| const FilePath& file_content_path, |
| DriveCache::FileOperationType cache_operation, |
| const base::Closure& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Post a task to the same thread, rather than calling it here, as |
| // AddUploadedFile() is asynchronous. |
| base::MessageLoopProxy::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&GDataFileSystem::AddUploadedFileOnUIThread, |
| ui_weak_ptr_, |
| upload_mode, |
| virtual_dir_path, |
| base::Passed(&entry), |
| file_content_path, |
| cache_operation, |
| callback)); |
| } |
| |
| void GDataFileSystem::AddUploadedFileOnUIThread( |
| UploadMode upload_mode, |
| const FilePath& virtual_dir_path, |
| scoped_ptr<DocumentEntry> entry, |
| const FilePath& file_content_path, |
| DriveCache::FileOperationType cache_operation, |
| const base::Closure& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // ScopedClosureRunner ensures that the specified callback is always invoked |
| // upon return or passed on. |
| base::ScopedClosureRunner callback_runner(callback); |
| |
| if (!entry.get()) { |
| NOTREACHED(); |
| return; |
| } |
| |
| DriveEntry* dir_entry = resource_metadata_->FindEntryByPathSync( |
| virtual_dir_path); |
| if (!dir_entry) |
| return; |
| |
| DriveDirectory* parent_dir = dir_entry->AsDriveDirectory(); |
| if (!parent_dir) |
| return; |
| |
| scoped_ptr<DriveEntry> new_entry( |
| resource_metadata_->FromDocumentEntry(*entry)); |
| if (!new_entry.get()) |
| return; |
| |
| const std::string& resource_id = new_entry->resource_id(); |
| AddUploadedFileParams* params = |
| new AddUploadedFileParams(upload_mode, |
| parent_dir, |
| new_entry.Pass(), |
| file_content_path, |
| cache_operation, |
| callback_runner.Release()); |
| |
| const FileMoveCallback file_move_callback = |
| base::Bind(&GDataFileSystem::ContinueAddUploadedFile, |
| ui_weak_ptr_, params); |
| |
| if (upload_mode == UPLOAD_EXISTING_FILE) { |
| // Remove an existing entry, which should be present. |
| resource_metadata_->GetEntryByResourceIdAsync( |
| resource_id, |
| base::Bind(&GDataFileSystem::RemoveStaleEntryOnUpload, |
| ui_weak_ptr_, |
| resource_id, |
| parent_dir, |
| file_move_callback)); |
| } else { |
| file_move_callback.Run(DRIVE_FILE_OK, FilePath()); |
| } |
| } |
| |
| void GDataFileSystem::ContinueAddUploadedFile( |
| AddUploadedFileParams* params, |
| DriveFileError error, |
| const FilePath& file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK_EQ(DRIVE_FILE_OK, error); |
| DCHECK(params->new_entry.get()); |
| DriveFile* file = params->new_entry->AsDriveFile(); |
| DCHECK(file); |
| |
| params->resource_id = file->resource_id(); |
| params->md5 = file->file_md5(); |
| resource_metadata_->AddEntryToDirectory( |
| params->parent_dir, |
| params->new_entry.release(), |
| base::Bind(&GDataFileSystem::NotifyAndRunFileMoveCallback, |
| ui_weak_ptr_, |
| base::Bind(&GDataFileSystem::AddUploadedFileToCache, |
| ui_weak_ptr_, |
| base::Owned(params)))); |
| } |
| |
| void GDataFileSystem::AddUploadedFileToCache( |
| AddUploadedFileParams* params, |
| DriveFileError error, |
| const FilePath& file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (params->upload_mode == UPLOAD_NEW_FILE) { |
| // Add the file to the cache if we have uploaded a new file. |
| cache_->StoreOnUIThread(params->resource_id, |
| params->md5, |
| params->file_content_path, |
| params->cache_operation, |
| base::Bind(&OnCacheUpdatedForAddUploadedFile, |
| params->callback)); |
| } else if (params->upload_mode == UPLOAD_EXISTING_FILE) { |
| // Clear the dirty bit if we have updated an existing file. |
| cache_->ClearDirtyOnUIThread(params->resource_id, |
| params->md5, |
| base::Bind(&OnCacheUpdatedForAddUploadedFile, |
| params->callback)); |
| } else { |
| NOTREACHED() << "Unexpected upload mode: " << params->upload_mode; |
| // Shouldn't reach here, so the line below should not make much sense, but |
| // since calling |callback| exactly once is our obligation, we'd better call |
| // it for not to clutter further more. |
| params->callback.Run(); |
| } |
| } |
| |
| void GDataFileSystem::UpdateEntryData(const std::string& resource_id, |
| const std::string& md5, |
| scoped_ptr<DocumentEntry> entry, |
| const FilePath& file_content_path, |
| const base::Closure& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // Post a task to the same thread, rather than calling it here, as |
| // UpdateEntryData() is asynchronous. |
| base::MessageLoopProxy::current()->PostTask( |
| FROM_HERE, |
| base::Bind(&GDataFileSystem::UpdateEntryDataOnUIThread, |
| ui_weak_ptr_, |
| resource_id, |
| md5, |
| base::Passed(&entry), |
| file_content_path, |
| callback)); |
| } |
| |
| void GDataFileSystem::UpdateEntryDataOnUIThread( |
| const std::string& resource_id, |
| const std::string& md5, |
| scoped_ptr<DocumentEntry> entry, |
| const FilePath& file_content_path, |
| const base::Closure& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| scoped_ptr<DriveFile> new_entry( |
| resource_metadata_->FromDocumentEntry(*entry)->AsDriveFile()); |
| if (!new_entry.get()) { |
| return; |
| } |
| |
| resource_metadata_->RefreshFile(new_entry.Pass()); |
| |
| // Add the file to the cache if we have uploaded a new file. |
| cache_->StoreOnUIThread(resource_id, |
| md5, |
| file_content_path, |
| DriveCache::FILE_OPERATION_MOVE, |
| base::Bind(&OnCacheUpdatedForAddUploadedFile, |
| callback)); |
| } |
| |
| void GDataFileSystem::Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (type == chrome::NOTIFICATION_PREF_CHANGED) { |
| PrefService* pref_service = profile_->GetPrefs(); |
| std::string* pref_name = content::Details<std::string>(details).ptr(); |
| if (*pref_name == prefs::kDisableGDataHostedFiles) { |
| SetHideHostedDocuments( |
| pref_service->GetBoolean(prefs::kDisableGDataHostedFiles)); |
| } |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| void GDataFileSystem::SetHideHostedDocuments(bool hide) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (hide == hide_hosted_docs_) |
| return; |
| |
| hide_hosted_docs_ = hide; |
| const FilePath root_path = resource_metadata_->root()->GetFilePath(); |
| |
| // Kick off directory refresh when this setting changes. |
| FOR_EACH_OBSERVER(GDataFileSystemInterface::Observer, observers_, |
| OnDirectoryChanged(root_path)); |
| } |
| |
| //============= GDataFileSystem: internal helper functions ===================== |
| |
| void GDataFileSystem::InitializePreferenceObserver() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| pref_registrar_.reset(new PrefChangeRegistrar()); |
| pref_registrar_->Init(profile_->GetPrefs()); |
| pref_registrar_->Add(prefs::kDisableGDataHostedFiles, this); |
| } |
| |
| void GDataFileSystem::OpenFile(const FilePath& file_path, |
| const OpenFileCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| RunTaskOnUIThread(base::Bind(&GDataFileSystem::OpenFileOnUIThread, |
| ui_weak_ptr_, |
| file_path, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::OpenFileOnUIThread(const FilePath& file_path, |
| const OpenFileCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // If the file is already opened, it cannot be opened again before closed. |
| // This is for avoiding simultaneous modification to the file, and moreover |
| // to avoid an inconsistent cache state (suppose an operation sequence like |
| // Open->Open->modify->Close->modify->Close; the second modify may not be |
| // synchronized to the server since it is already Closed on the cache). |
| if (open_files_.find(file_path) != open_files_.end()) { |
| MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(callback, DRIVE_FILE_ERROR_IN_USE, FilePath())); |
| return; |
| } |
| open_files_.insert(file_path); |
| |
| resource_metadata_->GetEntryInfoByPath( |
| file_path, |
| base::Bind(&GDataFileSystem::OnGetEntryInfoCompleteForOpenFile, |
| ui_weak_ptr_, |
| file_path, |
| base::Bind(&GDataFileSystem::OnOpenFileFinished, |
| ui_weak_ptr_, |
| file_path, |
| callback))); |
| } |
| |
| void GDataFileSystem::OnGetEntryInfoCompleteForOpenFile( |
| const FilePath& file_path, |
| const OpenFileCallback& callback, |
| DriveFileError error, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (entry_proto.get() && !entry_proto->has_file_specific_info()) |
| error = DRIVE_FILE_ERROR_NOT_FOUND; |
| |
| if (error == DRIVE_FILE_OK) { |
| if (entry_proto->file_specific_info().file_md5().empty() || |
| entry_proto->file_specific_info().is_hosted_document()) { |
| // No support for opening a directory or hosted document. |
| error = DRIVE_FILE_ERROR_INVALID_OPERATION; |
| } |
| } |
| |
| if (error != DRIVE_FILE_OK) { |
| if (!callback.is_null()) |
| callback.Run(error, FilePath()); |
| return; |
| } |
| |
| DCHECK(!entry_proto->resource_id().empty()); |
| GetResolvedFileByPath( |
| file_path, |
| base::Bind(&GDataFileSystem::OnGetFileCompleteForOpenFile, |
| ui_weak_ptr_, |
| callback, |
| GetFileCompleteForOpenParams( |
| entry_proto->resource_id(), |
| entry_proto->file_specific_info().file_md5())), |
| GetContentCallback(), |
| error, |
| entry_proto.get()); |
| } |
| |
| void GDataFileSystem::OnGetFileCompleteForOpenFile( |
| const OpenFileCallback& callback, |
| const GetFileCompleteForOpenParams& entry_proto, |
| DriveFileError error, |
| const FilePath& file_path, |
| const std::string& mime_type, |
| DriveFileType file_type) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (error != DRIVE_FILE_OK) { |
| if (!callback.is_null()) |
| callback.Run(error, FilePath()); |
| return; |
| } |
| |
| // OpenFileOnUIThread ensures that the file is a regular file. |
| DCHECK_EQ(REGULAR_FILE, file_type); |
| |
| cache_->MarkDirtyOnUIThread( |
| entry_proto.resource_id, |
| entry_proto.md5, |
| base::Bind(&GDataFileSystem::OnMarkDirtyInCacheCompleteForOpenFile, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::OnMarkDirtyInCacheCompleteForOpenFile( |
| const OpenFileCallback& callback, |
| DriveFileError error, |
| const std::string& resource_id, |
| const std::string& md5, |
| const FilePath& cache_file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| if (!callback.is_null()) |
| callback.Run(error, cache_file_path); |
| } |
| |
| void GDataFileSystem::OnOpenFileFinished(const FilePath& file_path, |
| const OpenFileCallback& callback, |
| DriveFileError result, |
| const FilePath& cache_file_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| |
| // All the invocation of |callback| from operations initiated from OpenFile |
| // must go through here. Removes the |file_path| from the remembered set when |
| // the file was not successfully opened. |
| if (result != DRIVE_FILE_OK) |
| open_files_.erase(file_path); |
| |
| if (!callback.is_null()) |
| callback.Run(result, cache_file_path); |
| } |
| |
| void GDataFileSystem::CloseFile(const FilePath& file_path, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK(!callback.is_null()); |
| |
| RunTaskOnUIThread(base::Bind(&GDataFileSystem::CloseFileOnUIThread, |
| ui_weak_ptr_, |
| file_path, |
| CreateRelayCallback(callback))); |
| } |
| |
| void GDataFileSystem::CloseFileOnUIThread( |
| const FilePath& file_path, |
| const FileOperationCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (open_files_.find(file_path) == open_files_.end()) { |
| // The file is not being opened. |
| MessageLoop::current()->PostTask( |
| FROM_HERE, |
| base::Bind(callback, DRIVE_FILE_ERROR_NOT_FOUND)); |
| return; |
| } |
| |
| // Step 1 of CloseFile: Get resource_id and md5 for |file_path|. |
| resource_metadata_->GetEntryInfoByPath( |
| file_path, |
| base::Bind(&GDataFileSystem::CloseFileOnUIThreadAfterGetEntryInfo, |
| ui_weak_ptr_, |
| file_path, |
| base::Bind(&GDataFileSystem::CloseFileOnUIThreadFinalize, |
| ui_weak_ptr_, |
| file_path, |
| callback))); |
| } |
| |
| void GDataFileSystem::CloseFileOnUIThreadAfterGetEntryInfo( |
| const FilePath& file_path, |
| const FileOperationCallback& callback, |
| DriveFileError error, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (entry_proto.get() && !entry_proto->has_file_specific_info()) |
| error = DRIVE_FILE_ERROR_NOT_FOUND; |
| |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(error); |
| return; |
| } |
| |
| // Step 2 of CloseFile: Commit the modification in cache. This will trigger |
| // background upload. |
| // TODO(benchan,kinaba): Call ClearDirtyInCache instead of CommitDirtyInCache |
| // if the file has not been modified. Come up with a way to detect the |
| // intactness effectively, or provide a method for user to declare it when |
| // calling CloseFile(). |
| cache_->CommitDirtyOnUIThread( |
| entry_proto->resource_id(), |
| entry_proto->file_specific_info().file_md5(), |
| base::Bind(&GDataFileSystem::CloseFileOnUIThreadAfterCommitDirtyInCache, |
| ui_weak_ptr_, |
| callback)); |
| } |
| |
| void GDataFileSystem::CloseFileOnUIThreadAfterCommitDirtyInCache( |
| const FileOperationCallback& callback, |
| DriveFileError error, |
| const std::string& /* resource_id */, |
| const std::string& /* md5 */) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| callback.Run(error); |
| } |
| |
| void GDataFileSystem::CloseFileOnUIThreadFinalize( |
| const FilePath& file_path, |
| const FileOperationCallback& callback, |
| DriveFileError result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| // Step 3 of CloseFile. |
| // All the invocation of |callback| from operations initiated from CloseFile |
| // must go through here. Removes the |file_path| from the remembered set so |
| // that subsequent operations can open the file again. |
| open_files_.erase(file_path); |
| |
| // Then invokes the user-supplied callback function. |
| callback.Run(result); |
| } |
| |
| void GDataFileSystem::CheckLocalModificationAndRun( |
| scoped_ptr<DriveEntryProto> entry_proto, |
| const GetEntryInfoCallback& callback) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(entry_proto.get()); |
| DCHECK(!callback.is_null()); |
| |
| // For entries that will never be cached, use the original entry info as is. |
| if (!entry_proto->has_file_specific_info() || |
| entry_proto->file_specific_info().is_hosted_document()) { |
| callback.Run(DRIVE_FILE_OK, entry_proto.Pass()); |
| return; |
| } |
| |
| // Checks if the file is cached and modified locally. |
| const std::string resource_id = entry_proto->resource_id(); |
| const std::string md5 = entry_proto->file_specific_info().file_md5(); |
| cache_->GetCacheEntryOnUIThread( |
| resource_id, |
| md5, |
| base::Bind( |
| &GDataFileSystem::CheckLocalModificationAndRunAfterGetCacheEntry, |
| ui_weak_ptr_, base::Passed(&entry_proto), callback)); |
| } |
| |
| void GDataFileSystem::CheckLocalModificationAndRunAfterGetCacheEntry( |
| scoped_ptr<DriveEntryProto> entry_proto, |
| const GetEntryInfoCallback& callback, |
| bool success, |
| const DriveCacheEntry& cache_entry) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| // When no dirty cache is found, use the original entry info as is. |
| if (!success || !cache_entry.is_dirty()) { |
| callback.Run(DRIVE_FILE_OK, entry_proto.Pass()); |
| return; |
| } |
| |
| // Gets the cache file path. |
| const std::string& resource_id = entry_proto->resource_id(); |
| const std::string& md5 = entry_proto->file_specific_info().file_md5(); |
| cache_->GetFileOnUIThread( |
| resource_id, |
| md5, |
| base::Bind( |
| &GDataFileSystem::CheckLocalModificationAndRunAfterGetCacheFile, |
| ui_weak_ptr_, base::Passed(&entry_proto), callback)); |
| } |
| |
| void GDataFileSystem::CheckLocalModificationAndRunAfterGetCacheFile( |
| scoped_ptr<DriveEntryProto> entry_proto, |
| const GetEntryInfoCallback& callback, |
| DriveFileError error, |
| const std::string& resource_id, |
| const std::string& md5, |
| const FilePath& local_cache_path) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| // When no dirty cache is found, use the original entry info as is. |
| if (error != DRIVE_FILE_OK) { |
| callback.Run(DRIVE_FILE_OK, entry_proto.Pass()); |
| return; |
| } |
| |
| // If the cache is dirty, obtain the file info from the cache file itself. |
| base::PlatformFileInfo* file_info = new base::PlatformFileInfo; |
| bool* get_file_info_result = new bool(false); |
| util::PostBlockingPoolSequencedTaskAndReply( |
| FROM_HERE, |
| blocking_task_runner_, |
| base::Bind(&GetFileInfoOnBlockingPool, |
| local_cache_path, |
| base::Unretained(file_info), |
| base::Unretained(get_file_info_result)), |
| base::Bind(&GDataFileSystem::CheckLocalModificationAndRunAfterGetFileInfo, |
| ui_weak_ptr_, |
| base::Passed(&entry_proto), |
| callback, |
| base::Owned(file_info), |
| base::Owned(get_file_info_result))); |
| } |
| |
| void GDataFileSystem::CheckLocalModificationAndRunAfterGetFileInfo( |
| scoped_ptr<DriveEntryProto> entry_proto, |
| const GetEntryInfoCallback& callback, |
| base::PlatformFileInfo* file_info, |
| bool* get_file_info_result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(!callback.is_null()); |
| |
| if (!*get_file_info_result) { |
| callback.Run(DRIVE_FILE_ERROR_NOT_FOUND, scoped_ptr<DriveEntryProto>()); |
| return; |
| } |
| |
| PlatformFileInfoProto entry_file_info; |
| DriveEntry::ConvertPlatformFileInfoToProto(*file_info, &entry_file_info); |
| *entry_proto->mutable_file_info() = entry_file_info; |
| callback.Run(DRIVE_FILE_OK, entry_proto.Pass()); |
| } |
| |
| } // namespace gdata |