1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/chromeos/drive/sync_client.h"
10 #include "base/message_loop/message_loop_proxy.h"
11 #include "chrome/browser/chromeos/drive/drive.pb.h"
12 #include "chrome/browser/chromeos/drive/file_cache.h"
13 #include "chrome/browser/chromeos/drive/file_system/download_operation.h"
14 #include "chrome/browser/chromeos/drive/file_system/operation_delegate.h"
15 #include "chrome/browser/chromeos/drive/file_system_util.h"
16 #include "chrome/browser/chromeos/drive/job_scheduler.h"
17 #include "chrome/browser/chromeos/drive/sync/entry_update_performer.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "google_apis/drive/task_util.h"
21 using content::BrowserThread
;
28 // The delay constant is used to delay processing a sync task. We should not
29 // process SyncTasks immediately for the following reasons:
31 // 1) For fetching, the user may accidentally click on "Make available
32 // offline" checkbox on a file, and immediately cancel it in a second.
33 // It's a waste to fetch the file in this case.
35 // 2) For uploading, file writing via HTML5 file system API is performed in
36 // two steps: 1) truncate a file to 0 bytes, 2) write contents. We
37 // shouldn't start uploading right after the step 1). Besides, the user
38 // may edit the same file repeatedly in a short period of time.
40 // TODO(satorux): We should find a way to handle the upload case more nicely,
41 // and shorten the delay. crbug.com/134774
42 const int kDelaySeconds
= 1;
44 // The delay constant is used to delay retrying a sync task on server errors.
45 const int kLongDelaySeconds
= 600;
47 // Iterates entries and appends IDs to |to_fetch| if the file is pinned but not
48 // fetched (not present locally), to |to_update| if the file needs update.
49 void CollectBacklog(ResourceMetadata
* metadata
,
50 std::vector
<std::string
>* to_fetch
,
51 std::vector
<std::string
>* to_update
) {
55 scoped_ptr
<ResourceMetadata::Iterator
> it
= metadata
->GetIterator();
56 for (; !it
->IsAtEnd(); it
->Advance()) {
57 const std::string
& local_id
= it
->GetID();
58 const ResourceEntry
& entry
= it
->GetValue();
59 if (entry
.parent_local_id() == util::kDriveTrashDirLocalId
) {
60 to_update
->push_back(local_id
);
64 bool should_update
= false;
65 switch (entry
.metadata_edit_state()) {
66 case ResourceEntry::CLEAN
:
68 case ResourceEntry::SYNCING
:
69 case ResourceEntry::DIRTY
:
74 if (entry
.file_specific_info().cache_state().is_pinned() &&
75 !entry
.file_specific_info().cache_state().is_present())
76 to_fetch
->push_back(local_id
);
78 if (entry
.file_specific_info().cache_state().is_dirty())
82 to_update
->push_back(local_id
);
84 DCHECK(!it
->HasError());
87 // Iterates cache entries and collects IDs of ones with obsolete cache files.
88 void CheckExistingPinnedFiles(ResourceMetadata
* metadata
,
90 std::vector
<std::string
>* local_ids
) {
91 scoped_ptr
<ResourceMetadata::Iterator
> it
= metadata
->GetIterator();
92 for (; !it
->IsAtEnd(); it
->Advance()) {
93 const ResourceEntry
& entry
= it
->GetValue();
94 const FileCacheEntry
& cache_state
=
95 entry
.file_specific_info().cache_state();
96 const std::string
& local_id
= it
->GetID();
97 if (!cache_state
.is_pinned() || !cache_state
.is_present())
100 // If MD5s don't match, it indicates the local cache file is stale, unless
101 // the file is dirty (the MD5 is "local"). We should never re-fetch the
102 // file when we have a locally modified version.
103 if (entry
.file_specific_info().md5() == cache_state
.md5() ||
104 cache_state
.is_dirty())
107 FileError error
= cache
->Remove(local_id
);
108 if (error
!= FILE_ERROR_OK
) {
109 LOG(WARNING
) << "Failed to remove cache entry: " << local_id
;
113 error
= cache
->Pin(local_id
);
114 if (error
!= FILE_ERROR_OK
) {
115 LOG(WARNING
) << "Failed to pin cache entry: " << local_id
;
119 local_ids
->push_back(local_id
);
121 DCHECK(!it
->HasError());
124 // Gets the parent entry of the entry specified by the ID.
125 FileError
GetParentResourceEntry(ResourceMetadata
* metadata
,
126 const std::string
& local_id
,
127 ResourceEntry
* parent
) {
129 FileError error
= metadata
->GetResourceEntryById(local_id
, &entry
);
130 if (error
!= FILE_ERROR_OK
)
132 return metadata
->GetResourceEntryById(entry
.parent_local_id(), parent
);
137 SyncClient::SyncTask::SyncTask()
138 : state(SUSPENDED
), context(BACKGROUND
), should_run_again(false) {}
139 SyncClient::SyncTask::~SyncTask() {}
141 SyncClient::SyncClient(base::SequencedTaskRunner
* blocking_task_runner
,
142 file_system::OperationDelegate
* delegate
,
143 JobScheduler
* scheduler
,
144 ResourceMetadata
* metadata
,
146 LoaderController
* loader_controller
,
147 const base::FilePath
& temporary_file_directory
)
148 : blocking_task_runner_(blocking_task_runner
),
149 operation_delegate_(delegate
),
152 download_operation_(new file_system::DownloadOperation(
153 blocking_task_runner
,
158 temporary_file_directory
)),
159 entry_update_performer_(new EntryUpdatePerformer(blocking_task_runner
,
165 delay_(base::TimeDelta::FromSeconds(kDelaySeconds
)),
166 long_delay_(base::TimeDelta::FromSeconds(kLongDelaySeconds
)),
167 weak_ptr_factory_(this) {
168 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
171 SyncClient::~SyncClient() {
172 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
175 void SyncClient::StartProcessingBacklog() {
176 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
178 std::vector
<std::string
>* to_fetch
= new std::vector
<std::string
>;
179 std::vector
<std::string
>* to_update
= new std::vector
<std::string
>;
180 blocking_task_runner_
->PostTaskAndReply(
182 base::Bind(&CollectBacklog
, metadata_
, to_fetch
, to_update
),
183 base::Bind(&SyncClient::OnGetLocalIdsOfBacklog
,
184 weak_ptr_factory_
.GetWeakPtr(),
185 base::Owned(to_fetch
),
186 base::Owned(to_update
)));
189 void SyncClient::StartCheckingExistingPinnedFiles() {
190 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
192 std::vector
<std::string
>* local_ids
= new std::vector
<std::string
>;
193 blocking_task_runner_
->PostTaskAndReply(
195 base::Bind(&CheckExistingPinnedFiles
,
199 base::Bind(&SyncClient::AddFetchTasks
,
200 weak_ptr_factory_
.GetWeakPtr(),
201 base::Owned(local_ids
)));
204 void SyncClient::AddFetchTask(const std::string
& local_id
) {
205 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
206 AddFetchTaskInternal(local_id
, delay_
);
209 void SyncClient::RemoveFetchTask(const std::string
& local_id
) {
210 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
212 SyncTasks::iterator it
= tasks_
.find(SyncTasks::key_type(FETCH
, local_id
));
213 if (it
== tasks_
.end())
216 SyncTask
* task
= &it
->second
;
217 switch (task
->state
) {
220 OnTaskComplete(FETCH
, local_id
, FILE_ERROR_ABORT
);
223 if (!task
->cancel_closure
.is_null())
224 task
->cancel_closure
.Run();
229 void SyncClient::AddUpdateTask(const ClientContext
& context
,
230 const std::string
& local_id
) {
231 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
232 AddUpdateTaskInternal(context
, local_id
, delay_
);
235 bool SyncClient:: WaitForUpdateTaskToComplete(
236 const std::string
& local_id
,
237 const FileOperationCallback
& callback
) {
238 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
240 SyncTasks::iterator it
= tasks_
.find(SyncTasks::key_type(UPDATE
, local_id
));
241 if (it
== tasks_
.end())
244 SyncTask
* task
= &it
->second
;
245 task
->waiting_callbacks
.push_back(callback
);
249 base::Closure
SyncClient::PerformFetchTask(const std::string
& local_id
,
250 const ClientContext
& context
) {
251 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
252 return download_operation_
->EnsureFileDownloadedByLocalId(
255 GetFileContentInitializedCallback(),
256 google_apis::GetContentCallback(),
257 base::Bind(&SyncClient::OnFetchFileComplete
,
258 weak_ptr_factory_
.GetWeakPtr(),
262 void SyncClient::AddFetchTaskInternal(const std::string
& local_id
,
263 const base::TimeDelta
& delay
) {
264 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
267 task
.state
= PENDING
;
268 task
.context
= ClientContext(BACKGROUND
);
269 task
.task
= base::Bind(&SyncClient::PerformFetchTask
,
270 base::Unretained(this),
272 AddTask(SyncTasks::key_type(FETCH
, local_id
), task
, delay
);
275 base::Closure
SyncClient::PerformUpdateTask(const std::string
& local_id
,
276 const ClientContext
& context
) {
277 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
278 entry_update_performer_
->UpdateEntry(
281 base::Bind(&SyncClient::OnTaskComplete
,
282 weak_ptr_factory_
.GetWeakPtr(),
285 return base::Closure();
288 void SyncClient::AddUpdateTaskInternal(const ClientContext
& context
,
289 const std::string
& local_id
,
290 const base::TimeDelta
& delay
) {
291 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
294 task
.state
= PENDING
;
295 task
.context
= context
;
296 task
.task
= base::Bind(&SyncClient::PerformUpdateTask
,
297 base::Unretained(this),
299 AddTask(SyncTasks::key_type(UPDATE
, local_id
), task
, delay
);
302 void SyncClient::AddTask(const SyncTasks::key_type
& key
,
303 const SyncTask
& task
,
304 const base::TimeDelta
& delay
) {
305 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
307 SyncTasks::iterator it
= tasks_
.find(key
);
308 if (it
!= tasks_
.end()) {
309 switch (it
->second
.state
) {
311 // Activate the task.
312 it
->second
.state
= PENDING
;
315 // The same task will run, do nothing.
318 // Something has changed since the task started. Schedule rerun.
319 it
->second
.should_run_again
= true;
325 DCHECK_EQ(PENDING
, task
.state
);
326 base::MessageLoopProxy::current()->PostDelayedTask(
328 base::Bind(&SyncClient::StartTask
, weak_ptr_factory_
.GetWeakPtr(), key
),
332 void SyncClient::StartTask(const SyncTasks::key_type
& key
) {
333 ResourceEntry
* parent
= new ResourceEntry
;
334 base::PostTaskAndReplyWithResult(
335 blocking_task_runner_
.get(),
337 base::Bind(&GetParentResourceEntry
, metadata_
, key
.second
, parent
),
338 base::Bind(&SyncClient::StartTaskAfterGetParentResourceEntry
,
339 weak_ptr_factory_
.GetWeakPtr(),
341 base::Owned(parent
)));
344 void SyncClient::StartTaskAfterGetParentResourceEntry(
345 const SyncTasks::key_type
& key
,
346 const ResourceEntry
* parent
,
348 const SyncType type
= key
.first
;
349 const std::string
& local_id
= key
.second
;
350 SyncTasks::iterator it
= tasks_
.find(key
);
351 if (it
== tasks_
.end())
354 SyncTask
* task
= &it
->second
;
355 switch (task
->state
) {
359 case RUNNING
: // Do nothing.
363 if (error
!= FILE_ERROR_OK
) {
364 OnTaskComplete(type
, local_id
, error
);
368 if (type
== UPDATE
&&
369 parent
->resource_id().empty() &&
370 parent
->local_id() != util::kDriveTrashDirLocalId
) {
371 // Parent entry needs to be synced to get a resource ID.
372 // Suspend the task and register it as a dependent task of the parent.
373 const SyncTasks::key_type
key_parent(type
, parent
->local_id());
374 SyncTasks::iterator it_parent
= tasks_
.find(key_parent
);
375 if (it_parent
== tasks_
.end()) {
376 OnTaskComplete(type
, local_id
, FILE_ERROR_INVALID_OPERATION
);
377 LOG(WARNING
) << "Parent task not found: type = " << type
<< ", id = "
378 << local_id
<< ", parent_id = " << parent
->local_id();
381 task
->state
= SUSPENDED
;
382 it_parent
->second
.dependent_tasks
.push_back(key
);
387 task
->state
= RUNNING
;
388 task
->cancel_closure
= task
->task
.Run(task
->context
);
391 void SyncClient::OnGetLocalIdsOfBacklog(
392 const std::vector
<std::string
>* to_fetch
,
393 const std::vector
<std::string
>* to_update
) {
394 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
396 // Give priority to upload tasks over fetch tasks, so that dirty files are
397 // uploaded as soon as possible.
398 for (size_t i
= 0; i
< to_update
->size(); ++i
) {
399 const std::string
& local_id
= (*to_update
)[i
];
400 DVLOG(1) << "Queuing to update: " << local_id
;
401 AddUpdateTask(ClientContext(BACKGROUND
), local_id
);
404 for (size_t i
= 0; i
< to_fetch
->size(); ++i
) {
405 const std::string
& local_id
= (*to_fetch
)[i
];
406 DVLOG(1) << "Queuing to fetch: " << local_id
;
407 AddFetchTaskInternal(local_id
, delay_
);
411 void SyncClient::AddFetchTasks(const std::vector
<std::string
>* local_ids
) {
412 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
414 for (size_t i
= 0; i
< local_ids
->size(); ++i
)
415 AddFetchTask((*local_ids
)[i
]);
418 void SyncClient::OnTaskComplete(SyncType type
,
419 const std::string
& local_id
,
421 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
423 const SyncTasks::key_type
key(type
, local_id
);
424 SyncTasks::iterator it
= tasks_
.find(key
);
425 DCHECK(it
!= tasks_
.end());
427 base::TimeDelta retry_delay
= base::TimeDelta::FromSeconds(0);
431 DVLOG(1) << "Completed: type = " << type
<< ", id = " << local_id
;
433 case FILE_ERROR_ABORT
:
434 // Ignore it because this is caused by user's cancel operations.
436 case FILE_ERROR_NO_CONNECTION
:
437 // Run the task again so that we'll retry once the connection is back.
438 it
->second
.should_run_again
= true;
439 it
->second
.context
= ClientContext(BACKGROUND
);
441 case FILE_ERROR_SERVICE_UNAVAILABLE
:
442 // Run the task again so that we'll retry once the service is back.
443 it
->second
.should_run_again
= true;
444 it
->second
.context
= ClientContext(BACKGROUND
);
445 retry_delay
= long_delay_
;
446 operation_delegate_
->OnDriveSyncError(
447 file_system::DRIVE_SYNC_ERROR_SERVICE_UNAVAILABLE
, local_id
);
450 operation_delegate_
->OnDriveSyncError(
451 file_system::DRIVE_SYNC_ERROR_MISC
, local_id
);
452 LOG(WARNING
) << "Failed: type = " << type
<< ", id = " << local_id
453 << ": " << FileErrorToString(error
);
456 for (size_t i
= 0; i
< it
->second
.waiting_callbacks
.size(); ++i
) {
457 base::MessageLoopProxy::current()->PostTask(
458 FROM_HERE
, base::Bind(it
->second
.waiting_callbacks
[i
], error
));
460 it
->second
.waiting_callbacks
.clear();
462 if (it
->second
.should_run_again
) {
463 DVLOG(1) << "Running again: type = " << type
<< ", id = " << local_id
;
464 it
->second
.state
= PENDING
;
465 it
->second
.should_run_again
= false;
466 base::MessageLoopProxy::current()->PostDelayedTask(
468 base::Bind(&SyncClient::StartTask
, weak_ptr_factory_
.GetWeakPtr(), key
),
471 for (size_t i
= 0; i
< it
->second
.dependent_tasks
.size(); ++i
)
472 StartTask(it
->second
.dependent_tasks
[i
]);
477 void SyncClient::OnFetchFileComplete(const std::string
& local_id
,
479 const base::FilePath
& local_path
,
480 scoped_ptr
<ResourceEntry
> entry
) {
481 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
482 OnTaskComplete(FETCH
, local_id
, error
);
483 if (error
== FILE_ERROR_ABORT
) {
484 // If user cancels download, unpin the file so that we do not sync the file
486 base::PostTaskAndReplyWithResult(
487 blocking_task_runner_
.get(),
489 base::Bind(&FileCache::Unpin
, base::Unretained(cache_
), local_id
),
490 base::Bind(&util::EmptyFileOperationCallback
));
494 } // namespace internal