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/sync_file_system/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/local_change_processor.h"
12 #include "chrome/browser/sync_file_system/logger.h"
13 #include "content/public/browser/browser_context.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "content/public/browser/site_instance.h"
16 #include "content/public/browser/storage_partition.h"
18 #include "webkit/browser/fileapi/file_system_context.h"
19 #include "webkit/browser/fileapi/file_system_url.h"
20 #include "webkit/browser/fileapi/syncable/file_change.h"
21 #include "webkit/browser/fileapi/syncable/local_file_change_tracker.h"
22 #include "webkit/browser/fileapi/syncable/local_file_sync_context.h"
23 #include "webkit/browser/fileapi/syncable/sync_file_metadata.h"
25 using content::BrowserThread
;
26 using fileapi::FileSystemURL
;
28 namespace sync_file_system
{
32 void PrepareForProcessRemoteChangeCallbackAdapter(
33 const RemoteChangeProcessor::PrepareChangeCallback
& callback
,
34 SyncStatusCode status
,
35 const LocalFileSyncInfo
& sync_file_info
) {
36 callback
.Run(status
, sync_file_info
.metadata
, sync_file_info
.changes
);
41 LocalFileSyncService::OriginChangeMap::OriginChangeMap()
42 : next_(change_count_map_
.end()) {}
43 LocalFileSyncService::OriginChangeMap::~OriginChangeMap() {}
45 bool LocalFileSyncService::OriginChangeMap::NextOriginToProcess(GURL
* origin
) {
47 if (change_count_map_
.empty())
49 Map::iterator begin
= next_
;
51 if (next_
== change_count_map_
.end())
52 next_
= change_count_map_
.begin();
53 DCHECK_NE(0, next_
->second
);
54 *origin
= next_
++->first
;
55 if (!ContainsKey(disabled_origins_
, *origin
))
57 } while (next_
!= begin
);
61 int64
LocalFileSyncService::OriginChangeMap::GetTotalChangeCount() const {
62 int64 num_changes
= 0;
63 for (Map::const_iterator iter
= change_count_map_
.begin();
64 iter
!= change_count_map_
.end(); ++iter
) {
65 if (ContainsKey(disabled_origins_
, iter
->first
))
67 num_changes
+= iter
->second
;
72 void LocalFileSyncService::OriginChangeMap::SetOriginChangeCount(
73 const GURL
& origin
, int64 changes
) {
75 change_count_map_
[origin
] = changes
;
78 Map::iterator found
= change_count_map_
.find(origin
);
79 if (found
!= change_count_map_
.end()) {
82 change_count_map_
.erase(found
);
86 void LocalFileSyncService::OriginChangeMap::SetOriginEnabled(
87 const GURL
& origin
, bool enabled
) {
89 disabled_origins_
.erase(origin
);
91 disabled_origins_
.insert(origin
);
94 // LocalFileSyncService -------------------------------------------------------
96 LocalFileSyncService::LocalFileSyncService(Profile
* profile
)
98 sync_context_(new LocalFileSyncContext(
99 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI
).get(),
100 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO
)
102 local_change_processor_(NULL
) {
103 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
104 sync_context_
->AddOriginChangeObserver(this);
107 LocalFileSyncService::~LocalFileSyncService() {
108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
111 void LocalFileSyncService::Shutdown() {
112 sync_context_
->RemoveOriginChangeObserver(this);
113 sync_context_
->ShutdownOnUIThread();
117 void LocalFileSyncService::MaybeInitializeFileSystemContext(
118 const GURL
& app_origin
,
119 fileapi::FileSystemContext
* file_system_context
,
120 const SyncStatusCallback
& callback
) {
121 sync_context_
->MaybeInitializeFileSystemContext(
122 app_origin
, file_system_context
,
123 base::Bind(&LocalFileSyncService::DidInitializeFileSystemContext
,
124 AsWeakPtr(), app_origin
,
125 make_scoped_refptr(file_system_context
), callback
));
128 void LocalFileSyncService::AddChangeObserver(Observer
* observer
) {
129 change_observers_
.AddObserver(observer
);
132 void LocalFileSyncService::RegisterURLForWaitingSync(
133 const FileSystemURL
& url
,
134 const base::Closure
& on_syncable_callback
) {
135 sync_context_
->RegisterURLForWaitingSync(url
, on_syncable_callback
);
138 void LocalFileSyncService::ProcessLocalChange(
139 const SyncFileCallback
& callback
) {
140 DCHECK(local_change_processor_
);
141 // Pick an origin to process next.
143 if (!origin_change_map_
.NextOriginToProcess(&origin
)) {
144 callback
.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC
, FileSystemURL());
147 DCHECK(local_sync_callback_
.is_null());
148 DCHECK(!origin
.is_empty());
149 DCHECK(ContainsKey(origin_to_contexts_
, origin
));
151 DVLOG(1) << "Starting ProcessLocalChange";
153 local_sync_callback_
= callback
;
155 sync_context_
->GetFileForLocalSync(
156 origin_to_contexts_
[origin
],
157 base::Bind(&LocalFileSyncService::DidGetFileForLocalSync
,
161 void LocalFileSyncService::SetLocalChangeProcessor(
162 LocalChangeProcessor
* processor
) {
163 local_change_processor_
= processor
;
166 void LocalFileSyncService::HasPendingLocalChanges(
167 const FileSystemURL
& url
,
168 const HasPendingLocalChangeCallback
& callback
) {
169 if (!ContainsKey(origin_to_contexts_
, url
.origin())) {
170 base::MessageLoopProxy::current()->PostTask(
172 base::Bind(callback
, SYNC_FILE_ERROR_INVALID_URL
, false));
175 sync_context_
->HasPendingLocalChanges(
176 origin_to_contexts_
[url
.origin()], url
, callback
);
179 void LocalFileSyncService::ClearSyncFlagForURL(
180 const FileSystemURL
& url
) {
181 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
182 sync_context_
->ClearSyncFlagForURL(url
);
185 void LocalFileSyncService::GetLocalFileMetadata(
186 const FileSystemURL
& url
, const SyncFileMetadataCallback
& callback
) {
187 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
188 sync_context_
->GetFileMetadata(origin_to_contexts_
[url
.origin()],
192 void LocalFileSyncService::PrepareForProcessRemoteChange(
193 const FileSystemURL
& url
,
194 const PrepareChangeCallback
& callback
) {
195 DVLOG(1) << "PrepareForProcessRemoteChange: " << url
.DebugString();
197 if (!ContainsKey(origin_to_contexts_
, url
.origin())) {
198 // This could happen if a remote sync is triggered for the app that hasn't
199 // been initialized in this service.
201 // The given url.origin() must be for valid installed app.
202 ExtensionService
* extension_service
=
203 extensions::ExtensionSystem::Get(profile_
)->extension_service();
204 const extensions::Extension
* extension
= extension_service
->GetInstalledApp(
208 logging::LOG_WARNING
,
210 "PrepareForProcessRemoteChange called for non-existing origin: %s",
211 url
.origin().spec().c_str());
213 // The extension has been uninstalled and this method is called
214 // before the remote changes for the origin are removed.
215 callback
.Run(SYNC_STATUS_NO_CHANGE_TO_SYNC
,
216 SyncFileMetadata(), FileChangeList());
219 GURL site_url
= extension_service
->GetSiteForExtensionId(extension
->id());
220 DCHECK(!site_url
.is_empty());
221 scoped_refptr
<fileapi::FileSystemContext
> file_system_context
=
222 content::BrowserContext::GetStoragePartitionForSite(
223 profile_
, site_url
)->GetFileSystemContext();
224 MaybeInitializeFileSystemContext(
226 file_system_context
.get(),
227 base::Bind(&LocalFileSyncService::DidInitializeForRemoteSync
,
235 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
236 sync_context_
->PrepareForSync(
237 origin_to_contexts_
[url
.origin()], url
,
238 base::Bind(&PrepareForProcessRemoteChangeCallbackAdapter
, callback
));
241 void LocalFileSyncService::ApplyRemoteChange(
242 const FileChange
& change
,
243 const base::FilePath
& local_path
,
244 const FileSystemURL
& url
,
245 const SyncStatusCallback
& callback
) {
246 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
247 sync_context_
->ApplyRemoteChange(
248 origin_to_contexts_
[url
.origin()],
249 change
, local_path
, url
, callback
);
252 void LocalFileSyncService::ClearLocalChanges(
253 const FileSystemURL
& url
,
254 const base::Closure
& completion_callback
) {
255 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
256 sync_context_
->ClearChangesForURL(origin_to_contexts_
[url
.origin()],
257 url
, completion_callback
);
260 void LocalFileSyncService::RecordFakeLocalChange(
261 const FileSystemURL
& url
,
262 const FileChange
& change
,
263 const SyncStatusCallback
& callback
) {
264 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
265 sync_context_
->RecordFakeLocalChange(origin_to_contexts_
[url
.origin()],
266 url
, change
, callback
);
269 void LocalFileSyncService::OnChangesAvailableInOrigins(
270 const std::set
<GURL
>& origins
) {
271 bool need_notification
= false;
272 for (std::set
<GURL
>::const_iterator iter
= origins
.begin();
273 iter
!= origins
.end(); ++iter
) {
274 const GURL
& origin
= *iter
;
275 if (!ContainsKey(origin_to_contexts_
, origin
)) {
276 // This could happen if this is called for apps/origins that haven't
277 // been initialized yet, or for apps/origins that are disabled.
278 // (Local change tracker could call this for uninitialized origins
279 // while it's reading dirty files from the database in the
280 // initialization phase.)
281 pending_origins_with_changes_
.insert(origin
);
284 need_notification
= true;
285 fileapi::FileSystemContext
* context
= origin_to_contexts_
[origin
];
286 DCHECK(context
->change_tracker());
287 origin_change_map_
.SetOriginChangeCount(
288 origin
, context
->change_tracker()->num_changes());
290 if (!need_notification
)
292 int64 num_changes
= origin_change_map_
.GetTotalChangeCount();
293 FOR_EACH_OBSERVER(Observer
, change_observers_
,
294 OnLocalChangeAvailable(num_changes
));
297 void LocalFileSyncService::SetOriginEnabled(const GURL
& origin
, bool enabled
) {
298 if (!ContainsKey(origin_to_contexts_
, origin
))
300 origin_change_map_
.SetOriginEnabled(origin
, enabled
);
303 void LocalFileSyncService::DidInitializeFileSystemContext(
304 const GURL
& app_origin
,
305 fileapi::FileSystemContext
* file_system_context
,
306 const SyncStatusCallback
& callback
,
307 SyncStatusCode status
) {
308 if (status
!= SYNC_STATUS_OK
) {
309 callback
.Run(status
);
312 DCHECK(file_system_context
);
313 origin_to_contexts_
[app_origin
] = file_system_context
;
315 if (pending_origins_with_changes_
.find(app_origin
) !=
316 pending_origins_with_changes_
.end()) {
317 // We have remaining changes for the origin.
318 pending_origins_with_changes_
.erase(app_origin
);
319 DCHECK(file_system_context
->change_tracker());
320 origin_change_map_
.SetOriginChangeCount(
321 app_origin
, file_system_context
->change_tracker()->num_changes());
322 int64 num_changes
= origin_change_map_
.GetTotalChangeCount();
323 FOR_EACH_OBSERVER(Observer
, change_observers_
,
324 OnLocalChangeAvailable(num_changes
));
326 callback
.Run(status
);
329 void LocalFileSyncService::DidInitializeForRemoteSync(
330 const FileSystemURL
& url
,
331 fileapi::FileSystemContext
* file_system_context
,
332 const PrepareChangeCallback
& callback
,
333 SyncStatusCode status
) {
334 if (status
!= SYNC_STATUS_OK
) {
335 DVLOG(1) << "FileSystemContext initialization failed for remote sync:"
336 << url
.DebugString() << " status=" << status
337 << " (" << SyncStatusCodeToString(status
) << ")";
338 callback
.Run(status
, SyncFileMetadata(), FileChangeList());
341 origin_to_contexts_
[url
.origin()] = file_system_context
;
342 PrepareForProcessRemoteChange(url
, callback
);
345 void LocalFileSyncService::RunLocalSyncCallback(
346 SyncStatusCode status
,
347 const FileSystemURL
& url
) {
348 DVLOG(1) << "Local sync is finished with: " << status
349 << " on " << url
.DebugString();
350 DCHECK(!local_sync_callback_
.is_null());
351 SyncFileCallback callback
= local_sync_callback_
;
352 local_sync_callback_
.Reset();
353 callback
.Run(status
, url
);
356 void LocalFileSyncService::DidGetFileForLocalSync(
357 SyncStatusCode status
,
358 const LocalFileSyncInfo
& sync_file_info
) {
359 DCHECK(!local_sync_callback_
.is_null());
360 if (status
!= SYNC_STATUS_OK
) {
361 RunLocalSyncCallback(status
, sync_file_info
.url
);
364 if (sync_file_info
.changes
.empty()) {
365 // There's a slight chance this could happen.
366 SyncFileCallback callback
= local_sync_callback_
;
367 local_sync_callback_
.Reset();
368 ProcessLocalChange(callback
);
372 DVLOG(1) << "ProcessLocalChange: " << sync_file_info
.url
.DebugString()
373 << " change:" << sync_file_info
.changes
.front().DebugString();
375 local_change_processor_
->ApplyLocalChange(
376 sync_file_info
.changes
.front(),
377 sync_file_info
.local_file_path
,
378 sync_file_info
.metadata
,
380 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL
,
383 sync_file_info
.changes
.front(),
384 sync_file_info
.changes
.PopAndGetNewList()));
387 void LocalFileSyncService::ProcessNextChangeForURL(
388 const LocalFileSyncInfo
& sync_file_info
,
389 const FileChange
& last_change
,
390 const FileChangeList
& changes
,
391 SyncStatusCode status
) {
392 DVLOG(1) << "Processed one local change: "
393 << sync_file_info
.url
.DebugString()
394 << " change:" << last_change
.DebugString()
395 << " status:" << status
;
397 if (status
== SYNC_FILE_ERROR_NOT_FOUND
&&
398 last_change
.change() == FileChange::FILE_CHANGE_DELETE
) {
399 // This must be ok (and could happen).
400 status
= SYNC_STATUS_OK
;
403 // TODO(kinuko,tzik): Handle other errors that should not be considered
406 const FileSystemURL
& url
= sync_file_info
.url
;
407 if (status
!= SYNC_STATUS_OK
|| changes
.empty()) {
408 if (status
== SYNC_STATUS_OK
|| status
== SYNC_STATUS_HAS_CONFLICT
) {
409 // Clear the recorded changes for the URL if the sync was successfull
410 // OR has failed due to conflict (so that we won't stick to the same
411 // conflicting file again and again).
412 DCHECK(ContainsKey(origin_to_contexts_
, url
.origin()));
413 sync_context_
->ClearChangesForURL(
414 origin_to_contexts_
[url
.origin()], url
,
415 base::Bind(&LocalFileSyncService::RunLocalSyncCallback
,
416 AsWeakPtr(), status
, url
));
419 RunLocalSyncCallback(status
, url
);
423 local_change_processor_
->ApplyLocalChange(
425 sync_file_info
.local_file_path
,
426 sync_file_info
.metadata
,
428 base::Bind(&LocalFileSyncService::ProcessNextChangeForURL
,
429 AsWeakPtr(), sync_file_info
,
430 changes
.front(), changes
.PopAndGetNewList()));
433 } // namespace sync_file_system