- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / sync_file_system / local / local_file_change_tracker.cc
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_change_tracker.h"
6
7 #include <queue>
8
9 #include "base/location.h"
10 #include "base/logging.h"
11 #include "base/sequenced_task_runner.h"
12 #include "base/stl_util.h"
13 #include "chrome/browser/sync_file_system/local/local_file_sync_status.h"
14 #include "chrome/browser/sync_file_system/syncable_file_system_util.h"
15 #include "third_party/leveldatabase/src/include/leveldb/db.h"
16 #include "webkit/browser/fileapi/file_system_context.h"
17 #include "webkit/browser/fileapi/file_system_file_util.h"
18 #include "webkit/browser/fileapi/file_system_operation_context.h"
19 #include "webkit/common/fileapi/file_system_util.h"
20
21 using fileapi::FileSystemContext;
22 using fileapi::FileSystemFileUtil;
23 using fileapi::FileSystemOperationContext;
24 using fileapi::FileSystemURL;
25 using fileapi::FileSystemURLSet;
26
27 namespace sync_file_system {
28
29 namespace {
30 const base::FilePath::CharType kDatabaseName[] =
31     FILE_PATH_LITERAL("LocalFileChangeTracker");
32 const char kMark[] = "d";
33 }  // namespace
34
35 // A database class that stores local file changes in a local database. This
36 // object must be destructed on file_task_runner.
37 class LocalFileChangeTracker::TrackerDB {
38  public:
39   explicit TrackerDB(const base::FilePath& base_path);
40
41   SyncStatusCode MarkDirty(const std::string& url);
42   SyncStatusCode ClearDirty(const std::string& url);
43   SyncStatusCode GetDirtyEntries(
44       std::queue<FileSystemURL>* dirty_files);
45
46  private:
47   enum RecoveryOption {
48     REPAIR_ON_CORRUPTION,
49     FAIL_ON_CORRUPTION,
50   };
51
52   SyncStatusCode Init(RecoveryOption recovery_option);
53   SyncStatusCode Repair(const std::string& db_path);
54   void HandleError(const tracked_objects::Location& from_here,
55                    const leveldb::Status& status);
56
57   const base::FilePath base_path_;
58   scoped_ptr<leveldb::DB> db_;
59   SyncStatusCode db_status_;
60
61   DISALLOW_COPY_AND_ASSIGN(TrackerDB);
62 };
63
64 LocalFileChangeTracker::ChangeInfo::ChangeInfo() : change_seq(-1) {}
65 LocalFileChangeTracker::ChangeInfo::~ChangeInfo() {}
66
67 // LocalFileChangeTracker ------------------------------------------------------
68
69 LocalFileChangeTracker::LocalFileChangeTracker(
70     const base::FilePath& base_path,
71     base::SequencedTaskRunner* file_task_runner)
72     : initialized_(false),
73       file_task_runner_(file_task_runner),
74       tracker_db_(new TrackerDB(base_path)),
75       current_change_seq_(0),
76       num_changes_(0) {
77 }
78
79 LocalFileChangeTracker::~LocalFileChangeTracker() {
80   DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
81   tracker_db_.reset();
82 }
83
84 void LocalFileChangeTracker::OnStartUpdate(const FileSystemURL& url) {
85   DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
86   if (ContainsKey(changes_, url))
87     return;
88   // TODO(nhiroki): propagate the error code (see http://crbug.com/152127).
89   MarkDirtyOnDatabase(url);
90 }
91
92 void LocalFileChangeTracker::OnEndUpdate(const FileSystemURL& url) {}
93
94 void LocalFileChangeTracker::OnCreateFile(const FileSystemURL& url) {
95   RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
96                                SYNC_FILE_TYPE_FILE));
97 }
98
99 void LocalFileChangeTracker::OnCreateFileFrom(const FileSystemURL& url,
100                                               const FileSystemURL& src) {
101   RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
102                                SYNC_FILE_TYPE_FILE));
103 }
104
105 void LocalFileChangeTracker::OnRemoveFile(const FileSystemURL& url) {
106   RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
107                                SYNC_FILE_TYPE_FILE));
108 }
109
110 void LocalFileChangeTracker::OnModifyFile(const FileSystemURL& url) {
111   RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
112                                SYNC_FILE_TYPE_FILE));
113 }
114
115 void LocalFileChangeTracker::OnCreateDirectory(const FileSystemURL& url) {
116   RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
117                                SYNC_FILE_TYPE_DIRECTORY));
118 }
119
120 void LocalFileChangeTracker::OnRemoveDirectory(const FileSystemURL& url) {
121   RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
122                                SYNC_FILE_TYPE_DIRECTORY));
123 }
124
125 void LocalFileChangeTracker::GetNextChangedURLs(
126     std::deque<FileSystemURL>* urls, int max_urls) {
127   DCHECK(urls);
128   DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
129   urls->clear();
130   // Mildly prioritizes the URLs that older changes and have not been updated
131   // for a while.
132   for (ChangeSeqMap::iterator iter = change_seqs_.begin();
133        iter != change_seqs_.end() &&
134        (max_urls == 0 || urls->size() < static_cast<size_t>(max_urls));
135        ++iter) {
136     urls->push_back(iter->second);
137   }
138 }
139
140 void LocalFileChangeTracker::GetChangesForURL(
141     const FileSystemURL& url, FileChangeList* changes) {
142   DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
143   DCHECK(changes);
144   changes->clear();
145   FileChangeMap::iterator found = changes_.find(url);
146   if (found == changes_.end())
147     return;
148   *changes = found->second.change_list;
149 }
150
151 void LocalFileChangeTracker::ClearChangesForURL(const FileSystemURL& url) {
152   DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
153   ClearDirtyOnDatabase(url);
154   mirror_changes_.erase(url);
155   FileChangeMap::iterator found = changes_.find(url);
156   if (found == changes_.end())
157     return;
158   change_seqs_.erase(found->second.change_seq);
159   changes_.erase(found);
160   UpdateNumChanges();
161 }
162
163 void LocalFileChangeTracker::CreateFreshMirrorForURL(
164     const fileapi::FileSystemURL& url) {
165   DCHECK(!ContainsKey(mirror_changes_, url));
166   mirror_changes_[url] = ChangeInfo();
167 }
168
169 void LocalFileChangeTracker::RemoveMirrorAndCommitChangesForURL(
170     const fileapi::FileSystemURL& url) {
171   FileChangeMap::iterator found = mirror_changes_.find(url);
172   if (found == mirror_changes_.end())
173     return;
174   mirror_changes_.erase(found);
175
176   if (ContainsKey(changes_, url))
177     MarkDirtyOnDatabase(url);
178   else
179     ClearDirtyOnDatabase(url);
180   UpdateNumChanges();
181 }
182
183 void LocalFileChangeTracker::ResetToMirrorAndCommitChangesForURL(
184     const fileapi::FileSystemURL& url) {
185   FileChangeMap::iterator found = mirror_changes_.find(url);
186   if (found == mirror_changes_.end() || found->second.change_list.empty()) {
187     ClearChangesForURL(url);
188     return;
189   }
190   const ChangeInfo& info = found->second;
191   change_seqs_[info.change_seq] = url;
192   changes_[url] = info;
193   RemoveMirrorAndCommitChangesForURL(url);
194 }
195
196 SyncStatusCode LocalFileChangeTracker::Initialize(
197     FileSystemContext* file_system_context) {
198   DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
199   DCHECK(!initialized_);
200   DCHECK(file_system_context);
201
202   SyncStatusCode status = CollectLastDirtyChanges(file_system_context);
203   if (status == SYNC_STATUS_OK)
204     initialized_ = true;
205   return status;
206 }
207
208 void LocalFileChangeTracker::UpdateNumChanges() {
209   base::AutoLock lock(num_changes_lock_);
210   num_changes_ = static_cast<int64>(change_seqs_.size());
211 }
212
213 void LocalFileChangeTracker::GetAllChangedURLs(FileSystemURLSet* urls) {
214   std::deque<FileSystemURL> url_deque;
215   GetNextChangedURLs(&url_deque, 0);
216   urls->clear();
217   urls->insert(url_deque.begin(), url_deque.end());
218 }
219
220 void LocalFileChangeTracker::DropAllChanges() {
221   changes_.clear();
222   change_seqs_.clear();
223   mirror_changes_.clear();
224 }
225
226 SyncStatusCode LocalFileChangeTracker::MarkDirtyOnDatabase(
227     const FileSystemURL& url) {
228   std::string serialized_url;
229   if (!SerializeSyncableFileSystemURL(url, &serialized_url))
230     return SYNC_FILE_ERROR_INVALID_URL;
231
232   return tracker_db_->MarkDirty(serialized_url);
233 }
234
235 SyncStatusCode LocalFileChangeTracker::ClearDirtyOnDatabase(
236     const FileSystemURL& url) {
237   std::string serialized_url;
238   if (!SerializeSyncableFileSystemURL(url, &serialized_url))
239     return SYNC_FILE_ERROR_INVALID_URL;
240
241   return tracker_db_->ClearDirty(serialized_url);
242 }
243
244 SyncStatusCode LocalFileChangeTracker::CollectLastDirtyChanges(
245     FileSystemContext* file_system_context) {
246   DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
247
248   std::queue<FileSystemURL> dirty_files;
249   const SyncStatusCode status = tracker_db_->GetDirtyEntries(&dirty_files);
250   if (status != SYNC_STATUS_OK)
251     return status;
252
253   FileSystemFileUtil* file_util =
254       file_system_context->sandbox_delegate()->sync_file_util();
255   DCHECK(file_util);
256   scoped_ptr<FileSystemOperationContext> context(
257       new FileSystemOperationContext(file_system_context));
258
259   base::PlatformFileInfo file_info;
260   base::FilePath platform_path;
261
262   while (!dirty_files.empty()) {
263     const FileSystemURL url = dirty_files.front();
264     dirty_files.pop();
265     DCHECK_EQ(url.type(), fileapi::kFileSystemTypeSyncable);
266
267     switch (file_util->GetFileInfo(context.get(), url,
268                                    &file_info, &platform_path)) {
269       case base::PLATFORM_FILE_OK: {
270         if (!file_info.is_directory) {
271           RecordChange(url, FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE,
272                                        SYNC_FILE_TYPE_FILE));
273           break;
274         }
275
276         RecordChange(url, FileChange(
277             FileChange::FILE_CHANGE_ADD_OR_UPDATE,
278             SYNC_FILE_TYPE_DIRECTORY));
279
280         // Push files and directories in this directory into |dirty_files|.
281         scoped_ptr<FileSystemFileUtil::AbstractFileEnumerator> enumerator(
282             file_util->CreateFileEnumerator(context.get(), url));
283         base::FilePath path_each;
284         while (!(path_each = enumerator->Next()).empty()) {
285           dirty_files.push(CreateSyncableFileSystemURL(
286                   url.origin(), path_each));
287         }
288         break;
289       }
290       case base::PLATFORM_FILE_ERROR_NOT_FOUND: {
291         // File represented by |url| has already been deleted. Since we cannot
292         // figure out if this file was directory or not from the URL, file
293         // type is treated as SYNC_FILE_TYPE_UNKNOWN.
294         //
295         // NOTE: Directory to have been reverted (that is, ADD -> DELETE) is
296         // also treated as FILE_CHANGE_DELETE.
297         RecordChange(url, FileChange(FileChange::FILE_CHANGE_DELETE,
298                                      SYNC_FILE_TYPE_UNKNOWN));
299         break;
300       }
301       case base::PLATFORM_FILE_ERROR_FAILED:
302       default:
303         // TODO(nhiroki): handle file access error (http://crbug.com/155251).
304         LOG(WARNING) << "Failed to access local file.";
305         break;
306     }
307   }
308   return SYNC_STATUS_OK;
309 }
310
311 void LocalFileChangeTracker::RecordChange(
312     const FileSystemURL& url, const FileChange& change) {
313   DCHECK(file_task_runner_->RunsTasksOnCurrentThread());
314   int change_seq = current_change_seq_++;
315   RecordChangeToChangeMaps(url, change, change_seq, &changes_, &change_seqs_);
316   if (ContainsKey(mirror_changes_, url))
317     RecordChangeToChangeMaps(url, change, change_seq, &mirror_changes_, NULL);
318   UpdateNumChanges();
319 }
320
321 void LocalFileChangeTracker::RecordChangeToChangeMaps(
322     const FileSystemURL& url,
323     const FileChange& change,
324     int new_change_seq,
325     FileChangeMap* changes,
326     ChangeSeqMap* change_seqs) {
327   ChangeInfo& info = (*changes)[url];
328   if (info.change_seq >= 0 && change_seqs)
329     change_seqs->erase(info.change_seq);
330   info.change_list.Update(change);
331   if (info.change_list.empty()) {
332     changes->erase(url);
333     return;
334   }
335   info.change_seq = new_change_seq;
336   if (change_seqs)
337     (*change_seqs)[info.change_seq] = url;
338 }
339
340 // TrackerDB -------------------------------------------------------------------
341
342 LocalFileChangeTracker::TrackerDB::TrackerDB(const base::FilePath& base_path)
343   : base_path_(base_path),
344     db_status_(SYNC_STATUS_OK) {}
345
346 SyncStatusCode LocalFileChangeTracker::TrackerDB::Init(
347     RecoveryOption recovery_option) {
348   if (db_.get() && db_status_ == SYNC_STATUS_OK)
349     return SYNC_STATUS_OK;
350
351   std::string path = fileapi::FilePathToString(
352       base_path_.Append(kDatabaseName));
353   leveldb::Options options;
354   options.max_open_files = 0;  // Use minimum.
355   options.create_if_missing = true;
356   leveldb::DB* db;
357   leveldb::Status status = leveldb::DB::Open(options, path, &db);
358   if (status.ok()) {
359     db_.reset(db);
360     return SYNC_STATUS_OK;
361   }
362
363   HandleError(FROM_HERE, status);
364   if (!status.IsCorruption())
365     return LevelDBStatusToSyncStatusCode(status);
366
367   // Try to repair the corrupted DB.
368   switch (recovery_option) {
369     case FAIL_ON_CORRUPTION:
370       return SYNC_DATABASE_ERROR_CORRUPTION;
371     case REPAIR_ON_CORRUPTION:
372       return Repair(path);
373   }
374   NOTREACHED();
375   return SYNC_DATABASE_ERROR_FAILED;
376 }
377
378 SyncStatusCode LocalFileChangeTracker::TrackerDB::Repair(
379     const std::string& db_path) {
380   DCHECK(!db_.get());
381   LOG(WARNING) << "Attempting to repair TrackerDB.";
382
383   leveldb::Options options;
384   options.max_open_files = 0;  // Use minimum.
385   if (leveldb::RepairDB(db_path, options).ok() &&
386       Init(FAIL_ON_CORRUPTION) == SYNC_STATUS_OK) {
387     // TODO(nhiroki): perform some consistency checks between TrackerDB and
388     // syncable file system.
389     LOG(WARNING) << "Repairing TrackerDB completed.";
390     return SYNC_STATUS_OK;
391   }
392
393   LOG(WARNING) << "Failed to repair TrackerDB.";
394   return SYNC_DATABASE_ERROR_CORRUPTION;
395 }
396
397 // TODO(nhiroki): factor out the common methods into somewhere else.
398 void LocalFileChangeTracker::TrackerDB::HandleError(
399     const tracked_objects::Location& from_here,
400     const leveldb::Status& status) {
401   LOG(ERROR) << "LocalFileChangeTracker::TrackerDB failed at: "
402              << from_here.ToString() << " with error: " << status.ToString();
403 }
404
405 SyncStatusCode LocalFileChangeTracker::TrackerDB::MarkDirty(
406     const std::string& url) {
407   if (db_status_ != SYNC_STATUS_OK)
408     return db_status_;
409
410   db_status_ = Init(REPAIR_ON_CORRUPTION);
411   if (db_status_ != SYNC_STATUS_OK) {
412     db_.reset();
413     return db_status_;
414   }
415
416   leveldb::Status status = db_->Put(leveldb::WriteOptions(), url, kMark);
417   if (!status.ok()) {
418     HandleError(FROM_HERE, status);
419     db_status_ = LevelDBStatusToSyncStatusCode(status);
420     db_.reset();
421     return db_status_;
422   }
423   return SYNC_STATUS_OK;
424 }
425
426 SyncStatusCode LocalFileChangeTracker::TrackerDB::ClearDirty(
427     const std::string& url) {
428   if (db_status_ != SYNC_STATUS_OK)
429     return db_status_;
430
431   // Should not reach here before initializing the database. The database should
432   // be cleared after read, and should be initialized during read if
433   // uninitialized.
434   DCHECK(db_.get());
435
436   leveldb::Status status = db_->Delete(leveldb::WriteOptions(), url);
437   if (!status.ok() && !status.IsNotFound()) {
438     HandleError(FROM_HERE, status);
439     db_status_ = LevelDBStatusToSyncStatusCode(status);
440     db_.reset();
441     return db_status_;
442   }
443   return SYNC_STATUS_OK;
444 }
445
446 SyncStatusCode LocalFileChangeTracker::TrackerDB::GetDirtyEntries(
447     std::queue<FileSystemURL>* dirty_files) {
448   if (db_status_ != SYNC_STATUS_OK)
449     return db_status_;
450
451   db_status_ = Init(REPAIR_ON_CORRUPTION);
452   if (db_status_ != SYNC_STATUS_OK) {
453     db_.reset();
454     return db_status_;
455   }
456
457   scoped_ptr<leveldb::Iterator> iter(db_->NewIterator(leveldb::ReadOptions()));
458   iter->SeekToFirst();
459   FileSystemURL url;
460   while (iter->Valid()) {
461     if (!DeserializeSyncableFileSystemURL(iter->key().ToString(), &url)) {
462       LOG(WARNING) << "Failed to deserialize an URL. "
463                    << "TrackerDB might be corrupted.";
464       db_status_ = SYNC_DATABASE_ERROR_CORRUPTION;
465       db_.reset();
466       return db_status_;
467     }
468     dirty_files->push(url);
469     iter->Next();
470   }
471   return SYNC_STATUS_OK;
472 }
473
474 }  // namespace sync_file_system