Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(612)

Side by Side Diff: chrome/browser/sync_file_system/drive/apply_local_change_delegate.cc

Issue 14977008: [SyncFileSystem] Separate out ApplyLocalChange from DriveFileSyncService. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: drop extra empty line Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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/drive/apply_local_change_delegate.h"
6
7 #include "base/bind.h"
8 #include "base/callback.h"
9 #include "chrome/browser/sync_file_system/drive/api_util.h"
10 #include "chrome/browser/sync_file_system/drive_file_sync_service.h"
11 #include "chrome/browser/sync_file_system/drive_metadata_store.h"
12 #include "webkit/fileapi/syncable/syncable_file_system_util.h"
13
14 namespace sync_file_system {
15 namespace drive {
16
17 namespace {
18
19 void OwnDelegateInstance(ApplyLocalChangeDelegate* delegate,
20 const SyncStatusCallback& callback,
21 SyncStatusCode status) {
22 callback.Run(status);
23 }
24
25 } // namespace
26
27 ApplyLocalChangeDelegate::~ApplyLocalChangeDelegate() {}
28
29 ApplyLocalChangeDelegate::ApplyLocalChangeDelegate(
kinuko 2013/05/16 06:57:49 nit: method order different from .h
tzik 2013/05/16 11:06:14 Done.
30 base::WeakPtr<DriveFileSyncService> sync_service,
31 const FileChange& local_change,
32 const base::FilePath& local_path,
33 const SyncFileMetadata& local_metadata,
34 const fileapi::FileSystemURL& url)
35 : sync_service_(sync_service),
36 url_(url),
37 local_change_(local_change),
38 local_path_(local_path),
39 local_metadata_(local_metadata),
40 has_drive_metadata_(false),
41 has_remote_change_(false),
42 weak_factory_(this) {}
43
44 void ApplyLocalChangeDelegate::ApplyLocalChange(
45 base::WeakPtr<DriveFileSyncService> sync_service,
46 const FileChange& local_change,
47 const base::FilePath& local_path,
48 const SyncFileMetadata& local_metadata,
49 const fileapi::FileSystemURL& url,
50 const SyncStatusCallback& callback) {
51 if (!sync_service)
52 return;
53
54 ApplyLocalChangeDelegate* delegate = new ApplyLocalChangeDelegate(
55 sync_service, local_change, local_path, local_metadata, url);
56 SyncStatusCallback completion_callback =
57 base::Bind(&OwnDelegateInstance, base::Owned(delegate), callback);
kinuko 2013/05/16 06:57:49 I understand this class's very self-contained and
tzik 2013/05/16 11:06:14 Done. I added |running_local_sync_task_| to DriveF
58 delegate->Run(completion_callback);
59 }
60
61 void ApplyLocalChangeDelegate::Run(const SyncStatusCallback& callback) {
62 if (!sync_service_)
63 return;
64
65 // TODO(nhiroki): support directory operations (http://crbug.com/161442).
66 DCHECK(IsSyncDirectoryOperationEnabled() || !local_change_.IsDirectory());
67
68 has_drive_metadata_ =
69 metadata_store()->ReadEntry(url_, &drive_metadata_) == SYNC_STATUS_OK;
70
71 if (!has_drive_metadata_)
72 drive_metadata_.set_md5_checksum(std::string());
73
74 sync_service_->EnsureOriginRootDirectory(
75 url_.origin(),
76 base::Bind(&ApplyLocalChangeDelegate::DidGetOriginRoot,
77 weak_factory_.GetWeakPtr(),
78 callback));
79 }
80
81 void ApplyLocalChangeDelegate::DidGetOriginRoot(
82 const SyncStatusCallback& callback,
83 SyncStatusCode status,
84 const std::string& origin_resource_id) {
85 if (!sync_service_)
86 return;
87
88 if (status != SYNC_STATUS_OK) {
89 callback.Run(status);
90 return;
91 }
92
93 origin_resource_id_ = origin_resource_id;
94
95 has_remote_change_ =
96 remote_change_handler()->GetChangeForURL(url_, &remote_change_);
97 if (has_remote_change_ && drive_metadata_.resource_id().empty())
98 drive_metadata_.set_resource_id(remote_change_.resource_id);
99
100 LocalSyncOperationType operation = LocalSyncOperationResolver::Resolve(
101 local_change_,
102 has_remote_change_ ? &remote_change_.change : NULL,
103 has_drive_metadata_ ? &drive_metadata_ : NULL);
104
105 DVLOG(1) << "ApplyLocalChange for " << url_.DebugString()
106 << " local_change:" << local_change_.DebugString()
107 << " ==> operation:" << operation;
108
109 switch (operation) {
110 case LOCAL_SYNC_OPERATION_ADD_FILE:
111 UploadNewFile(callback);
112 return;
113 case LOCAL_SYNC_OPERATION_ADD_DIRECTORY:
114 CreateDirectory(callback);
115 return;
116 case LOCAL_SYNC_OPERATION_UPDATE_FILE:
117 UploadExistingFile(callback);
118 return;
119 case LOCAL_SYNC_OPERATION_DELETE_FILE:
120 DeleteFile(callback);
121 return;
122 case LOCAL_SYNC_OPERATION_DELETE_DIRECTORY:
123 DeleteDirectory(callback);
124 return;
125 case LOCAL_SYNC_OPERATION_NONE:
126 callback.Run(SYNC_STATUS_OK);
127 return;
128 case LOCAL_SYNC_OPERATION_CONFLICT:
129 HandleConflict(callback);
130 return;
131 case LOCAL_SYNC_OPERATION_RESOLVE_TO_LOCAL:
132 ResolveToLocal(callback);
133 return;
134 case LOCAL_SYNC_OPERATION_RESOLVE_TO_REMOTE:
135 ResolveToRemote(callback);
136 return;
137 case LOCAL_SYNC_OPERATION_DELETE_METADATA:
138 DeleteMetadata(base::Bind(
139 &ApplyLocalChangeDelegate::DidApplyLocalChange,
140 weak_factory_.GetWeakPtr(), callback, google_apis::HTTP_SUCCESS));
141 return;
142 case LOCAL_SYNC_OPERATION_FAIL: {
143 callback.Run(SYNC_STATUS_FAILED);
144 return;
145 }
146 }
147 NOTREACHED();
148 callback.Run(SYNC_STATUS_FAILED);
149 }
150
151 void ApplyLocalChangeDelegate::UploadNewFile(
152 const SyncStatusCallback& callback) {
153 if (!sync_service_)
154 return;
155
156 api_util()->UploadNewFile(
157 origin_resource_id_,
158 local_path_,
159 DriveFileSyncService::PathToTitle(url_.path()),
160 base::Bind(&ApplyLocalChangeDelegate::DidUploadNewFile,
161 weak_factory_.GetWeakPtr(), callback));
162 }
163
164 void ApplyLocalChangeDelegate::DidUploadNewFile(
165 const SyncStatusCallback& callback,
166 google_apis::GDataErrorCode error,
167 const std::string& resource_id,
168 const std::string& md5) {
169 if (!sync_service_)
170 return;
171
172 switch (error) {
173 case google_apis::HTTP_CREATED:
174 UpdateMetadata(
175 resource_id, md5, DriveMetadata::RESOURCE_TYPE_FILE,
176 base::Bind(&ApplyLocalChangeDelegate::DidApplyLocalChange,
177 weak_factory_.GetWeakPtr(), callback, error));
178 sync_service_->NotifyObserversFileStatusChanged(
179 url_,
180 SYNC_FILE_STATUS_SYNCED,
181 SYNC_ACTION_ADDED,
182 SYNC_DIRECTION_LOCAL_TO_REMOTE);
183 return;
184 case google_apis::HTTP_CONFLICT:
185 HandleCreationConflict(resource_id, DriveMetadata::RESOURCE_TYPE_FILE,
186 callback);
187 return;
188 default:
189 callback.Run(GDataErrorCodeToSyncStatusCodeWrapper(error));
190 }
191 }
192
193 void ApplyLocalChangeDelegate::CreateDirectory(
194 const SyncStatusCallback& callback) {
195 if (!sync_service_)
196 return;
197
198 DCHECK(IsSyncDirectoryOperationEnabled());
199 api_util()->CreateDirectory(
200 origin_resource_id_,
201 DriveFileSyncService::PathToTitle(url_.path()),
202 base::Bind(&ApplyLocalChangeDelegate::DidCreateDirectory,
203 weak_factory_.GetWeakPtr(), callback));
204 }
205
206 void ApplyLocalChangeDelegate::DidCreateDirectory(
207 const SyncStatusCallback& callback,
208 google_apis::GDataErrorCode error,
209 const std::string& resource_id) {
210 if (!sync_service_)
211 return;
212
213 switch (error) {
214 case google_apis::HTTP_SUCCESS:
215 case google_apis::HTTP_CREATED: {
216 UpdateMetadata(
217 resource_id, std::string(), DriveMetadata::RESOURCE_TYPE_FOLDER,
218 base::Bind(&ApplyLocalChangeDelegate::DidApplyLocalChange,
219 weak_factory_.GetWeakPtr(), callback, error));
220 sync_service_->NotifyObserversFileStatusChanged(
221 url_,
222 SYNC_FILE_STATUS_SYNCED,
223 SYNC_ACTION_ADDED,
224 SYNC_DIRECTION_LOCAL_TO_REMOTE);
225 return;
226 }
227
228 case google_apis::HTTP_CONFLICT:
229 // There were conflicts and a file was left.
230 // TODO(kinuko): Handle the latter case (http://crbug.com/237090).
231 // Fall-through
232
233 default:
234 callback.Run(GDataErrorCodeToSyncStatusCodeWrapper(error));
235 }
236 }
237
238 void ApplyLocalChangeDelegate::UploadExistingFile(
239 const SyncStatusCallback& callback) {
240 if (!sync_service_)
241 return;
242
243 DCHECK(has_drive_metadata_);
244 api_util()->UploadExistingFile(
245 drive_metadata_.resource_id(),
246 drive_metadata_.md5_checksum(),
247 local_path_,
248 base::Bind(&ApplyLocalChangeDelegate::DidUploadExistingFile,
249 weak_factory_.GetWeakPtr(), callback));
250 }
251
252 void ApplyLocalChangeDelegate::DidUploadExistingFile(
253 const SyncStatusCallback& callback,
254 google_apis::GDataErrorCode error,
255 const std::string& resource_id,
256 const std::string& md5) {
257 if (!sync_service_)
258 return;
259
260 DCHECK(has_drive_metadata_);
261 switch (error) {
262 case google_apis::HTTP_SUCCESS:
263 UpdateMetadata(
264 resource_id, md5, DriveMetadata::RESOURCE_TYPE_FILE,
265 base::Bind(&ApplyLocalChangeDelegate::DidApplyLocalChange,
266 weak_factory_.GetWeakPtr(), callback, error));
267 sync_service_->NotifyObserversFileStatusChanged(
268 url_,
269 SYNC_FILE_STATUS_SYNCED,
270 SYNC_ACTION_UPDATED,
271 SYNC_DIRECTION_LOCAL_TO_REMOTE);
272 return;
273 case google_apis::HTTP_CONFLICT: {
274 HandleConflict(callback);
275 return;
276 }
277 case google_apis::HTTP_NOT_MODIFIED: {
278 DidApplyLocalChange(callback,
279 google_apis::HTTP_SUCCESS, SYNC_STATUS_OK);
280 return;
281 }
282 case google_apis::HTTP_NOT_FOUND: {
283 UploadNewFile(callback);
284 return;
285 }
286 default: {
287 const SyncStatusCode status =
288 GDataErrorCodeToSyncStatusCodeWrapper(error);
289 DCHECK_NE(SYNC_STATUS_OK, status);
290 callback.Run(status);
291 return;
292 }
293 }
294 }
295
296 void ApplyLocalChangeDelegate::DeleteFile(
297 const SyncStatusCallback& callback) {
298 if (!sync_service_)
299 return;
300
301 DCHECK(has_drive_metadata_);
302 api_util()->DeleteFile(
303 drive_metadata_.resource_id(),
304 drive_metadata_.md5_checksum(),
305 base::Bind(&ApplyLocalChangeDelegate::DidDeleteFile,
306 weak_factory_.GetWeakPtr(), callback));
307 }
308
309 void ApplyLocalChangeDelegate::DeleteDirectory(
310 const SyncStatusCallback& callback) {
311 if (!sync_service_)
312 return;
313
314 DCHECK(IsSyncDirectoryOperationEnabled());
315 DCHECK(has_drive_metadata_);
316 // This does not handle recursive directory deletion
317 // (which should not happen other than after a restart).
318 api_util()->DeleteFile(
319 drive_metadata_.resource_id(),
320 std::string(), // empty md5
321 base::Bind(&ApplyLocalChangeDelegate::DidDeleteFile,
322 weak_factory_.GetWeakPtr(), callback));
323 }
324
325 void ApplyLocalChangeDelegate::DidDeleteFile(
326 const SyncStatusCallback& callback,
327 google_apis::GDataErrorCode error) {
328 if (!sync_service_)
329 return;
330
331 DCHECK(has_drive_metadata_);
332
333 switch (error) {
334 // Regardless of whether the deletion has succeeded (HTTP_SUCCESS) or
335 // has failed with ETag conflict error (HTTP_PRECONDITION or HTTP_CONFLICT)
336 // we should just delete the drive_metadata.
337 // In the former case the file should be just gone now, and
338 // in the latter case the remote change will be applied in a future
339 // remote sync.
340 case google_apis::HTTP_SUCCESS:
341 case google_apis::HTTP_PRECONDITION:
342 case google_apis::HTTP_CONFLICT:
343 DeleteMetadata(base::Bind(&ApplyLocalChangeDelegate::DidApplyLocalChange,
344 weak_factory_.GetWeakPtr(), callback, error));
345 sync_service_->NotifyObserversFileStatusChanged(
346 url_,
347 SYNC_FILE_STATUS_SYNCED,
348 SYNC_ACTION_DELETED,
349 SYNC_DIRECTION_LOCAL_TO_REMOTE);
350 return;
351 case google_apis::HTTP_NOT_FOUND:
352 DidApplyLocalChange(callback,
353 google_apis::HTTP_SUCCESS, SYNC_STATUS_OK);
354 return;
355 default: {
356 const SyncStatusCode status =
357 GDataErrorCodeToSyncStatusCodeWrapper(error);
358 DCHECK_NE(SYNC_STATUS_OK, status);
359 callback.Run(status);
360 return;
361 }
362 }
363 }
364
365 void ApplyLocalChangeDelegate::ResolveToLocal(
366 const SyncStatusCallback& callback) {
367 if (!sync_service_)
368 return;
369
370 api_util()->DeleteFile(
371 drive_metadata_.resource_id(),
372 drive_metadata_.md5_checksum(),
373 base::Bind(
374 &ApplyLocalChangeDelegate::DidDeleteFileToResolveToLocal,
375 weak_factory_.GetWeakPtr(), callback));
376 }
377
378 void ApplyLocalChangeDelegate::DidDeleteFileToResolveToLocal(
379 const SyncStatusCallback& callback,
380 google_apis::GDataErrorCode error) {
381 if (!sync_service_)
382 return;
383
384 if (error != google_apis::HTTP_SUCCESS &&
385 error != google_apis::HTTP_NOT_FOUND) {
386 remote_change_handler()->RemoveChangeForURL(url_);
387 callback.Run(GDataErrorCodeToSyncStatusCodeWrapper(error));
388 return;
389 }
390
391 DCHECK_NE(SYNC_FILE_TYPE_UNKNOWN, local_metadata_.file_type);
392 if (local_metadata_.file_type == SYNC_FILE_TYPE_FILE) {
393 UploadNewFile(callback);
394 return;
395 }
396
397 DCHECK(IsSyncDirectoryOperationEnabled());
398 DCHECK_EQ(SYNC_FILE_TYPE_DIRECTORY, local_metadata_.file_type);
399 CreateDirectory(callback);
400 }
401
402 void ApplyLocalChangeDelegate::ResolveToRemote(
403 const SyncStatusCallback& callback) {
404 if (!sync_service_)
405 return;
406
407 // Mark the file as to-be-fetched.
408 DCHECK(!drive_metadata_.resource_id().empty());
409
410 SyncFileType type = remote_change_.change.file_type();
411 SetToBeFetched(
412 drive_metadata_.resource_id(),
413 DriveFileSyncService::SyncFileTypeToDriveMetadataResourceType(type),
414 base::Bind(&ApplyLocalChangeDelegate::DidResolveToRemote,
415 weak_factory_.GetWeakPtr(), callback));
416 // The synced notification will be dispatched when the remote file is
417 // downloaded.
418 }
419
420 void ApplyLocalChangeDelegate::DidResolveToRemote(
421 const SyncStatusCallback& callback,
422 SyncStatusCode status) {
423 if (!sync_service_)
424 return;
425
426 DCHECK(has_drive_metadata_);
427 if (status != SYNC_STATUS_OK) {
428 callback.Run(status);
429 return;
430 }
431
432 SyncFileType file_type = SYNC_FILE_TYPE_FILE;
433 if (drive_metadata_.type() == DriveMetadata::RESOURCE_TYPE_FOLDER)
434 file_type = SYNC_FILE_TYPE_DIRECTORY;
435 sync_service_->AppendFetchChange(
436 url_.origin(), url_.path(), drive_metadata_.resource_id(), file_type);
437 callback.Run(status);
438 }
439
440 void ApplyLocalChangeDelegate::DidApplyLocalChange(
441 const SyncStatusCallback& callback,
442 const google_apis::GDataErrorCode error,
443 SyncStatusCode status) {
444 if (!sync_service_)
445 return;
446
447 if (status == SYNC_STATUS_OK) {
448 remote_change_handler()->RemoveChangeForURL(url_);
449 status = GDataErrorCodeToSyncStatusCodeWrapper(error);
450 }
451 callback.Run(status);
452 }
453
454 void ApplyLocalChangeDelegate::UpdateMetadata(
455 const std::string& resource_id,
456 const std::string& md5,
457 DriveMetadata::ResourceType type,
458 const SyncStatusCallback& callback) {
459 if (!sync_service_)
460 return;
461
462 drive_metadata_.set_resource_id(resource_id);
463 drive_metadata_.set_md5_checksum(md5);
464 drive_metadata_.set_conflicted(false);
465 drive_metadata_.set_to_be_fetched(false);
466 drive_metadata_.set_type(type);
467 metadata_store()->UpdateEntry(url_, drive_metadata_, callback);
468 }
469
470 void ApplyLocalChangeDelegate::SetToBeFetched(
471 const std::string& resource_id,
472 DriveMetadata::ResourceType type,
473 const SyncStatusCallback& callback) {
474 if (!sync_service_)
475 return;
476
477 drive_metadata_.set_resource_id(resource_id);
478 drive_metadata_.set_md5_checksum(std::string());
479 drive_metadata_.set_conflicted(false);
480 drive_metadata_.set_to_be_fetched(true);
481 drive_metadata_.set_type(type);
482 metadata_store()->UpdateEntry(url_, drive_metadata_, callback);
483 }
484
485 void ApplyLocalChangeDelegate::SetConflict(
486 const std::string& resource_id,
487 const std::string& md5,
488 DriveMetadata::ResourceType type,
489 const SyncStatusCallback& callback) {
490 if (!sync_service_)
491 return;
492
493 drive_metadata_.set_resource_id(resource_id);
494 drive_metadata_.set_md5_checksum(md5);
495 drive_metadata_.set_conflicted(true);
496 drive_metadata_.set_to_be_fetched(false);
497 drive_metadata_.set_type(type);
498 metadata_store()->UpdateEntry(url_, drive_metadata_, callback);
499 }
500
501 void ApplyLocalChangeDelegate::DeleteMetadata(
502 const SyncStatusCallback& callback) {
503 metadata_store()->DeleteEntry(url_, callback);
504 }
505
506 void ApplyLocalChangeDelegate::HandleCreationConflict(
507 const std::string& resource_id,
508 DriveMetadata::ResourceType type,
509 const SyncStatusCallback& callback) {
510 if (!sync_service_)
511 return;
512
513 // File-file conflict is found.
514 // Populates a fake drive_metadata and set has_drive_metadata = true.
515 // In HandleConflictLocalSync:
516 // - If conflict_resolution is manual, we'll change conflicted to true
517 // and save the metadata.
518 // - Otherwise we'll save the metadata with empty md5 and will start
519 // over local sync as UploadExistingFile.
520 drive_metadata_.set_resource_id(resource_id);
521 drive_metadata_.set_md5_checksum(std::string());
522 drive_metadata_.set_conflicted(false);
523 drive_metadata_.set_to_be_fetched(false);
524 drive_metadata_.set_type(type);
525 has_drive_metadata_ = true;
526 HandleConflict(callback);
527 }
528
529 void ApplyLocalChangeDelegate::HandleConflict(
530 const SyncStatusCallback& callback) {
531 if (!sync_service_)
532 return;
533
534 DCHECK(!drive_metadata_.resource_id().empty());
535
536 api_util()->GetResourceEntry(
537 drive_metadata_.resource_id(),
538 base::Bind(
539 &ApplyLocalChangeDelegate::DidGetEntryForConflictResolution,
540 weak_factory_.GetWeakPtr(), callback));
541 }
542
543 void ApplyLocalChangeDelegate::DidGetEntryForConflictResolution(
544 const SyncStatusCallback& callback,
545 google_apis::GDataErrorCode error,
546 scoped_ptr<google_apis::ResourceEntry> entry) {
547 if (!sync_service_)
548 return;
549
550 SyncFileType local_file_type = local_metadata_.file_type;
551 base::Time local_modification_time = local_metadata_.last_modified;
552
553 SyncFileType remote_file_type;
554 base::Time remote_modification_time = entry->updated_time();
555 if (entry->is_file())
556 remote_file_type = SYNC_FILE_TYPE_FILE;
557 else if (entry->is_folder())
558 remote_file_type = SYNC_FILE_TYPE_DIRECTORY;
559 else
560 remote_file_type = SYNC_FILE_TYPE_UNKNOWN;
561
562 switch (sync_service_->ResolveConflictForLocalSync(
563 local_file_type, local_modification_time,
564 remote_file_type, remote_modification_time)) {
565 case DriveFileSyncService::CONFLICT_RESOLUTION_MARK_CONFLICT:
566 if (drive_metadata_.conflicted()) {
567 callback.Run(SYNC_STATUS_HAS_CONFLICT);
568 return;
569 }
570
571 SetConflict(drive_metadata_.resource_id(),
572 drive_metadata_.md5_checksum(),
573 DriveFileSyncService::SyncFileTypeToDriveMetadataResourceType(
574 local_file_type),
575 base::Bind(&ApplyLocalChangeDelegate::DidApplyLocalChange,
576 weak_factory_.GetWeakPtr(), callback,
577 google_apis::HTTP_SUCCESS));
578 return;
579 case DriveFileSyncService::CONFLICT_RESOLUTION_LOCAL_WIN:
580 DVLOG(1) << "Resolving conflict for local sync:"
581 << url_.DebugString() << ": LOCAL WIN";
582 ResolveToLocal(callback);
583 return;
584 case DriveFileSyncService::CONFLICT_RESOLUTION_REMOTE_WIN:
585 DVLOG(1) << "Resolving conflict for local sync:"
586 << url_.DebugString() << ": REMOTE WIN";
587 ResolveToRemote(callback);
588 return;
589 }
590 NOTREACHED();
591 callback.Run(SYNC_STATUS_FAILED);
592 }
593
594 SyncStatusCode ApplyLocalChangeDelegate::GDataErrorCodeToSyncStatusCodeWrapper(
595 google_apis::GDataErrorCode error) {
596 return sync_service_->GDataErrorCodeToSyncStatusCodeWrapper(error);
597 }
598
599 DriveMetadataStore* ApplyLocalChangeDelegate::metadata_store() {
600 return sync_service_->metadata_store_.get();
601 }
602
603 APIUtilInterface* ApplyLocalChangeDelegate::api_util() {
604 return sync_service_->api_util_.get();
605 }
606
607 RemoteChangeHandler* ApplyLocalChangeDelegate::remote_change_handler() {
608 return &sync_service_->remote_change_handler_;
609 }
610
611 } // drive
612 } // namespace sync_file_system
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698