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.
5 #include "chrome/browser/sync_file_system/local/local_file_sync_service.h"
7 #include "base/single_thread_task_runner.h"
8 #include "base/stl_util.h"
9 #include "base/thread_task_runner_handle.h"
10 #include "chrome/browser/extensions/extension_util.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/sync_file_system/file_change.h"
13 #include "chrome/browser/sync_file_system/local/local_file_change_tracker.h"
14 #include "chrome/browser/sync_file_system/local/local_file_sync_context.h"
15 #include "chrome/browser/sync_file_system/local/sync_file_system_backend.h"
16 #include "chrome/browser/sync_file_system/local_change_processor.h"
17 #include "chrome/browser/sync_file_system/logger.h"
18 #include "chrome/browser/sync_file_system/sync_file_metadata.h"
19 #include "content/public/browser/browser_context.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/browser/site_instance.h"
22 #include "content/public/browser/storage_partition.h"
23 #include "extensions/browser/extension_registry.h"
24 #include "extensions/common/extension_set.h"
25 #include "storage/browser/blob/scoped_file.h"
26 #include "storage/browser/fileapi/file_system_context.h"
27 #include "storage/browser/fileapi/file_system_url.h"
30 using content::BrowserThread
;
31 using storage::FileSystemURL
;
33 namespace sync_file_system
{
37 void PrepareForProcessRemoteChangeCallbackAdapter(
38 const RemoteChangeProcessor::PrepareChangeCallback
& callback
,
39 SyncStatusCode status
,
40 const LocalFileSyncInfo
& sync_file_info
,
41 storage::ScopedFile snapshot
) {
42 callback
.Run(status
, sync_file_info
.metadata
, sync_file_info
.changes
);
45 void InvokeCallbackOnNthInvocation(int* count
, const base::Closure
& callback
) {
53 LocalFileSyncService::OriginChangeMap::OriginChangeMap()
54 : next_(change_count_map_
.end()) {}
55 LocalFileSyncService::OriginChangeMap::~OriginChangeMap() {}
57 bool LocalFileSyncService::OriginChangeMap::NextOriginToProcess(GURL
* origin
) {
59 if (change_count_map_
.empty())
61 Map::iterator begin
= next_
;
63 if (next_
== change_count_map_
.end())
64 next_
= change_count_map_
.begin();
65 DCHECK_NE(0, next_
->second
);
66 *origin
= next_
++->first
;
67 if (!ContainsKey(disabled_origins_
, *origin
))
69 } while (next_
!= begin
);
73 int64
LocalFileSyncService::OriginChangeMap::GetTotalChangeCount() const {
74 int64 num_changes
= 0;
75 for (Map::const_iterator iter
= change_count_map_
.begin();
76 iter
!= change_count_map_
.end(); ++iter
) {
77 if (ContainsKey(disabled_origins_
, iter
->first
))
79 num_changes
+= iter
->second
;
84 void LocalFileSyncService::OriginChangeMap::SetOriginChangeCount(
85 const GURL
& origin
, int64 changes
) {
87 change_count_map_
[origin
] = changes
;
90 Map::iterator found
= change_count_map_
.find(origin
);
91 if (found
!= change_count_map_
.end()) {
94 change_count_map_
.erase(found
);
98 void LocalFileSyncService::OriginChangeMap::SetOriginEnabled(
99 const GURL
& origin
, bool enabled
) {
101 disabled_origins_
.erase(origin
);
103 disabled_origins_
.insert(origin
);
106 // LocalFileSyncService -------------------------------------------------------
108 scoped_ptr
<LocalFileSyncService
> LocalFileSyncService::Create(
110 return make_scoped_ptr(new LocalFileSyncService(profile
, nullptr));
113 scoped_ptr
<LocalFileSyncService
> LocalFileSyncService::CreateForTesting(
116 scoped_ptr
<LocalFileSyncService
> sync_service(
117 new LocalFileSyncService(profile
, env
));
118 sync_service
->sync_context_
->set_mock_notify_changes_duration_in_sec(0);
119 return sync_service
.Pass();
122 LocalFileSyncService::~LocalFileSyncService() {
123 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
126 void LocalFileSyncService::Shutdown() {
127 sync_context_
->RemoveOriginChangeObserver(this);
128 sync_context_
->ShutdownOnUIThread();
132 void LocalFileSyncService::MaybeInitializeFileSystemContext(
133 const GURL
& app_origin
,
134 storage::FileSystemContext
* file_system_context
,
135 const SyncStatusCallback
& callback
) {
136 sync_context_
->MaybeInitializeFileSystemContext(
137 app_origin
, file_system_context
,
138 base::Bind(&LocalFileSyncService::DidInitializeFileSystemContext
,
139 AsWeakPtr(), app_origin
,
140 make_scoped_refptr(file_system_context
), callback
));
143 void LocalFileSyncService::AddChangeObserver(Observer
* observer
) {
144 change_observers_
.AddObserver(observer
);
147 void LocalFileSyncService::RegisterURLForWaitingSync(
148 const FileSystemURL
& url
,
149 const base::Closure
& on_syncable_callback
) {
150 sync_context_
->RegisterURLForWaitingSync(url
, on_syncable_callback
);
153 void LocalFileSyncService::ProcessLocalChange(
154 const SyncFileCallback
& callback
) {
155 // Pick an origin to process next.
157 if (!origin_change_map_
.NextOriginToProcess(&origin
)) {
158 callback
.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC
, FileSystemURL());
161 DCHECK(!origin
.is_empty());
162 DCHECK(ContainsKey(origin_to_contexts_
, origin
));
164 DVLOG(1) << "Starting ProcessLocalChange";
166 sync_context_
->GetFileForLocalSync(
167 origin_to_contexts_
[origin
],
168 base::Bind(&LocalFileSyncService::DidGetFileForLocalSync
,
169 AsWeakPtr(), callback
));
172 void LocalFileSyncService::SetLocalChangeProcessor(
173 LocalChangeProcessor
* local_change_processor
) {
174 local_change_processor_
= local_change_processor
;
177 void LocalFileSyncService::SetLocalChangeProcessorCallback(
178 const GetLocalChangeProcessorCallback
& get_local_change_processor
) {
179 get_local_change_processor_
= get_local_change_processor
;
182 void LocalFileSyncService::HasPendingLocalChanges(
183 const FileSystemURL
& url
,
184 const HasPendingLocalChangeCallback
& callback
) {
185 if (!ContainsKey(origin_to_contexts_
, url
.origin())) {
186 base::ThreadTaskRunnerHandle::Get()->PostTask(
188 base::Bind(callback
, SYNC_FILE_ERROR_INVALID_URL
, false));
191 sync_context_
->HasPendingLocalChanges(
192 origin_to_contexts_
[url
.origin()], url
, callback
);
195 void LocalFileSyncService::PromoteDemotedChanges(
196 const base::Closure
& callback
) {
197 if (origin_to_contexts_
.empty()) {
202 base::Closure completion_callback
=
203 base::Bind(&InvokeCallbackOnNthInvocation
,
204 base::Owned(new int(origin_to_contexts_
.size() + 1)),
206 for (OriginToContext::iterator iter
= origin_to_contexts_
.begin();
207 iter
!= origin_to_contexts_
.end(); ++iter
)
208 sync_context_
->PromoteDemotedChanges(iter
->first
, iter
->second
,
209 completion_callback
);
210 completion_callback
.Run();
213 void LocalFileSyncService::GetLocalFileMetadata(
214 const FileSystemURL
& url
, const SyncFileMetadataCallback
& callback
) {
215 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
216 sync_context_
->GetFileMetadata(origin_to_contexts_
[url
.origin()],
220 void LocalFileSyncService::PrepareForProcessRemoteChange(
221 const FileSystemURL
& url
,
222 const PrepareChangeCallback
& callback
) {
223 DVLOG(1) << "PrepareForProcessRemoteChange: " << url
.DebugString();
225 if (!ContainsKey(origin_to_contexts_
, url
.origin())) {
226 // This could happen if a remote sync is triggered for the app that hasn't
227 // been initialized in this service.
229 // The given url.origin() must be for valid installed app.
230 const extensions::Extension
* extension
=
231 extensions::ExtensionRegistry::Get(profile_
)
232 ->enabled_extensions().GetAppByURL(url
.origin());
235 logging::LOG_WARNING
,
237 "PrepareForProcessRemoteChange called for non-existing origin: %s",
238 url
.origin().spec().c_str());
240 // The extension has been uninstalled and this method is called
241 // before the remote changes for the origin are removed.
242 callback
.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC
,
243 SyncFileMetadata(), FileChangeList());
247 extensions::util::GetSiteForExtensionId(extension
->id(), profile_
);
248 DCHECK(!site_url
.is_empty());
249 scoped_refptr
<storage::FileSystemContext
> file_system_context
=
250 content::BrowserContext::GetStoragePartitionForSite(profile_
, site_url
)
251 ->GetFileSystemContext();
252 MaybeInitializeFileSystemContext(
254 file_system_context
.get(),
255 base::Bind(&LocalFileSyncService::DidInitializeForRemoteSync
,
263 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
264 sync_context_
->PrepareForSync(
265 origin_to_contexts_
[url
.origin()], url
,
266 LocalFileSyncContext::SYNC_EXCLUSIVE
,
267 base::Bind(&PrepareForProcessRemoteChangeCallbackAdapter
, callback
));
270 void LocalFileSyncService::ApplyRemoteChange(
271 const FileChange
& change
,
272 const base::FilePath
& local_path
,
273 const FileSystemURL
& url
,
274 const SyncStatusCallback
& callback
) {
275 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
276 util::Log(logging::LOG_VERBOSE
, FROM_HERE
,
277 "[Remote -> Local] ApplyRemoteChange: %s on %s",
278 change
.DebugString().c_str(),
279 url
.DebugString().c_str());
281 sync_context_
->ApplyRemoteChange(
282 origin_to_contexts_
[url
.origin()],
283 change
, local_path
, url
,
284 base::Bind(&LocalFileSyncService::DidApplyRemoteChange
, AsWeakPtr(),
288 void LocalFileSyncService::FinalizeRemoteSync(
289 const FileSystemURL
& url
,
290 bool clear_local_changes
,
291 const base::Closure
& completion_callback
) {
292 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
293 sync_context_
->FinalizeExclusiveSync(
294 origin_to_contexts_
[url
.origin()],
295 url
, clear_local_changes
, completion_callback
);
298 void LocalFileSyncService::RecordFakeLocalChange(
299 const FileSystemURL
& url
,
300 const FileChange
& change
,
301 const SyncStatusCallback
& callback
) {
302 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
303 sync_context_
->RecordFakeLocalChange(origin_to_contexts_
[url
.origin()],
304 url
, change
, callback
);
307 void LocalFileSyncService::OnChangesAvailableInOrigins(
308 const std::set
<GURL
>& origins
) {
309 bool need_notification
= false;
310 for (std::set
<GURL
>::const_iterator iter
= origins
.begin();
311 iter
!= origins
.end(); ++iter
) {
312 const GURL
& origin
= *iter
;
313 if (!ContainsKey(origin_to_contexts_
, origin
)) {
314 // This could happen if this is called for apps/origins that haven't
315 // been initialized yet, or for apps/origins that are disabled.
316 // (Local change tracker could call this for uninitialized origins
317 // while it's reading dirty files from the database in the
318 // initialization phase.)
319 pending_origins_with_changes_
.insert(origin
);
322 need_notification
= true;
323 SyncFileSystemBackend
* backend
=
324 SyncFileSystemBackend::GetBackend(origin_to_contexts_
[origin
]);
326 DCHECK(backend
->change_tracker());
327 origin_change_map_
.SetOriginChangeCount(
328 origin
, backend
->change_tracker()->num_changes());
330 if (!need_notification
)
332 int64 num_changes
= origin_change_map_
.GetTotalChangeCount();
333 FOR_EACH_OBSERVER(Observer
, change_observers_
,
334 OnLocalChangeAvailable(num_changes
));
337 void LocalFileSyncService::SetOriginEnabled(const GURL
& origin
, bool enabled
) {
338 if (!ContainsKey(origin_to_contexts_
, origin
))
340 origin_change_map_
.SetOriginEnabled(origin
, enabled
);
343 LocalFileSyncService::LocalFileSyncService(Profile
* profile
,
344 leveldb::Env
* env_override
)
346 sync_context_(new LocalFileSyncContext(
349 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI
).get(),
350 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO
)
352 local_change_processor_(nullptr) {
353 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
354 sync_context_
->AddOriginChangeObserver(this);
357 void LocalFileSyncService::DidInitializeFileSystemContext(
358 const GURL
& app_origin
,
359 storage::FileSystemContext
* file_system_context
,
360 const SyncStatusCallback
& callback
,
361 SyncStatusCode status
) {
362 if (status
!= SYNC_STATUS_OK
) {
363 callback
.Run(status
);
366 DCHECK(file_system_context
);
367 origin_to_contexts_
[app_origin
] = file_system_context
;
369 if (pending_origins_with_changes_
.find(app_origin
) !=
370 pending_origins_with_changes_
.end()) {
371 // We have remaining changes for the origin.
372 pending_origins_with_changes_
.erase(app_origin
);
373 SyncFileSystemBackend
* backend
=
374 SyncFileSystemBackend::GetBackend(file_system_context
);
376 DCHECK(backend
->change_tracker());
377 origin_change_map_
.SetOriginChangeCount(
378 app_origin
, backend
->change_tracker()->num_changes());
379 int64 num_changes
= origin_change_map_
.GetTotalChangeCount();
380 FOR_EACH_OBSERVER(Observer
, change_observers_
,
381 OnLocalChangeAvailable(num_changes
));
383 callback
.Run(status
);
386 void LocalFileSyncService::DidInitializeForRemoteSync(
387 const FileSystemURL
& url
,
388 storage::FileSystemContext
* file_system_context
,
389 const PrepareChangeCallback
& callback
,
390 SyncStatusCode status
) {
391 if (status
!= SYNC_STATUS_OK
) {
392 DVLOG(1) << "FileSystemContext initialization failed for remote sync:"
393 << url
.DebugString() << " status=" << status
394 << " (" << SyncStatusCodeToString(status
) << ")";
395 callback
.Run(status
, SyncFileMetadata(), FileChangeList());
398 origin_to_contexts_
[url
.origin()] = file_system_context
;
399 PrepareForProcessRemoteChange(url
, callback
);
402 void LocalFileSyncService::DidApplyRemoteChange(
403 const SyncStatusCallback
& callback
,
404 SyncStatusCode status
) {
405 util::Log(logging::LOG_VERBOSE
, FROM_HERE
,
406 "[Remote -> Local] ApplyRemoteChange finished --> %s",
407 SyncStatusCodeToString(status
));
408 callback
.Run(status
);
411 void LocalFileSyncService::DidGetFileForLocalSync(
412 const SyncFileCallback
& callback
,
413 SyncStatusCode status
,
414 const LocalFileSyncInfo
& sync_file_info
,
415 storage::ScopedFile snapshot
) {
416 if (status
!= SYNC_STATUS_OK
) {
417 callback
.Run(status
, sync_file_info
.url
);
420 if (sync_file_info
.changes
.empty()) {
421 // There's a slight chance this could happen.
422 ProcessLocalChange(callback
);
426 FileChange next_change
= sync_file_info
.changes
.front();
427 DVLOG(1) << "ProcessLocalChange: " << sync_file_info
.url
.DebugString()
428 << " change:" << next_change
.DebugString();
430 GetLocalChangeProcessor(sync_file_info
.url
)->ApplyLocalChange(
432 sync_file_info
.local_file_path
,
433 sync_file_info
.metadata
,
435 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL
,
436 AsWeakPtr(), callback
,
437 base::Passed(&snapshot
), sync_file_info
,
438 next_change
, sync_file_info
.changes
.PopAndGetNewList()));
441 void LocalFileSyncService::ProcessNextChangeForURL(
442 const SyncFileCallback
& callback
,
443 storage::ScopedFile snapshot
,
444 const LocalFileSyncInfo
& sync_file_info
,
445 const FileChange
& processed_change
,
446 const FileChangeList
& changes
,
447 SyncStatusCode status
) {
448 DVLOG(1) << "Processed one local change: "
449 << sync_file_info
.url
.DebugString()
450 << " change:" << processed_change
.DebugString()
451 << " status:" << status
;
453 if (status
== SYNC_STATUS_RETRY
) {
454 GetLocalChangeProcessor(sync_file_info
.url
)->ApplyLocalChange(
456 sync_file_info
.local_file_path
,
457 sync_file_info
.metadata
,
459 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL
,
460 AsWeakPtr(), callback
, base::Passed(&snapshot
),
461 sync_file_info
, processed_change
, changes
));
465 if (status
== SYNC_FILE_ERROR_NOT_FOUND
&&
466 processed_change
.change() == FileChange::FILE_CHANGE_DELETE
) {
467 // This must be ok (and could happen).
468 status
= SYNC_STATUS_OK
;
471 const FileSystemURL
& url
= sync_file_info
.url
;
472 if (status
!= SYNC_STATUS_OK
|| changes
.empty()) {
473 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
474 sync_context_
->FinalizeSnapshotSync(
475 origin_to_contexts_
[url
.origin()], url
, status
,
476 base::Bind(callback
, status
, url
));
480 FileChange next_change
= changes
.front();
481 GetLocalChangeProcessor(url
)->ApplyLocalChange(
483 sync_file_info
.local_file_path
,
484 sync_file_info
.metadata
,
486 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL
,
487 AsWeakPtr(), callback
,
488 base::Passed(&snapshot
), sync_file_info
,
489 next_change
, changes
.PopAndGetNewList()));
492 LocalChangeProcessor
* LocalFileSyncService::GetLocalChangeProcessor(
493 const FileSystemURL
& url
) {
494 if (!get_local_change_processor_
.is_null())
495 return get_local_change_processor_
.Run(url
.origin());
496 DCHECK(local_change_processor_
);
497 return local_change_processor_
;
500 } // namespace sync_file_system