- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / drive / sync_client.cc
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.
4
5 #include "chrome/browser/chromeos/drive/sync_client.h"
6
7 #include <vector>
8
9 #include "base/bind.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/update_operation.h"
15 #include "chrome/browser/chromeos/drive/file_system_util.h"
16 #include "chrome/browser/google_apis/task_util.h"
17 #include "content/public/browser/browser_thread.h"
18
19 using content::BrowserThread;
20
21 namespace drive {
22 namespace internal {
23
24 namespace {
25
26 // The delay constant is used to delay processing a sync task. We should not
27 // process SyncTasks immediately for the following reasons:
28 //
29 // 1) For fetching, the user may accidentally click on "Make available
30 //    offline" checkbox on a file, and immediately cancel it in a second.
31 //    It's a waste to fetch the file in this case.
32 //
33 // 2) For uploading, file writing via HTML5 file system API is performed in
34 //    two steps: 1) truncate a file to 0 bytes, 2) write contents. We
35 //    shouldn't start uploading right after the step 1). Besides, the user
36 //    may edit the same file repeatedly in a short period of time.
37 //
38 // TODO(satorux): We should find a way to handle the upload case more nicely,
39 // and shorten the delay. crbug.com/134774
40 const int kDelaySeconds = 5;
41
42 // The delay constant is used to delay retrying a sync task on server errors.
43 const int kLongDelaySeconds = 600;
44
45 // Iterates cache entries and appends IDs to |to_fetch| if the file is pinned
46 // but not fetched (not present locally), or to |to_upload| if the file is dirty
47 // but not uploaded.
48 void CollectBacklog(FileCache* cache,
49                     std::vector<std::string>* to_fetch,
50                     std::vector<std::string>* to_upload) {
51   DCHECK(to_fetch);
52   DCHECK(to_upload);
53
54   scoped_ptr<FileCache::Iterator> it = cache->GetIterator();
55   for (; !it->IsAtEnd(); it->Advance()) {
56     const FileCacheEntry& cache_entry = it->GetValue();
57     const std::string& local_id = it->GetID();
58     if (cache_entry.is_pinned() && !cache_entry.is_present())
59       to_fetch->push_back(local_id);
60
61     if (cache_entry.is_dirty())
62       to_upload->push_back(local_id);
63   }
64   DCHECK(!it->HasError());
65 }
66
67 // Iterates cache entries and collects IDs of ones with obsolete cache files.
68 void CheckExistingPinnedFiles(ResourceMetadata* metadata,
69                               FileCache* cache,
70                               std::vector<std::string>* local_ids) {
71   scoped_ptr<FileCache::Iterator> it = cache->GetIterator();
72   for (; !it->IsAtEnd(); it->Advance()) {
73     const FileCacheEntry& cache_entry = it->GetValue();
74     const std::string& local_id = it->GetID();
75     if (!cache_entry.is_pinned() || !cache_entry.is_present())
76       continue;
77
78     ResourceEntry entry;
79     FileError error = metadata->GetResourceEntryById(local_id, &entry);
80     if (error != FILE_ERROR_OK) {
81       LOG(WARNING) << "Entry not found: " << local_id;
82       continue;
83     }
84
85     // If MD5s don't match, it indicates the local cache file is stale, unless
86     // the file is dirty (the MD5 is "local"). We should never re-fetch the
87     // file when we have a locally modified version.
88     if (entry.file_specific_info().md5() == cache_entry.md5() ||
89         cache_entry.is_dirty())
90       continue;
91
92     error = cache->Remove(local_id);
93     if (error != FILE_ERROR_OK) {
94       LOG(WARNING) << "Failed to remove cache entry: " << local_id;
95       continue;
96     }
97
98     error = cache->Pin(local_id);
99     if (error != FILE_ERROR_OK) {
100       LOG(WARNING) << "Failed to pin cache entry: " << local_id;
101       continue;
102     }
103
104     local_ids->push_back(local_id);
105   }
106   DCHECK(!it->HasError());
107 }
108
109 }  // namespace
110
111 SyncClient::SyncClient(base::SequencedTaskRunner* blocking_task_runner,
112                        file_system::OperationObserver* observer,
113                        JobScheduler* scheduler,
114                        ResourceMetadata* metadata,
115                        FileCache* cache,
116                        const base::FilePath& temporary_file_directory)
117     : blocking_task_runner_(blocking_task_runner),
118       metadata_(metadata),
119       cache_(cache),
120       download_operation_(new file_system::DownloadOperation(
121           blocking_task_runner,
122           observer,
123           scheduler,
124           metadata,
125           cache,
126           temporary_file_directory)),
127       update_operation_(new file_system::UpdateOperation(blocking_task_runner,
128                                                          observer,
129                                                          scheduler,
130                                                          metadata,
131                                                          cache)),
132       delay_(base::TimeDelta::FromSeconds(kDelaySeconds)),
133       long_delay_(base::TimeDelta::FromSeconds(kLongDelaySeconds)),
134       weak_ptr_factory_(this) {
135   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
136 }
137
138 SyncClient::~SyncClient() {
139   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
140 }
141
142 void SyncClient::StartProcessingBacklog() {
143   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
144
145   std::vector<std::string>* to_fetch = new std::vector<std::string>;
146   std::vector<std::string>* to_upload = new std::vector<std::string>;
147   blocking_task_runner_->PostTaskAndReply(
148       FROM_HERE,
149       base::Bind(&CollectBacklog, cache_, to_fetch, to_upload),
150       base::Bind(&SyncClient::OnGetLocalIdsOfBacklog,
151                  weak_ptr_factory_.GetWeakPtr(),
152                  base::Owned(to_fetch),
153                  base::Owned(to_upload)));
154 }
155
156 void SyncClient::StartCheckingExistingPinnedFiles() {
157   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
158
159   std::vector<std::string>* local_ids = new std::vector<std::string>;
160   blocking_task_runner_->PostTaskAndReply(
161       FROM_HERE,
162       base::Bind(&CheckExistingPinnedFiles,
163                  metadata_,
164                  cache_,
165                  local_ids),
166       base::Bind(&SyncClient::AddFetchTasks,
167                  weak_ptr_factory_.GetWeakPtr(),
168                  base::Owned(local_ids)));
169 }
170
171 void SyncClient::AddFetchTask(const std::string& local_id) {
172   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
173
174   AddTaskToQueue(FETCH, local_id, delay_);
175 }
176
177 void SyncClient::RemoveFetchTask(const std::string& local_id) {
178   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
179
180   // TODO(kinaba): Cancel tasks in JobScheduler as well. crbug.com/248856
181   pending_fetch_list_.erase(local_id);
182 }
183
184 void SyncClient::AddUploadTask(const std::string& local_id) {
185   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
186
187   AddTaskToQueue(UPLOAD, local_id, delay_);
188 }
189
190 void SyncClient::AddTaskToQueue(SyncType type,
191                                 const std::string& local_id,
192                                 const base::TimeDelta& delay) {
193   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
194
195   // If the same task is already queued, ignore this task.
196   switch (type) {
197     case FETCH:
198       if (fetch_list_.find(local_id) == fetch_list_.end()) {
199         fetch_list_.insert(local_id);
200         pending_fetch_list_.insert(local_id);
201       } else {
202         return;
203       }
204       break;
205     case UPLOAD:
206     case UPLOAD_NO_CONTENT_CHECK:
207       if (upload_list_.find(local_id) == upload_list_.end()) {
208         upload_list_.insert(local_id);
209       } else {
210         return;
211       }
212       break;
213   }
214
215   base::MessageLoopProxy::current()->PostDelayedTask(
216       FROM_HERE,
217       base::Bind(&SyncClient::StartTask,
218                  weak_ptr_factory_.GetWeakPtr(),
219                  type,
220                  local_id),
221       delay);
222 }
223
224 void SyncClient::StartTask(SyncType type, const std::string& local_id) {
225   switch (type) {
226     case FETCH:
227       // Check if the resource has been removed from the start list.
228       if (pending_fetch_list_.find(local_id) != pending_fetch_list_.end()) {
229         DVLOG(1) << "Fetching " << local_id;
230         pending_fetch_list_.erase(local_id);
231
232         download_operation_->EnsureFileDownloadedByLocalId(
233             local_id,
234             ClientContext(BACKGROUND),
235             GetFileContentInitializedCallback(),
236             google_apis::GetContentCallback(),
237             base::Bind(&SyncClient::OnFetchFileComplete,
238                        weak_ptr_factory_.GetWeakPtr(),
239                        local_id));
240       } else {
241         // Cancel the task.
242         fetch_list_.erase(local_id);
243       }
244       break;
245     case UPLOAD:
246     case UPLOAD_NO_CONTENT_CHECK:
247       DVLOG(1) << "Uploading " << local_id;
248       update_operation_->UpdateFileByLocalId(
249           local_id,
250           ClientContext(BACKGROUND),
251           type == UPLOAD ? file_system::UpdateOperation::RUN_CONTENT_CHECK
252                          : file_system::UpdateOperation::NO_CONTENT_CHECK,
253           base::Bind(&SyncClient::OnUploadFileComplete,
254                      weak_ptr_factory_.GetWeakPtr(),
255                      local_id));
256       break;
257   }
258 }
259
260 void SyncClient::OnGetLocalIdsOfBacklog(
261     const std::vector<std::string>* to_fetch,
262     const std::vector<std::string>* to_upload) {
263   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
264
265   // Give priority to upload tasks over fetch tasks, so that dirty files are
266   // uploaded as soon as possible.
267   for (size_t i = 0; i < to_upload->size(); ++i) {
268     const std::string& local_id = (*to_upload)[i];
269     DVLOG(1) << "Queuing to upload: " << local_id;
270     AddTaskToQueue(UPLOAD_NO_CONTENT_CHECK, local_id, delay_);
271   }
272
273   for (size_t i = 0; i < to_fetch->size(); ++i) {
274     const std::string& local_id = (*to_fetch)[i];
275     DVLOG(1) << "Queuing to fetch: " << local_id;
276     AddTaskToQueue(FETCH, local_id, delay_);
277   }
278 }
279
280 void SyncClient::AddFetchTasks(const std::vector<std::string>* local_ids) {
281   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
282
283   for (size_t i = 0; i < local_ids->size(); ++i)
284     AddFetchTask((*local_ids)[i]);
285 }
286
287 void SyncClient::OnFetchFileComplete(const std::string& local_id,
288                                      FileError error,
289                                      const base::FilePath& local_path,
290                                      scoped_ptr<ResourceEntry> entry) {
291   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
292
293   fetch_list_.erase(local_id);
294
295   if (error == FILE_ERROR_OK) {
296     DVLOG(1) << "Fetched " << local_id << ": " << local_path.value();
297   } else {
298     switch (error) {
299       case FILE_ERROR_ABORT:
300         // If user cancels download, unpin the file so that we do not sync the
301         // file again.
302         cache_->UnpinOnUIThread(local_id,
303                                 base::Bind(&util::EmptyFileOperationCallback));
304         break;
305       case FILE_ERROR_NO_CONNECTION:
306         // Re-queue the task so that we'll retry once the connection is back.
307         AddTaskToQueue(FETCH, local_id, delay_);
308         break;
309       case FILE_ERROR_SERVICE_UNAVAILABLE:
310         // Re-queue the task so that we'll retry once the service is back.
311         AddTaskToQueue(FETCH, local_id, long_delay_);
312         break;
313       default:
314         LOG(WARNING) << "Failed to fetch " << local_id
315                      << ": " << FileErrorToString(error);
316     }
317   }
318 }
319
320 void SyncClient::OnUploadFileComplete(const std::string& local_id,
321                                       FileError error) {
322   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
323
324   upload_list_.erase(local_id);
325
326   if (error == FILE_ERROR_OK) {
327     DVLOG(1) << "Uploaded " << local_id;
328   } else {
329     switch (error) {
330       case FILE_ERROR_NO_CONNECTION:
331         // Re-queue the task so that we'll retry once the connection is back.
332         AddTaskToQueue(UPLOAD_NO_CONTENT_CHECK, local_id, delay_);
333         break;
334       case FILE_ERROR_SERVICE_UNAVAILABLE:
335         // Re-queue the task so that we'll retry once the service is back.
336         AddTaskToQueue(UPLOAD_NO_CONTENT_CHECK, local_id, long_delay_);
337         break;
338       default:
339         LOG(WARNING) << "Failed to upload " << local_id << ": "
340                      << FileErrorToString(error);
341     }
342   }
343 }
344
345 }  // namespace internal
346 }  // namespace drive