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/stl_util.h"
8 #include "chrome/browser/extensions/extension_service.h"
9 #include "chrome/browser/extensions/extension_system.h"
10 #include "chrome/browser/profiles/profile.h"
11 #include "chrome/browser/sync_file_system/file_change.h"
12 #include "chrome/browser/sync_file_system/local/local_file_change_tracker.h"
13 #include "chrome/browser/sync_file_system/local/local_file_sync_context.h"
14 #include "chrome/browser/sync_file_system/local/sync_file_system_backend.h"
15 #include "chrome/browser/sync_file_system/local_change_processor.h"
16 #include "chrome/browser/sync_file_system/logger.h"
17 #include "chrome/browser/sync_file_system/sync_file_metadata.h"
18 #include "content/public/browser/browser_context.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/site_instance.h"
21 #include "content/public/browser/storage_partition.h"
23 #include "webkit/browser/fileapi/file_system_context.h"
24 #include "webkit/browser/fileapi/file_system_url.h"
25 #include "webkit/common/blob/scoped_file.h"
27 using content::BrowserThread
;
28 using fileapi::FileSystemURL
;
30 namespace sync_file_system
{
34 void PrepareForProcessRemoteChangeCallbackAdapter(
35 const RemoteChangeProcessor::PrepareChangeCallback
& callback
,
36 SyncStatusCode status
,
37 const LocalFileSyncInfo
& sync_file_info
,
38 webkit_blob::ScopedFile snapshot
) {
39 callback
.Run(status
, sync_file_info
.metadata
, sync_file_info
.changes
);
44 LocalFileSyncService::OriginChangeMap::OriginChangeMap()
45 : next_(change_count_map_
.end()) {}
46 LocalFileSyncService::OriginChangeMap::~OriginChangeMap() {}
48 bool LocalFileSyncService::OriginChangeMap::NextOriginToProcess(GURL
* origin
) {
50 if (change_count_map_
.empty())
52 Map::iterator begin
= next_
;
54 if (next_
== change_count_map_
.end())
55 next_
= change_count_map_
.begin();
56 DCHECK_NE(0, next_
->second
);
57 *origin
= next_
++->first
;
58 if (!ContainsKey(disabled_origins_
, *origin
))
60 } while (next_
!= begin
);
64 int64
LocalFileSyncService::OriginChangeMap::GetTotalChangeCount() const {
65 int64 num_changes
= 0;
66 for (Map::const_iterator iter
= change_count_map_
.begin();
67 iter
!= change_count_map_
.end(); ++iter
) {
68 if (ContainsKey(disabled_origins_
, iter
->first
))
70 num_changes
+= iter
->second
;
75 void LocalFileSyncService::OriginChangeMap::SetOriginChangeCount(
76 const GURL
& origin
, int64 changes
) {
78 change_count_map_
[origin
] = changes
;
81 Map::iterator found
= change_count_map_
.find(origin
);
82 if (found
!= change_count_map_
.end()) {
85 change_count_map_
.erase(found
);
89 void LocalFileSyncService::OriginChangeMap::SetOriginEnabled(
90 const GURL
& origin
, bool enabled
) {
92 disabled_origins_
.erase(origin
);
94 disabled_origins_
.insert(origin
);
97 // LocalFileSyncService -------------------------------------------------------
99 LocalFileSyncService::LocalFileSyncService(Profile
* profile
)
101 sync_context_(new LocalFileSyncContext(
103 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI
).get(),
104 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO
)
106 local_change_processor_(NULL
) {
107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
108 sync_context_
->AddOriginChangeObserver(this);
111 LocalFileSyncService::~LocalFileSyncService() {
112 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
115 void LocalFileSyncService::Shutdown() {
116 sync_context_
->RemoveOriginChangeObserver(this);
117 sync_context_
->ShutdownOnUIThread();
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
));
132 void LocalFileSyncService::AddChangeObserver(Observer
* observer
) {
133 change_observers_
.AddObserver(observer
);
136 void LocalFileSyncService::RegisterURLForWaitingSync(
137 const FileSystemURL
& url
,
138 const base::Closure
& on_syncable_callback
) {
139 sync_context_
->RegisterURLForWaitingSync(url
, on_syncable_callback
);
142 void LocalFileSyncService::ProcessLocalChange(
143 const SyncFileCallback
& callback
) {
144 // Pick an origin to process next.
146 if (!origin_change_map_
.NextOriginToProcess(&origin
)) {
147 callback
.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC
, FileSystemURL());
150 DCHECK(local_sync_callback_
.is_null());
151 DCHECK(!origin
.is_empty());
152 DCHECK(ContainsKey(origin_to_contexts_
, origin
));
154 DVLOG(1) << "Starting ProcessLocalChange";
156 local_sync_callback_
= callback
;
158 sync_context_
->GetFileForLocalSync(
159 origin_to_contexts_
[origin
],
160 base::Bind(&LocalFileSyncService::DidGetFileForLocalSync
,
164 void LocalFileSyncService::SetLocalChangeProcessor(
165 LocalChangeProcessor
* local_change_processor
) {
166 local_change_processor_
= local_change_processor
;
169 void LocalFileSyncService::SetLocalChangeProcessorCallback(
170 const GetLocalChangeProcessorCallback
& get_local_change_processor
) {
171 get_local_change_processor_
= get_local_change_processor
;
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(
180 base::Bind(callback
, SYNC_FILE_ERROR_INVALID_URL
, false));
183 sync_context_
->HasPendingLocalChanges(
184 origin_to_contexts_
[url
.origin()], url
, callback
);
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
);
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()],
200 void LocalFileSyncService::PrepareForProcessRemoteChange(
201 const FileSystemURL
& url
,
202 const PrepareChangeCallback
& callback
) {
203 DVLOG(1) << "PrepareForProcessRemoteChange: " << url
.DebugString();
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.
209 // The given url.origin() must be for valid installed app.
210 ExtensionService
* extension_service
=
211 extensions::ExtensionSystem::Get(profile_
)->extension_service();
212 const extensions::Extension
* extension
= extension_service
->GetInstalledApp(
216 logging::LOG_WARNING
,
218 "PrepareForProcessRemoteChange called for non-existing origin: %s",
219 url
.origin().spec().c_str());
221 // The extension has been uninstalled and this method is called
222 // before the remote changes for the origin are removed.
223 callback
.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC
,
224 SyncFileMetadata(), FileChangeList());
227 GURL site_url
= extension_service
->GetSiteForExtensionId(extension
->id());
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(
234 file_system_context
.get(),
235 base::Bind(&LocalFileSyncService::DidInitializeForRemoteSync
,
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
));
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());
261 sync_context_
->ApplyRemoteChange(
262 origin_to_contexts_
[url
.origin()],
263 change
, local_path
, url
,
264 base::Bind(&LocalFileSyncService::DidApplyRemoteChange
, AsWeakPtr(),
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
);
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
);
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
);
302 need_notification
= true;
303 SyncFileSystemBackend
* backend
=
304 SyncFileSystemBackend::GetBackend(origin_to_contexts_
[origin
]);
306 DCHECK(backend
->change_tracker());
307 origin_change_map_
.SetOriginChangeCount(
308 origin
, backend
->change_tracker()->num_changes());
310 if (!need_notification
)
312 int64 num_changes
= origin_change_map_
.GetTotalChangeCount();
313 FOR_EACH_OBSERVER(Observer
, change_observers_
,
314 OnLocalChangeAvailable(num_changes
));
317 void LocalFileSyncService::SetOriginEnabled(const GURL
& origin
, bool enabled
) {
318 if (!ContainsKey(origin_to_contexts_
, origin
))
320 origin_change_map_
.SetOriginEnabled(origin
, enabled
);
323 void LocalFileSyncService::DidInitializeFileSystemContext(
324 const GURL
& app_origin
,
325 fileapi::FileSystemContext
* file_system_context
,
326 const SyncStatusCallback
& callback
,
327 SyncStatusCode status
) {
328 if (status
!= SYNC_STATUS_OK
) {
329 callback
.Run(status
);
332 DCHECK(file_system_context
);
333 origin_to_contexts_
[app_origin
] = file_system_context
;
335 if (pending_origins_with_changes_
.find(app_origin
) !=
336 pending_origins_with_changes_
.end()) {
337 // We have remaining changes for the origin.
338 pending_origins_with_changes_
.erase(app_origin
);
339 SyncFileSystemBackend
* backend
=
340 SyncFileSystemBackend::GetBackend(file_system_context
);
342 DCHECK(backend
->change_tracker());
343 origin_change_map_
.SetOriginChangeCount(
344 app_origin
, backend
->change_tracker()->num_changes());
345 int64 num_changes
= origin_change_map_
.GetTotalChangeCount();
346 FOR_EACH_OBSERVER(Observer
, change_observers_
,
347 OnLocalChangeAvailable(num_changes
));
349 callback
.Run(status
);
352 void LocalFileSyncService::DidInitializeForRemoteSync(
353 const FileSystemURL
& url
,
354 fileapi::FileSystemContext
* file_system_context
,
355 const PrepareChangeCallback
& callback
,
356 SyncStatusCode status
) {
357 if (status
!= SYNC_STATUS_OK
) {
358 DVLOG(1) << "FileSystemContext initialization failed for remote sync:"
359 << url
.DebugString() << " status=" << status
360 << " (" << SyncStatusCodeToString(status
) << ")";
361 callback
.Run(status
, SyncFileMetadata(), FileChangeList());
364 origin_to_contexts_
[url
.origin()] = file_system_context
;
365 PrepareForProcessRemoteChange(url
, callback
);
368 void LocalFileSyncService::RunLocalSyncCallback(
369 SyncStatusCode status
,
370 const FileSystemURL
& url
) {
371 DVLOG(1) << "Local sync is finished with: " << status
372 << " on " << url
.DebugString();
373 DCHECK(!local_sync_callback_
.is_null());
374 SyncFileCallback callback
= local_sync_callback_
;
375 local_sync_callback_
.Reset();
376 callback
.Run(status
, url
);
379 void LocalFileSyncService::DidApplyRemoteChange(
380 const SyncStatusCallback
& callback
,
381 SyncStatusCode status
) {
382 util::Log(logging::LOG_VERBOSE
, FROM_HERE
,
383 "[Remote -> Local] ApplyRemoteChange finished --> %s",
384 SyncStatusCodeToString(status
));
385 callback
.Run(status
);
388 void LocalFileSyncService::DidGetFileForLocalSync(
389 SyncStatusCode status
,
390 const LocalFileSyncInfo
& sync_file_info
,
391 webkit_blob::ScopedFile snapshot
) {
392 DCHECK(!local_sync_callback_
.is_null());
393 if (status
!= SYNC_STATUS_OK
) {
394 RunLocalSyncCallback(status
, sync_file_info
.url
);
397 if (sync_file_info
.changes
.empty()) {
398 // There's a slight chance this could happen.
399 SyncFileCallback callback
= local_sync_callback_
;
400 local_sync_callback_
.Reset();
401 ProcessLocalChange(callback
);
405 FileChange next_change
= sync_file_info
.changes
.front();
406 DVLOG(1) << "ProcessLocalChange: " << sync_file_info
.url
.DebugString()
407 << " change:" << next_change
.DebugString();
409 GetLocalChangeProcessor(sync_file_info
.url
)->ApplyLocalChange(
411 sync_file_info
.local_file_path
,
412 sync_file_info
.metadata
,
414 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL
,
415 AsWeakPtr(), base::Passed(&snapshot
), sync_file_info
,
416 next_change
, sync_file_info
.changes
.PopAndGetNewList()));
419 void LocalFileSyncService::ProcessNextChangeForURL(
420 webkit_blob::ScopedFile snapshot
,
421 const LocalFileSyncInfo
& sync_file_info
,
422 const FileChange
& processed_change
,
423 const FileChangeList
& changes
,
424 SyncStatusCode status
) {
425 DVLOG(1) << "Processed one local change: "
426 << sync_file_info
.url
.DebugString()
427 << " change:" << processed_change
.DebugString()
428 << " status:" << status
;
430 if (status
== SYNC_STATUS_RETRY
) {
431 GetLocalChangeProcessor(sync_file_info
.url
)->ApplyLocalChange(
433 sync_file_info
.local_file_path
,
434 sync_file_info
.metadata
,
436 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL
,
437 AsWeakPtr(), base::Passed(&snapshot
),
438 sync_file_info
, processed_change
, changes
));
442 if (status
== SYNC_FILE_ERROR_NOT_FOUND
&&
443 processed_change
.change() == FileChange::FILE_CHANGE_DELETE
) {
444 // This must be ok (and could happen).
445 status
= SYNC_STATUS_OK
;
448 const FileSystemURL
& url
= sync_file_info
.url
;
449 if (status
!= SYNC_STATUS_OK
|| changes
.empty()) {
450 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
451 sync_context_
->FinalizeSnapshotSync(
452 origin_to_contexts_
[url
.origin()], url
, status
,
453 base::Bind(&LocalFileSyncService::RunLocalSyncCallback
,
454 AsWeakPtr(), status
, url
));
458 FileChange next_change
= changes
.front();
459 GetLocalChangeProcessor(url
)->ApplyLocalChange(
461 sync_file_info
.local_file_path
,
462 sync_file_info
.metadata
,
464 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL
,
465 AsWeakPtr(), base::Passed(&snapshot
), sync_file_info
,
466 next_change
, changes
.PopAndGetNewList()));
469 LocalChangeProcessor
* LocalFileSyncService::GetLocalChangeProcessor(
470 const FileSystemURL
& url
) {
471 if (!get_local_change_processor_
.is_null())
472 return get_local_change_processor_
.Run(url
.origin());
473 DCHECK(local_change_processor_
);
474 return local_change_processor_
;
477 } // namespace sync_file_system