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.
4
5#include "chrome/browser/chromeos/drive/resource_metadata.h"
6
7#include "base/guid.h"
8#include "base/rand_util.h"
9#include "base/strings/string_number_conversions.h"
10#include "base/strings/stringprintf.h"
11#include "base/sys_info.h"
12#include "chrome/browser/chromeos/drive/drive.pb.h"
13#include "chrome/browser/chromeos/drive/file_cache.h"
14#include "chrome/browser/chromeos/drive/file_system_util.h"
15#include "chrome/browser/chromeos/drive/resource_metadata_storage.h"
16#include "content/public/browser/browser_thread.h"
17
18using content::BrowserThread;
19
20namespace drive {
21namespace internal {
22namespace {
23
24// Returns true if enough disk space is available for DB operation.
25// TODO(hashimoto): Merge this with FileCache's FreeDiskSpaceGetterInterface.
26bool EnoughDiskSpaceIsAvailableForDBOperation(const base::FilePath& path) {
27  const int64 kRequiredDiskSpaceInMB = 128;  // 128 MB seems to be large enough.
28  return base::SysInfo::AmountOfFreeDiskSpace(path) >=
29      kRequiredDiskSpaceInMB * (1 << 20);
30}
31
32// Returns a file name with a uniquifier appended. (e.g. "File (1).txt")
33std::string GetUniquifiedName(const std::string& name, int uniquifier) {
34  base::FilePath name_path = base::FilePath::FromUTF8Unsafe(name);
35  name_path = name_path.InsertBeforeExtension(
36      base::StringPrintf(" (%d)", uniquifier));
37  return name_path.AsUTF8Unsafe();
38}
39
40// Returns true when there is no entry with the specified name under the parent
41// other than the specified entry.
42FileError EntryCanUseName(ResourceMetadataStorage* storage,
43                          const std::string& parent_local_id,
44                          const std::string& local_id,
45                          const std::string& base_name,
46                          bool* result) {
47  std::string existing_entry_id;
48  FileError error = storage->GetChild(parent_local_id, base_name,
49                                      &existing_entry_id);
50  if (error == FILE_ERROR_OK)
51    *result = existing_entry_id == local_id;
52  else if (error == FILE_ERROR_NOT_FOUND)
53    *result = true;
54  else
55    return error;
56  return FILE_ERROR_OK;
57}
58
59// Returns true when the ID is used by an immutable entry.
60bool IsImmutableEntry(const std::string& id) {
61  return id == util::kDriveGrandRootLocalId ||
62      id == util::kDriveOtherDirLocalId ||
63      id == util::kDriveTrashDirLocalId;
64}
65
66}  // namespace
67
68ResourceMetadata::ResourceMetadata(
69    ResourceMetadataStorage* storage,
70    FileCache* cache,
71    scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
72    : blocking_task_runner_(blocking_task_runner),
73      storage_(storage),
74      cache_(cache) {
75  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
76}
77
78FileError ResourceMetadata::Initialize() {
79  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
80  return SetUpDefaultEntries();
81}
82
83void ResourceMetadata::Destroy() {
84  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
85
86  blocking_task_runner_->PostTask(
87      FROM_HERE,
88      base::Bind(&ResourceMetadata::DestroyOnBlockingPool,
89                 base::Unretained(this)));
90}
91
92FileError ResourceMetadata::Reset() {
93  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
94
95  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
96    return FILE_ERROR_NO_LOCAL_SPACE;
97
98  FileError error = storage_->SetLargestChangestamp(0);
99  if (error != FILE_ERROR_OK)
100    return error;
101
102  // Remove all root entries.
103  scoped_ptr<Iterator> it = GetIterator();
104  for (; !it->IsAtEnd(); it->Advance()) {
105    if (it->GetValue().parent_local_id().empty()) {
106      error = RemoveEntryRecursively(it->GetID());
107      if (error != FILE_ERROR_OK)
108        return error;
109    }
110  }
111  if (it->HasError())
112    return FILE_ERROR_FAILED;
113
114  return SetUpDefaultEntries();
115}
116
117ResourceMetadata::~ResourceMetadata() {
118  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
119}
120
121FileError ResourceMetadata::SetUpDefaultEntries() {
122  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
123
124  // Initialize "/drive".
125  ResourceEntry entry;
126  FileError error = storage_->GetEntry(util::kDriveGrandRootLocalId, &entry);
127  if (error == FILE_ERROR_NOT_FOUND) {
128    ResourceEntry root;
129    root.mutable_file_info()->set_is_directory(true);
130    root.set_local_id(util::kDriveGrandRootLocalId);
131    root.set_title(util::kDriveGrandRootDirName);
132    root.set_base_name(util::kDriveGrandRootDirName);
133    error = storage_->PutEntry(root);
134    if (error != FILE_ERROR_OK)
135      return error;
136  } else if (error == FILE_ERROR_OK) {
137    if (!entry.resource_id().empty()) {
138      // Old implementations used kDriveGrandRootLocalId as a resource ID.
139      entry.clear_resource_id();
140      error = storage_->PutEntry(entry);
141      if (error != FILE_ERROR_OK)
142        return error;
143    }
144  } else {
145    return error;
146  }
147
148  // Initialize "/drive/other".
149  error = storage_->GetEntry(util::kDriveOtherDirLocalId, &entry);
150  if (error == FILE_ERROR_NOT_FOUND) {
151    ResourceEntry other_dir;
152    other_dir.mutable_file_info()->set_is_directory(true);
153    other_dir.set_local_id(util::kDriveOtherDirLocalId);
154    other_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
155    other_dir.set_title(util::kDriveOtherDirName);
156    error = PutEntryUnderDirectory(other_dir);
157    if (error != FILE_ERROR_OK)
158      return error;
159  } else if (error == FILE_ERROR_OK) {
160    if (!entry.resource_id().empty()) {
161      // Old implementations used kDriveOtherDirLocalId as a resource ID.
162      entry.clear_resource_id();
163      error = storage_->PutEntry(entry);
164      if (error != FILE_ERROR_OK)
165        return error;
166    }
167  } else {
168    return error;
169  }
170
171  // Initialize "drive/trash".
172  error = storage_->GetEntry(util::kDriveTrashDirLocalId, &entry);
173  if (error == FILE_ERROR_NOT_FOUND) {
174    ResourceEntry trash_dir;
175    trash_dir.mutable_file_info()->set_is_directory(true);
176    trash_dir.set_local_id(util::kDriveTrashDirLocalId);
177    trash_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
178    trash_dir.set_title(util::kDriveTrashDirName);
179    error = PutEntryUnderDirectory(trash_dir);
180    if (error != FILE_ERROR_OK)
181      return error;
182  } else if (error != FILE_ERROR_OK) {
183    return error;
184  }
185
186  // Initialize "drive/root".
187  std::string child_id;
188  error = storage_->GetChild(
189      util::kDriveGrandRootLocalId, util::kDriveMyDriveRootDirName, &child_id);
190  if (error == FILE_ERROR_NOT_FOUND) {
191    ResourceEntry mydrive;
192    mydrive.mutable_file_info()->set_is_directory(true);
193    mydrive.set_parent_local_id(util::kDriveGrandRootLocalId);
194    mydrive.set_title(util::kDriveMyDriveRootDirName);
195
196    std::string local_id;
197    error = AddEntry(mydrive, &local_id);
198    if (error != FILE_ERROR_OK)
199      return error;
200  } else if (error != FILE_ERROR_OK) {
201    return error;
202  }
203  return FILE_ERROR_OK;
204}
205
206void ResourceMetadata::DestroyOnBlockingPool() {
207  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
208  delete this;
209}
210
211FileError ResourceMetadata::GetLargestChangestamp(int64* out_value) {
212  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
213  return storage_->GetLargestChangestamp(out_value);
214}
215
216FileError ResourceMetadata::SetLargestChangestamp(int64 value) {
217  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
218
219  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
220    return FILE_ERROR_NO_LOCAL_SPACE;
221
222  return storage_->SetLargestChangestamp(value);
223}
224
225FileError ResourceMetadata::AddEntry(const ResourceEntry& entry,
226                                     std::string* out_id) {
227  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
228  DCHECK(entry.local_id().empty());
229
230  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
231    return FILE_ERROR_NO_LOCAL_SPACE;
232
233  ResourceEntry parent;
234  FileError error = storage_->GetEntry(entry.parent_local_id(), &parent);
235  if (error != FILE_ERROR_OK)
236    return error;
237  if (!parent.file_info().is_directory())
238    return FILE_ERROR_NOT_A_DIRECTORY;
239
240  // Multiple entries with the same resource ID should not be present.
241  std::string local_id;
242  ResourceEntry existing_entry;
243  if (!entry.resource_id().empty()) {
244    error = storage_->GetIdByResourceId(entry.resource_id(), &local_id);
245    if (error == FILE_ERROR_OK)
246      error = storage_->GetEntry(local_id, &existing_entry);
247
248    if (error == FILE_ERROR_OK)
249      return FILE_ERROR_EXISTS;
250    else if (error != FILE_ERROR_NOT_FOUND)
251      return error;
252  }
253
254  // Generate unique local ID when needed.
255  // We don't check for ID collisions as its probability is extremely low.
256  if (local_id.empty())
257    local_id = base::GenerateGUID();
258
259  ResourceEntry new_entry(entry);
260  new_entry.set_local_id(local_id);
261
262  error = PutEntryUnderDirectory(new_entry);
263  if (error != FILE_ERROR_OK)
264    return error;
265
266  *out_id = local_id;
267  return FILE_ERROR_OK;
268}
269
270FileError ResourceMetadata::RemoveEntry(const std::string& id) {
271  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
272
273  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
274    return FILE_ERROR_NO_LOCAL_SPACE;
275
276  // Disallow deletion of default entries.
277  if (IsImmutableEntry(id))
278    return FILE_ERROR_ACCESS_DENIED;
279
280  ResourceEntry entry;
281  FileError error = storage_->GetEntry(id, &entry);
282  if (error != FILE_ERROR_OK)
283    return error;
284
285  return RemoveEntryRecursively(id);
286}
287
288FileError ResourceMetadata::GetResourceEntryById(const std::string& id,
289                                                 ResourceEntry* out_entry) {
290  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
291  DCHECK(!id.empty());
292  DCHECK(out_entry);
293
294  return storage_->GetEntry(id, out_entry);
295}
296
297FileError ResourceMetadata::GetResourceEntryByPath(const base::FilePath& path,
298                                                   ResourceEntry* out_entry) {
299  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
300  DCHECK(out_entry);
301
302  std::string id;
303  FileError error = GetIdByPath(path, &id);
304  if (error != FILE_ERROR_OK)
305    return error;
306
307  return GetResourceEntryById(id, out_entry);
308}
309
310FileError ResourceMetadata::ReadDirectoryByPath(
311    const base::FilePath& path,
312    ResourceEntryVector* out_entries) {
313  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
314  DCHECK(out_entries);
315
316  std::string id;
317  FileError error = GetIdByPath(path, &id);
318  if (error != FILE_ERROR_OK)
319    return error;
320  return ReadDirectoryById(id, out_entries);
321}
322
323FileError ResourceMetadata::ReadDirectoryById(
324    const std::string& id,
325    ResourceEntryVector* out_entries) {
326  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
327  DCHECK(out_entries);
328
329  ResourceEntry entry;
330  FileError error = GetResourceEntryById(id, &entry);
331  if (error != FILE_ERROR_OK)
332    return error;
333
334  if (!entry.file_info().is_directory())
335    return FILE_ERROR_NOT_A_DIRECTORY;
336
337  std::vector<std::string> children;
338  error = storage_->GetChildren(id, &children);
339  if (error != FILE_ERROR_OK)
340    return error;
341
342  ResourceEntryVector entries(children.size());
343  for (size_t i = 0; i < children.size(); ++i) {
344    error = storage_->GetEntry(children[i], &entries[i]);
345    if (error != FILE_ERROR_OK)
346      return error;
347  }
348  out_entries->swap(entries);
349  return FILE_ERROR_OK;
350}
351
352FileError ResourceMetadata::RefreshEntry(const ResourceEntry& entry) {
353  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
354
355  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
356    return FILE_ERROR_NO_LOCAL_SPACE;
357
358  ResourceEntry old_entry;
359  FileError error = storage_->GetEntry(entry.local_id(), &old_entry);
360  if (error != FILE_ERROR_OK)
361    return error;
362
363  if (IsImmutableEntry(entry.local_id()) ||
364      old_entry.file_info().is_directory() !=  // Reject incompatible input.
365      entry.file_info().is_directory())
366    return FILE_ERROR_INVALID_OPERATION;
367
368  if (!entry.resource_id().empty()) {
369    // Multiple entries cannot share the same resource ID.
370    std::string local_id;
371    FileError error = GetIdByResourceId(entry.resource_id(), &local_id);
372    switch (error) {
373      case FILE_ERROR_OK:
374        if (local_id != entry.local_id())
375          return FILE_ERROR_INVALID_OPERATION;
376        break;
377
378      case FILE_ERROR_NOT_FOUND:
379        break;
380
381      default:
382        return error;
383    }
384  }
385
386  // Make sure that the new parent exists and it is a directory.
387  ResourceEntry new_parent;
388  error = storage_->GetEntry(entry.parent_local_id(), &new_parent);
389  if (error != FILE_ERROR_OK)
390    return error;
391
392  if (!new_parent.file_info().is_directory())
393    return FILE_ERROR_NOT_A_DIRECTORY;
394
395  // Do not overwrite cache states.
396  // Cache state should be changed via FileCache.
397  ResourceEntry updated_entry(entry);
398  if (old_entry.file_specific_info().has_cache_state()) {
399    *updated_entry.mutable_file_specific_info()->mutable_cache_state() =
400        old_entry.file_specific_info().cache_state();
401  } else if (updated_entry.file_specific_info().has_cache_state()) {
402    updated_entry.mutable_file_specific_info()->clear_cache_state();
403  }
404  // Remove from the old parent and add it to the new parent with the new data.
405  return PutEntryUnderDirectory(updated_entry);
406}
407
408FileError ResourceMetadata::GetSubDirectoriesRecursively(
409    const std::string& id,
410    std::set<base::FilePath>* sub_directories) {
411  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
412
413  std::vector<std::string> children;
414  FileError error = storage_->GetChildren(id, &children);
415  if (error != FILE_ERROR_OK)
416    return error;
417  for (size_t i = 0; i < children.size(); ++i) {
418    ResourceEntry entry;
419    error = storage_->GetEntry(children[i], &entry);
420    if (error != FILE_ERROR_OK)
421      return error;
422    if (entry.file_info().is_directory()) {
423      base::FilePath path;
424      error = GetFilePath(children[i], &path);
425      if (error != FILE_ERROR_OK)
426        return error;
427      sub_directories->insert(path);
428      GetSubDirectoriesRecursively(children[i], sub_directories);
429    }
430  }
431  return FILE_ERROR_OK;
432}
433
434FileError ResourceMetadata::GetChildId(const std::string& parent_local_id,
435                                       const std::string& base_name,
436                                       std::string* out_child_id) {
437  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
438  return storage_->GetChild(parent_local_id, base_name, out_child_id);
439}
440
441scoped_ptr<ResourceMetadata::Iterator> ResourceMetadata::GetIterator() {
442  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
443
444  return storage_->GetIterator();
445}
446
447FileError ResourceMetadata::GetFilePath(const std::string& id,
448                                        base::FilePath* out_file_path) {
449  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
450
451  ResourceEntry entry;
452  FileError error = storage_->GetEntry(id, &entry);
453  if (error != FILE_ERROR_OK)
454    return error;
455
456  base::FilePath path;
457  if (!entry.parent_local_id().empty()) {
458    error = GetFilePath(entry.parent_local_id(), &path);
459    if (error != FILE_ERROR_OK)
460      return error;
461  } else if (entry.local_id() != util::kDriveGrandRootLocalId) {
462    DVLOG(1) << "Entries not under the grand root don't have paths.";
463    return FILE_ERROR_NOT_FOUND;
464  }
465  path = path.Append(base::FilePath::FromUTF8Unsafe(entry.base_name()));
466  *out_file_path = path;
467  return FILE_ERROR_OK;
468}
469
470FileError ResourceMetadata::GetIdByPath(const base::FilePath& file_path,
471                                        std::string* out_id) {
472  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
473
474  // Start from the root.
475  std::vector<base::FilePath::StringType> components;
476  file_path.GetComponents(&components);
477  if (components.empty() || components[0] != util::kDriveGrandRootDirName)
478    return FILE_ERROR_NOT_FOUND;
479
480  // Iterate over the remaining components.
481  std::string id = util::kDriveGrandRootLocalId;
482  for (size_t i = 1; i < components.size(); ++i) {
483    const std::string component = base::FilePath(components[i]).AsUTF8Unsafe();
484    std::string child_id;
485    FileError error = storage_->GetChild(id, component, &child_id);
486    if (error != FILE_ERROR_OK)
487      return error;
488    id = child_id;
489  }
490  *out_id = id;
491  return FILE_ERROR_OK;
492}
493
494FileError ResourceMetadata::GetIdByResourceId(const std::string& resource_id,
495                                              std::string* out_local_id) {
496  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
497  return storage_->GetIdByResourceId(resource_id, out_local_id);
498}
499
500FileError ResourceMetadata::PutEntryUnderDirectory(const ResourceEntry& entry) {
501  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
502  DCHECK(!entry.local_id().empty());
503  DCHECK(!entry.parent_local_id().empty());
504
505  std::string base_name;
506  FileError error = GetDeduplicatedBaseName(entry, &base_name);
507  if (error != FILE_ERROR_OK)
508    return error;
509  ResourceEntry updated_entry(entry);
510  updated_entry.set_base_name(base_name);
511  return storage_->PutEntry(updated_entry);
512}
513
514FileError ResourceMetadata::GetDeduplicatedBaseName(
515    const ResourceEntry& entry,
516    std::string* base_name) {
517  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
518  DCHECK(!entry.parent_local_id().empty());
519  DCHECK(!entry.title().empty());
520
521  // The entry name may have been changed due to prior name de-duplication.
522  // We need to first restore the file name based on the title before going
523  // through name de-duplication again when it is added to another directory.
524  *base_name = entry.title();
525  if (entry.has_file_specific_info() &&
526      entry.file_specific_info().is_hosted_document()) {
527    *base_name += entry.file_specific_info().document_extension();
528  }
529  *base_name = util::NormalizeFileName(*base_name);
530
531  // If |base_name| is not used, just return it.
532  bool can_use_name = false;
533  FileError error = EntryCanUseName(storage_, entry.parent_local_id(),
534                                    entry.local_id(), *base_name,
535                                    &can_use_name);
536  if (error != FILE_ERROR_OK || can_use_name)
537    return error;
538
539  // Find an unused number with binary search.
540  int smallest_known_unused_modifier = 1;
541  while (true) {
542    error = EntryCanUseName(storage_, entry.parent_local_id(), entry.local_id(),
543                            GetUniquifiedName(*base_name,
544                                              smallest_known_unused_modifier),
545                            &can_use_name);
546    if (error != FILE_ERROR_OK)
547      return error;
548    if (can_use_name)
549      break;
550
551    const int delta = base::RandInt(1, smallest_known_unused_modifier);
552    if (smallest_known_unused_modifier <= INT_MAX - delta) {
553      smallest_known_unused_modifier += delta;
554    } else {  // No luck finding an unused number. Try again.
555      smallest_known_unused_modifier = 1;
556    }
557  }
558
559  int largest_known_used_modifier = 1;
560  while (smallest_known_unused_modifier - largest_known_used_modifier > 1) {
561    const int modifier = largest_known_used_modifier +
562        (smallest_known_unused_modifier - largest_known_used_modifier) / 2;
563
564    error = EntryCanUseName(storage_, entry.parent_local_id(), entry.local_id(),
565                            GetUniquifiedName(*base_name, modifier),
566                            &can_use_name);
567    if (error != FILE_ERROR_OK)
568      return error;
569    if (can_use_name) {
570      smallest_known_unused_modifier = modifier;
571    } else {
572      largest_known_used_modifier = modifier;
573    }
574  }
575  *base_name = GetUniquifiedName(*base_name, smallest_known_unused_modifier);
576  return FILE_ERROR_OK;
577}
578
579FileError ResourceMetadata::RemoveEntryRecursively(const std::string& id) {
580  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
581
582  ResourceEntry entry;
583  FileError error = storage_->GetEntry(id, &entry);
584  if (error != FILE_ERROR_OK)
585    return error;
586
587  if (entry.file_info().is_directory()) {
588    std::vector<std::string> children;
589    error = storage_->GetChildren(id, &children);
590    if (error != FILE_ERROR_OK)
591      return error;
592    for (size_t i = 0; i < children.size(); ++i) {
593      error = RemoveEntryRecursively(children[i]);
594      if (error != FILE_ERROR_OK)
595        return error;
596    }
597  }
598
599  error = cache_->Remove(id);
600  if (error != FILE_ERROR_OK)
601    return error;
602
603  return storage_->RemoveEntry(id);
604}
605
606}  // namespace internal
607}  // namespace drive
608