blob: b9bda056d8ed46fdd1bb80a47d14626fa987469a [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/drive_sync_client.h"
#include <algorithm>
#include <vector>
#include "base/bind.h"
#include "base/message_loop_proxy.h"
#include "chrome/browser/api/prefs/pref_change_registrar.h"
#include "chrome/browser/chromeos/gdata/drive.pb.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
namespace gdata {
namespace {
// The delay constant is used to delay processing a SyncTask in
// DoSyncLoop(). We should not process SyncTasks immediately for the
// following reasons:
//
// 1) For fetching, the user may accidentally click on "Make available
// offline" checkbox on a file, and immediately cancel it in a second.
// It's a waste to fetch the file in this case.
//
// 2) For uploading, file writing via HTML5 file system API is performed in
// two steps: 1) truncate a file to 0 bytes, 2) write contents. We
// shouldn't start uploading right after the step 1). Besides, the user
// may edit the same file repeatedly in a short period of time.
//
// TODO(satorux): We should find a way to handle the upload case more nicely,
// and shorten the delay. crbug.com/134774
const int kDelaySeconds = 5;
// Functor for std::find_if() search for a sync task that matches
// |in_sync_type| and |in_resource_id|.
struct CompareTypeAndResourceId {
CompareTypeAndResourceId(const DriveSyncClient::SyncType& in_sync_type,
const std::string& in_resource_id)
: sync_type(in_sync_type),
resource_id(in_resource_id) {}
bool operator()(const DriveSyncClient::SyncTask& sync_task) {
return (sync_type == sync_task.sync_type &&
resource_id == sync_task.resource_id);
}
const DriveSyncClient::SyncType sync_type;
const std::string resource_id;
};
// Returns true if the current network connection is over cellular.
bool IsConnectionTypeCellular() {
bool is_cellular = false;
// Use switch, not if, to allow compiler to catch future enum changes.
// (e.g. Addition of CONNECTION_5G)
switch (net::NetworkChangeNotifier::GetConnectionType()) {
case net::NetworkChangeNotifier::CONNECTION_2G:
case net::NetworkChangeNotifier::CONNECTION_3G:
case net::NetworkChangeNotifier::CONNECTION_4G:
is_cellular = true;
break;
case net::NetworkChangeNotifier::CONNECTION_UNKNOWN:
case net::NetworkChangeNotifier::CONNECTION_ETHERNET:
case net::NetworkChangeNotifier::CONNECTION_WIFI:
case net::NetworkChangeNotifier::CONNECTION_NONE:
is_cellular = false;
break;
}
return is_cellular;
}
} // namespace
DriveSyncClient::SyncTask::SyncTask(SyncType in_sync_type,
const std::string& in_resource_id,
const base::Time& in_timestamp)
: sync_type(in_sync_type),
resource_id(in_resource_id),
timestamp(in_timestamp) {
}
DriveSyncClient::DriveSyncClient(Profile* profile,
DriveFileSystemInterface* file_system,
DriveCache* cache)
: profile_(profile),
file_system_(file_system),
cache_(cache),
registrar_(new PrefChangeRegistrar),
delay_(base::TimeDelta::FromSeconds(kDelaySeconds)),
sync_loop_is_running_(false),
ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
DriveSyncClient::~DriveSyncClient() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (file_system_)
file_system_->RemoveObserver(this);
if (cache_)
cache_->RemoveObserver(this);
net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
}
void DriveSyncClient::Initialize() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
file_system_->AddObserver(this);
cache_->AddObserver(this);
net::NetworkChangeNotifier::AddConnectionTypeObserver(this);
registrar_->Init(profile_->GetPrefs());
registrar_->Add(prefs::kDisableGData, this);
registrar_->Add(prefs::kDisableGDataOverCellular, this);
}
void DriveSyncClient::StartProcessingBacklog() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
cache_->GetResourceIdsOfBacklogOnUIThread(
base::Bind(&DriveSyncClient::OnGetResourceIdsOfBacklog,
weak_ptr_factory_.GetWeakPtr()));
}
void DriveSyncClient::StartCheckingExistingPinnedFiles() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
cache_->GetResourceIdsOfExistingPinnedFilesOnUIThread(
base::Bind(&DriveSyncClient::OnGetResourceIdsOfExistingPinnedFiles,
weak_ptr_factory_.GetWeakPtr()));
}
std::vector<std::string> DriveSyncClient::GetResourceIdsForTesting(
SyncType sync_type) const {
std::vector<std::string> resource_ids;
for (size_t i = 0; i < queue_.size(); ++i) {
const SyncTask& sync_task = queue_[i];
if (sync_task.sync_type == sync_type)
resource_ids.push_back(sync_task.resource_id);
}
return resource_ids;
}
void DriveSyncClient::StartSyncLoop() {
if (!sync_loop_is_running_)
DoSyncLoop();
}
void DriveSyncClient::DoSyncLoop() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (queue_.empty() || ShouldStopSyncLoop()) {
// Note that |queue_| is not cleared so the sync loop can resume.
sync_loop_is_running_ = false;
return;
}
sync_loop_is_running_ = true;
// Should copy before calling queue_.pop_front().
const SyncTask sync_task = queue_.front();
// Check if we are ready to process the task.
const base::TimeDelta elapsed = base::Time::Now() - sync_task.timestamp;
if (elapsed < delay_) {
// Not yet ready. Revisit at a later time.
const bool posted = base::MessageLoopProxy::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&DriveSyncClient::DoSyncLoop,
weak_ptr_factory_.GetWeakPtr()),
delay_);
DCHECK(posted);
return;
}
queue_.pop_front();
if (sync_task.sync_type == FETCH) {
DVLOG(1) << "Fetching " << sync_task.resource_id;
file_system_->GetFileByResourceId(
sync_task.resource_id,
base::Bind(&DriveSyncClient::OnFetchFileComplete,
weak_ptr_factory_.GetWeakPtr(),
sync_task),
GetContentCallback());
} else if (sync_task.sync_type == UPLOAD) {
DVLOG(1) << "Uploading " << sync_task.resource_id;
file_system_->UpdateFileByResourceId(
sync_task.resource_id,
base::Bind(&DriveSyncClient::OnUploadFileComplete,
weak_ptr_factory_.GetWeakPtr(),
sync_task.resource_id));
} else {
NOTREACHED() << ": Unexpected sync type: " << sync_task.sync_type;
}
}
bool DriveSyncClient::ShouldStopSyncLoop() {
// Should stop if the drive feature was disabled while running the fetch
// loop.
if (profile_->GetPrefs()->GetBoolean(prefs::kDisableGData))
return true;
// Should stop if the network is not online.
if (net::NetworkChangeNotifier::IsOffline())
return true;
// Should stop if the current connection is on cellular network, and
// fetching is disabled over cellular.
if (profile_->GetPrefs()->GetBoolean(prefs::kDisableGDataOverCellular) &&
IsConnectionTypeCellular())
return true;
return false;
}
void DriveSyncClient::OnInitialLoadFinished() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
StartProcessingBacklog();
}
void DriveSyncClient::OnFeedFromServerLoaded() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
StartCheckingExistingPinnedFiles();
}
void DriveSyncClient::OnCachePinned(const std::string& resource_id,
const std::string& md5) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
AddTaskToQueue(SyncTask(FETCH, resource_id, base::Time::Now()));
StartSyncLoop();
}
void DriveSyncClient::OnCacheUnpinned(const std::string& resource_id,
const std::string& md5) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Remove the resource_id if it's in the queue. This can happen if the user
// cancels pinning before the file is fetched.
std::deque<SyncTask>::iterator iter =
std::find_if(queue_.begin(), queue_.end(),
CompareTypeAndResourceId(FETCH, resource_id));
if (iter != queue_.end())
queue_.erase(iter);
}
void DriveSyncClient::OnCacheCommitted(const std::string& resource_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
AddTaskToQueue(SyncTask(UPLOAD, resource_id, base::Time::Now()));
StartSyncLoop();
}
void DriveSyncClient::AddTaskToQueue(const SyncTask& sync_task) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
std::deque<SyncTask>::iterator iter =
std::find_if(queue_.begin(), queue_.end(),
CompareTypeAndResourceId(sync_task.sync_type,
sync_task.resource_id));
// If the same task is already queued, remove it. We'll add back the new
// task to the end of the queue.
if (iter != queue_.end())
queue_.erase(iter);
queue_.push_back(sync_task);
}
void DriveSyncClient::OnGetResourceIdsOfBacklog(
const std::vector<std::string>& to_fetch,
const std::vector<std::string>& to_upload) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Give priority to upload tasks over fetch tasks, so that dirty files are
// uploaded as soon as possible.
for (size_t i = 0; i < to_upload.size(); ++i) {
const std::string& resource_id = to_upload[i];
DVLOG(1) << "Queuing to upload: " << resource_id;
AddTaskToQueue(SyncTask(UPLOAD, resource_id, base::Time::Now()));
}
for (size_t i = 0; i < to_fetch.size(); ++i) {
const std::string& resource_id = to_fetch[i];
DVLOG(1) << "Queuing to fetch: " << resource_id;
AddTaskToQueue(SyncTask(FETCH, resource_id, base::Time::Now()));
}
StartSyncLoop();
}
void DriveSyncClient::OnGetResourceIdsOfExistingPinnedFiles(
const std::vector<std::string>& resource_ids) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
for (size_t i = 0; i < resource_ids.size(); ++i) {
const std::string& resource_id = resource_ids[i];
file_system_->GetEntryInfoByResourceId(
resource_id,
base::Bind(&DriveSyncClient::OnGetEntryInfoByResourceId,
weak_ptr_factory_.GetWeakPtr(),
resource_id));
}
}
void DriveSyncClient::OnGetEntryInfoByResourceId(
const std::string& resource_id,
DriveFileError error,
const FilePath& /* drive_file_path */,
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) {
LOG(WARNING) << "Entry not found: " << resource_id;
return;
}
cache_->GetCacheEntryOnUIThread(
resource_id,
"" /* don't check MD5 */,
base::Bind(&DriveSyncClient::OnGetCacheEntry,
weak_ptr_factory_.GetWeakPtr(),
resource_id,
entry_proto->file_specific_info().file_md5()));
}
void DriveSyncClient::OnGetCacheEntry(
const std::string& resource_id,
const std::string& latest_md5,
bool success,
const DriveCacheEntry& cache_entry) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!success) {
LOG(WARNING) << "Cache entry not found: " << resource_id;
return;
}
// If MD5s don't match, it indicates the local cache file is stale, unless
// the file is dirty (the MD5 is "local"). We should never re-fetch the
// file when we have a locally modified version.
if (latest_md5 != cache_entry.md5() && !cache_entry.is_dirty()) {
cache_->RemoveOnUIThread(
resource_id,
base::Bind(&DriveSyncClient::OnRemove,
weak_ptr_factory_.GetWeakPtr()));
}
}
void DriveSyncClient::OnRemove(DriveFileError error,
const std::string& resource_id,
const std::string& md5) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (error != DRIVE_FILE_OK) {
LOG(WARNING) << "Failed to remove cache entry: " << resource_id;
return;
}
// Before fetching, we should pin this file again, so that the fetched file
// is downloaded properly to the persistent directory and marked pinned.
cache_->PinOnUIThread(resource_id,
md5,
base::Bind(&DriveSyncClient::OnPinned,
weak_ptr_factory_.GetWeakPtr()));
}
void DriveSyncClient::OnPinned(DriveFileError error,
const std::string& resource_id,
const std::string& /* md5 */) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (error != DRIVE_FILE_OK) {
LOG(WARNING) << "Failed to pin cache entry: " << resource_id;
return;
}
// Finally, adding to the queue.
AddTaskToQueue(SyncTask(FETCH, resource_id, base::Time::Now()));
StartSyncLoop();
}
void DriveSyncClient::OnFetchFileComplete(const SyncTask& sync_task,
DriveFileError error,
const FilePath& local_path,
const std::string& ununsed_mime_type,
DriveFileType file_type) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (error == DRIVE_FILE_OK) {
DVLOG(1) << "Fetched " << sync_task.resource_id << ": "
<< local_path.value();
} else {
switch (error) {
case DRIVE_FILE_ERROR_NO_CONNECTION:
// Re-queue the task so that we'll retry once the connection is back.
queue_.push_front(sync_task);
break;
default:
LOG(WARNING) << "Failed to fetch " << sync_task.resource_id
<< ": " << error;
}
}
// Continue the loop.
DoSyncLoop();
}
void DriveSyncClient::OnUploadFileComplete(const std::string& resource_id,
DriveFileError error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (error == DRIVE_FILE_OK) {
DVLOG(1) << "Uploaded " << resource_id;
} else {
// TODO(satorux): We should re-queue if the error is recoverable.
LOG(WARNING) << "Failed to upload " << resource_id << ": " << error;
}
// Continue the loop.
DoSyncLoop();
}
void DriveSyncClient::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Resume the sync loop if gdata preferences are changed. Note that we
// don't need to check the new values here as these will be checked in
// ShouldStopSyncLoop() as soon as the loop is resumed.
StartSyncLoop();
}
void DriveSyncClient::OnConnectionTypeChanged(
net::NetworkChangeNotifier::ConnectionType type) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Resume the sync loop if the network is back online. Note that we don't
// need to check the type of the network as it will be checked in
// ShouldStopSyncLoop() as soon as the loop is resumed.
if (!net::NetworkChangeNotifier::IsOffline())
StartSyncLoop();
}
} // namespace gdata