blob: 88cde5e3bd032f0aed77e234a301b5ca6cca2092 [file] [log] [blame]
// 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