1 // Copyright 2013 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/sync_file_system/local/local_file_sync_service.h"
6
7 #include "base/stl_util.h"
8 #include "chrome/browser/extensions/extension_util.h"
9 #include "chrome/browser/profiles/profile.h"
10 #include "chrome/browser/sync_file_system/file_change.h"
11 #include "chrome/browser/sync_file_system/local/local_file_change_tracker.h"
12 #include "chrome/browser/sync_file_system/local/local_file_sync_context.h"
13 #include "chrome/browser/sync_file_system/local/sync_file_system_backend.h"
14 #include "chrome/browser/sync_file_system/local_change_processor.h"
15 #include "chrome/browser/sync_file_system/logger.h"
16 #include "chrome/browser/sync_file_system/sync_file_metadata.h"
17 #include "content/public/browser/browser_context.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/site_instance.h"
20 #include "content/public/browser/storage_partition.h"
21 #include "extensions/browser/extension_registry.h"
22 #include "extensions/common/extension_set.h"
23 #include "url/gurl.h"
24 #include "webkit/browser/fileapi/file_system_context.h"
25 #include "webkit/browser/fileapi/file_system_url.h"
26 #include "webkit/common/blob/scoped_file.h"
27
28 using content::BrowserThread;
29 using fileapi::FileSystemURL;
30
31 namespace sync_file_system {
32
33 namespace {
34
PrepareForProcessRemoteChangeCallbackAdapter(const RemoteChangeProcessor::PrepareChangeCallback & callback,SyncStatusCode status,const LocalFileSyncInfo & sync_file_info,webkit_blob::ScopedFile snapshot)35 void PrepareForProcessRemoteChangeCallbackAdapter(
36 const RemoteChangeProcessor::PrepareChangeCallback& callback,
37 SyncStatusCode status,
38 const LocalFileSyncInfo& sync_file_info,
39 webkit_blob::ScopedFile snapshot) {
40 callback.Run(status, sync_file_info.metadata, sync_file_info.changes);
41 }
42
43 } // namespace
44
OriginChangeMap()45 LocalFileSyncService::OriginChangeMap::OriginChangeMap()
46 : next_(change_count_map_.end()) {}
~OriginChangeMap()47 LocalFileSyncService::OriginChangeMap::~OriginChangeMap() {}
48
NextOriginToProcess(GURL * origin)49 bool LocalFileSyncService::OriginChangeMap::NextOriginToProcess(GURL* origin) {
50 DCHECK(origin);
51 if (change_count_map_.empty())
52 return false;
53 Map::iterator begin = next_;
54 do {
55 if (next_ == change_count_map_.end())
56 next_ = change_count_map_.begin();
57 DCHECK_NE(0, next_->second);
58 *origin = next_++->first;
59 if (!ContainsKey(disabled_origins_, *origin))
60 return true;
61 } while (next_ != begin);
62 return false;
63 }
64
GetTotalChangeCount() const65 int64 LocalFileSyncService::OriginChangeMap::GetTotalChangeCount() const {
66 int64 num_changes = 0;
67 for (Map::const_iterator iter = change_count_map_.begin();
68 iter != change_count_map_.end(); ++iter) {
69 if (ContainsKey(disabled_origins_, iter->first))
70 continue;
71 num_changes += iter->second;
72 }
73 return num_changes;
74 }
75
SetOriginChangeCount(const GURL & origin,int64 changes)76 void LocalFileSyncService::OriginChangeMap::SetOriginChangeCount(
77 const GURL& origin, int64 changes) {
78 if (changes != 0) {
79 change_count_map_[origin] = changes;
80 return;
81 }
82 Map::iterator found = change_count_map_.find(origin);
83 if (found != change_count_map_.end()) {
84 if (next_ == found)
85 ++next_;
86 change_count_map_.erase(found);
87 }
88 }
89
SetOriginEnabled(const GURL & origin,bool enabled)90 void LocalFileSyncService::OriginChangeMap::SetOriginEnabled(
91 const GURL& origin, bool enabled) {
92 if (enabled)
93 disabled_origins_.erase(origin);
94 else
95 disabled_origins_.insert(origin);
96 }
97
98 // LocalFileSyncService -------------------------------------------------------
99
Create(Profile * profile)100 scoped_ptr<LocalFileSyncService> LocalFileSyncService::Create(
101 Profile* profile) {
102 return make_scoped_ptr(new LocalFileSyncService(profile, NULL));
103 }
104
CreateForTesting(Profile * profile,leveldb::Env * env)105 scoped_ptr<LocalFileSyncService> LocalFileSyncService::CreateForTesting(
106 Profile* profile,
107 leveldb::Env* env) {
108 return make_scoped_ptr(new LocalFileSyncService(profile, env));
109 }
110
~LocalFileSyncService()111 LocalFileSyncService::~LocalFileSyncService() {
112 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
113 }
114
Shutdown()115 void LocalFileSyncService::Shutdown() {
116 sync_context_->RemoveOriginChangeObserver(this);
117 sync_context_->ShutdownOnUIThread();
118 profile_ = NULL;
119 }
120
MaybeInitializeFileSystemContext(const GURL & app_origin,fileapi::FileSystemContext * file_system_context,const SyncStatusCallback & callback)121 void LocalFileSyncService::MaybeInitializeFileSystemContext(
122 const GURL& app_origin,
123 fileapi::FileSystemContext* file_system_context,
124 const SyncStatusCallback& callback) {
125 sync_context_->MaybeInitializeFileSystemContext(
126 app_origin, file_system_context,
127 base::Bind(&LocalFileSyncService::DidInitializeFileSystemContext,
128 AsWeakPtr(), app_origin,
129 make_scoped_refptr(file_system_context), callback));
130 }
131
AddChangeObserver(Observer * observer)132 void LocalFileSyncService::AddChangeObserver(Observer* observer) {
133 change_observers_.AddObserver(observer);
134 }
135
RegisterURLForWaitingSync(const FileSystemURL & url,const base::Closure & on_syncable_callback)136 void LocalFileSyncService::RegisterURLForWaitingSync(
137 const FileSystemURL& url,
138 const base::Closure& on_syncable_callback) {
139 sync_context_->RegisterURLForWaitingSync(url, on_syncable_callback);
140 }
141
ProcessLocalChange(const SyncFileCallback & callback)142 void LocalFileSyncService::ProcessLocalChange(
143 const SyncFileCallback& callback) {
144 // Pick an origin to process next.
145 GURL origin;
146 if (!origin_change_map_.NextOriginToProcess(&origin)) {
147 callback.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC, FileSystemURL());
148 return;
149 }
150 DCHECK(local_sync_callback_.is_null());
151 DCHECK(!origin.is_empty());
152 DCHECK(ContainsKey(origin_to_contexts_, origin));
153
154 DVLOG(1) << "Starting ProcessLocalChange";
155
156 local_sync_callback_ = callback;
157
158 sync_context_->GetFileForLocalSync(
159 origin_to_contexts_[origin],
160 base::Bind(&LocalFileSyncService::DidGetFileForLocalSync,
161 AsWeakPtr()));
162 }
163
SetLocalChangeProcessor(LocalChangeProcessor * local_change_processor)164 void LocalFileSyncService::SetLocalChangeProcessor(
165 LocalChangeProcessor* local_change_processor) {
166 local_change_processor_ = local_change_processor;
167 }
168
SetLocalChangeProcessorCallback(const GetLocalChangeProcessorCallback & get_local_change_processor)169 void LocalFileSyncService::SetLocalChangeProcessorCallback(
170 const GetLocalChangeProcessorCallback& get_local_change_processor) {
171 get_local_change_processor_ = get_local_change_processor;
172 }
173
HasPendingLocalChanges(const FileSystemURL & url,const HasPendingLocalChangeCallback & callback)174 void LocalFileSyncService::HasPendingLocalChanges(
175 const FileSystemURL& url,
176 const HasPendingLocalChangeCallback& callback) {
177 if (!ContainsKey(origin_to_contexts_, url.origin())) {
178 base::MessageLoopProxy::current()->PostTask(
179 FROM_HERE,
180 base::Bind(callback, SYNC_FILE_ERROR_INVALID_URL, false));
181 return;
182 }
183 sync_context_->HasPendingLocalChanges(
184 origin_to_contexts_[url.origin()], url, callback);
185 }
186
PromoteDemotedChanges()187 void LocalFileSyncService::PromoteDemotedChanges() {
188 for (OriginToContext::iterator iter = origin_to_contexts_.begin();
189 iter != origin_to_contexts_.end(); ++iter)
190 sync_context_->PromoteDemotedChanges(iter->first, iter->second);
191 }
192
GetLocalFileMetadata(const FileSystemURL & url,const SyncFileMetadataCallback & callback)193 void LocalFileSyncService::GetLocalFileMetadata(
194 const FileSystemURL& url, const SyncFileMetadataCallback& callback) {
195 DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
196 sync_context_->GetFileMetadata(origin_to_contexts_[url.origin()],
197 url, callback);
198 }
199
PrepareForProcessRemoteChange(const FileSystemURL & url,const PrepareChangeCallback & callback)200 void LocalFileSyncService::PrepareForProcessRemoteChange(
201 const FileSystemURL& url,
202 const PrepareChangeCallback& callback) {
203 DVLOG(1) << "PrepareForProcessRemoteChange: " << url.DebugString();
204
205 if (!ContainsKey(origin_to_contexts_, url.origin())) {
206 // This could happen if a remote sync is triggered for the app that hasn't
207 // been initialized in this service.
208 DCHECK(profile_);
209 // The given url.origin() must be for valid installed app.
210 const extensions::Extension* extension =
211 extensions::ExtensionRegistry::Get(profile_)
212 ->enabled_extensions().GetAppByURL(url.origin());
213 if (!extension) {
214 util::Log(
215 logging::LOG_WARNING,
216 FROM_HERE,
217 "PrepareForProcessRemoteChange called for non-existing origin: %s",
218 url.origin().spec().c_str());
219
220 // The extension has been uninstalled and this method is called
221 // before the remote changes for the origin are removed.
222 callback.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC,
223 SyncFileMetadata(), FileChangeList());
224 return;
225 }
226 GURL site_url =
227 extensions::util::GetSiteForExtensionId(extension->id(), profile_);
228 DCHECK(!site_url.is_empty());
229 scoped_refptr<fileapi::FileSystemContext> file_system_context =
230 content::BrowserContext::GetStoragePartitionForSite(
231 profile_, site_url)->GetFileSystemContext();
232 MaybeInitializeFileSystemContext(
233 url.origin(),
234 file_system_context.get(),
235 base::Bind(&LocalFileSyncService::DidInitializeForRemoteSync,
236 AsWeakPtr(),
237 url,
238 file_system_context,
239 callback));
240 return;
241 }
242
243 DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
244 sync_context_->PrepareForSync(
245 origin_to_contexts_[url.origin()], url,
246 LocalFileSyncContext::SYNC_EXCLUSIVE,
247 base::Bind(&PrepareForProcessRemoteChangeCallbackAdapter, callback));
248 }
249
ApplyRemoteChange(const FileChange & change,const base::FilePath & local_path,const FileSystemURL & url,const SyncStatusCallback & callback)250 void LocalFileSyncService::ApplyRemoteChange(
251 const FileChange& change,
252 const base::FilePath& local_path,
253 const FileSystemURL& url,
254 const SyncStatusCallback& callback) {
255 DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
256 util::Log(logging::LOG_VERBOSE, FROM_HERE,
257 "[Remote -> Local] ApplyRemoteChange: %s on %s",
258 change.DebugString().c_str(),
259 url.DebugString().c_str());
260
261 sync_context_->ApplyRemoteChange(
262 origin_to_contexts_[url.origin()],
263 change, local_path, url,
264 base::Bind(&LocalFileSyncService::DidApplyRemoteChange, AsWeakPtr(),
265 callback));
266 }
267
FinalizeRemoteSync(const FileSystemURL & url,bool clear_local_changes,const base::Closure & completion_callback)268 void LocalFileSyncService::FinalizeRemoteSync(
269 const FileSystemURL& url,
270 bool clear_local_changes,
271 const base::Closure& completion_callback) {
272 DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
273 sync_context_->FinalizeExclusiveSync(
274 origin_to_contexts_[url.origin()],
275 url, clear_local_changes, completion_callback);
276 }
277
RecordFakeLocalChange(const FileSystemURL & url,const FileChange & change,const SyncStatusCallback & callback)278 void LocalFileSyncService::RecordFakeLocalChange(
279 const FileSystemURL& url,
280 const FileChange& change,
281 const SyncStatusCallback& callback) {
282 DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
283 sync_context_->RecordFakeLocalChange(origin_to_contexts_[url.origin()],
284 url, change, callback);
285 }
286
OnChangesAvailableInOrigins(const std::set<GURL> & origins)287 void LocalFileSyncService::OnChangesAvailableInOrigins(
288 const std::set<GURL>& origins) {
289 bool need_notification = false;
290 for (std::set<GURL>::const_iterator iter = origins.begin();
291 iter != origins.end(); ++iter) {
292 const GURL& origin = *iter;
293 if (!ContainsKey(origin_to_contexts_, origin)) {
294 // This could happen if this is called for apps/origins that haven't
295 // been initialized yet, or for apps/origins that are disabled.
296 // (Local change tracker could call this for uninitialized origins
297 // while it's reading dirty files from the database in the
298 // initialization phase.)
299 pending_origins_with_changes_.insert(origin);
300 continue;
301 }
302 need_notification = true;
303 SyncFileSystemBackend* backend =
304 SyncFileSystemBackend::GetBackend(origin_to_contexts_[origin]);
305 DCHECK(backend);
306 DCHECK(backend->change_tracker());
307 origin_change_map_.SetOriginChangeCount(
308 origin, backend->change_tracker()->num_changes());
309 }
310 if (!need_notification)
311 return;
312 int64 num_changes = origin_change_map_.GetTotalChangeCount();
313 FOR_EACH_OBSERVER(Observer, change_observers_,
314 OnLocalChangeAvailable(num_changes));
315 }
316
SetOriginEnabled(const GURL & origin,bool enabled)317 void LocalFileSyncService::SetOriginEnabled(const GURL& origin, bool enabled) {
318 if (!ContainsKey(origin_to_contexts_, origin))
319 return;
320 origin_change_map_.SetOriginEnabled(origin, enabled);
321 }
322
LocalFileSyncService(Profile * profile,leveldb::Env * env_override)323 LocalFileSyncService::LocalFileSyncService(Profile* profile,
324 leveldb::Env* env_override)
325 : profile_(profile),
326 sync_context_(new LocalFileSyncContext(
327 profile_->GetPath(),
328 env_override,
329 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI).get(),
330 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO)
331 .get())),
332 local_change_processor_(NULL) {
333 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
334 sync_context_->AddOriginChangeObserver(this);
335 }
336
DidInitializeFileSystemContext(const GURL & app_origin,fileapi::FileSystemContext * file_system_context,const SyncStatusCallback & callback,SyncStatusCode status)337 void LocalFileSyncService::DidInitializeFileSystemContext(
338 const GURL& app_origin,
339 fileapi::FileSystemContext* file_system_context,
340 const SyncStatusCallback& callback,
341 SyncStatusCode status) {
342 if (status != SYNC_STATUS_OK) {
343 callback.Run(status);
344 return;
345 }
346 DCHECK(file_system_context);
347 origin_to_contexts_[app_origin] = file_system_context;
348
349 if (pending_origins_with_changes_.find(app_origin) !=
350 pending_origins_with_changes_.end()) {
351 // We have remaining changes for the origin.
352 pending_origins_with_changes_.erase(app_origin);
353 SyncFileSystemBackend* backend =
354 SyncFileSystemBackend::GetBackend(file_system_context);
355 DCHECK(backend);
356 DCHECK(backend->change_tracker());
357 origin_change_map_.SetOriginChangeCount(
358 app_origin, backend->change_tracker()->num_changes());
359 int64 num_changes = origin_change_map_.GetTotalChangeCount();
360 FOR_EACH_OBSERVER(Observer, change_observers_,
361 OnLocalChangeAvailable(num_changes));
362 }
363 callback.Run(status);
364 }
365
DidInitializeForRemoteSync(const FileSystemURL & url,fileapi::FileSystemContext * file_system_context,const PrepareChangeCallback & callback,SyncStatusCode status)366 void LocalFileSyncService::DidInitializeForRemoteSync(
367 const FileSystemURL& url,
368 fileapi::FileSystemContext* file_system_context,
369 const PrepareChangeCallback& callback,
370 SyncStatusCode status) {
371 if (status != SYNC_STATUS_OK) {
372 DVLOG(1) << "FileSystemContext initialization failed for remote sync:"
373 << url.DebugString() << " status=" << status
374 << " (" << SyncStatusCodeToString(status) << ")";
375 callback.Run(status, SyncFileMetadata(), FileChangeList());
376 return;
377 }
378 origin_to_contexts_[url.origin()] = file_system_context;
379 PrepareForProcessRemoteChange(url, callback);
380 }
381
RunLocalSyncCallback(SyncStatusCode status,const FileSystemURL & url)382 void LocalFileSyncService::RunLocalSyncCallback(
383 SyncStatusCode status,
384 const FileSystemURL& url) {
385 DVLOG(1) << "Local sync is finished with: " << status
386 << " on " << url.DebugString();
387 DCHECK(!local_sync_callback_.is_null());
388 SyncFileCallback callback = local_sync_callback_;
389 local_sync_callback_.Reset();
390 callback.Run(status, url);
391 }
392
DidApplyRemoteChange(const SyncStatusCallback & callback,SyncStatusCode status)393 void LocalFileSyncService::DidApplyRemoteChange(
394 const SyncStatusCallback& callback,
395 SyncStatusCode status) {
396 util::Log(logging::LOG_VERBOSE, FROM_HERE,
397 "[Remote -> Local] ApplyRemoteChange finished --> %s",
398 SyncStatusCodeToString(status));
399 callback.Run(status);
400 }
401
DidGetFileForLocalSync(SyncStatusCode status,const LocalFileSyncInfo & sync_file_info,webkit_blob::ScopedFile snapshot)402 void LocalFileSyncService::DidGetFileForLocalSync(
403 SyncStatusCode status,
404 const LocalFileSyncInfo& sync_file_info,
405 webkit_blob::ScopedFile snapshot) {
406 DCHECK(!local_sync_callback_.is_null());
407 if (status != SYNC_STATUS_OK) {
408 RunLocalSyncCallback(status, sync_file_info.url);
409 return;
410 }
411 if (sync_file_info.changes.empty()) {
412 // There's a slight chance this could happen.
413 SyncFileCallback callback = local_sync_callback_;
414 local_sync_callback_.Reset();
415 ProcessLocalChange(callback);
416 return;
417 }
418
419 FileChange next_change = sync_file_info.changes.front();
420 DVLOG(1) << "ProcessLocalChange: " << sync_file_info.url.DebugString()
421 << " change:" << next_change.DebugString();
422
423 GetLocalChangeProcessor(sync_file_info.url)->ApplyLocalChange(
424 next_change,
425 sync_file_info.local_file_path,
426 sync_file_info.metadata,
427 sync_file_info.url,
428 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL,
429 AsWeakPtr(), base::Passed(&snapshot), sync_file_info,
430 next_change, sync_file_info.changes.PopAndGetNewList()));
431 }
432
ProcessNextChangeForURL(webkit_blob::ScopedFile snapshot,const LocalFileSyncInfo & sync_file_info,const FileChange & processed_change,const FileChangeList & changes,SyncStatusCode status)433 void LocalFileSyncService::ProcessNextChangeForURL(
434 webkit_blob::ScopedFile snapshot,
435 const LocalFileSyncInfo& sync_file_info,
436 const FileChange& processed_change,
437 const FileChangeList& changes,
438 SyncStatusCode status) {
439 DVLOG(1) << "Processed one local change: "
440 << sync_file_info.url.DebugString()
441 << " change:" << processed_change.DebugString()
442 << " status:" << status;
443
444 if (status == SYNC_STATUS_RETRY) {
445 GetLocalChangeProcessor(sync_file_info.url)->ApplyLocalChange(
446 processed_change,
447 sync_file_info.local_file_path,
448 sync_file_info.metadata,
449 sync_file_info.url,
450 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL,
451 AsWeakPtr(), base::Passed(&snapshot),
452 sync_file_info, processed_change, changes));
453 return;
454 }
455
456 if (status == SYNC_FILE_ERROR_NOT_FOUND &&
457 processed_change.change() == FileChange::FILE_CHANGE_DELETE) {
458 // This must be ok (and could happen).
459 status = SYNC_STATUS_OK;
460 }
461
462 const FileSystemURL& url = sync_file_info.url;
463 if (status != SYNC_STATUS_OK || changes.empty()) {
464 DCHECK(ContainsKey(origin_to_contexts_, url.origin()));
465 sync_context_->FinalizeSnapshotSync(
466 origin_to_contexts_[url.origin()], url, status,
467 base::Bind(&LocalFileSyncService::RunLocalSyncCallback,
468 AsWeakPtr(), status, url));
469 return;
470 }
471
472 FileChange next_change = changes.front();
473 GetLocalChangeProcessor(url)->ApplyLocalChange(
474 changes.front(),
475 sync_file_info.local_file_path,
476 sync_file_info.metadata,
477 url,
478 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL,
479 AsWeakPtr(), base::Passed(&snapshot), sync_file_info,
480 next_change, changes.PopAndGetNewList()));
481 }
482
GetLocalChangeProcessor(const FileSystemURL & url)483 LocalChangeProcessor* LocalFileSyncService::GetLocalChangeProcessor(
484 const FileSystemURL& url) {
485 if (!get_local_change_processor_.is_null())
486 return get_local_change_processor_.Run(url.origin());
487 DCHECK(local_change_processor_);
488 return local_change_processor_;
489 }
490
491 } // namespace sync_file_system
492