Home | History | Annotate | Download | only in drive
      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/strings/string_number_conversions.h"
      9 #include "base/strings/stringprintf.h"
     10 #include "base/sys_info.h"
     11 #include "chrome/browser/chromeos/drive/drive.pb.h"
     12 #include "chrome/browser/chromeos/drive/file_system_util.h"
     13 #include "chrome/browser/chromeos/drive/resource_metadata_storage.h"
     14 #include "content/public/browser/browser_thread.h"
     15 
     16 using content::BrowserThread;
     17 
     18 namespace drive {
     19 namespace {
     20 
     21 // Sets entry's base name from its title and other attributes.
     22 void SetBaseNameFromTitle(ResourceEntry* entry) {
     23   std::string base_name = entry->title();
     24   if (entry->has_file_specific_info() &&
     25       entry->file_specific_info().is_hosted_document()) {
     26     base_name += entry->file_specific_info().document_extension();
     27   }
     28   entry->set_base_name(util::NormalizeFileName(base_name));
     29 }
     30 
     31 // Returns true if enough disk space is available for DB operation.
     32 // TODO(hashimoto): Merge this with FileCache's FreeDiskSpaceGetterInterface.
     33 bool EnoughDiskSpaceIsAvailableForDBOperation(const base::FilePath& path) {
     34   const int64 kRequiredDiskSpaceInMB = 128;  // 128 MB seems to be large enough.
     35   return base::SysInfo::AmountOfFreeDiskSpace(path) >=
     36       kRequiredDiskSpaceInMB * (1 << 20);
     37 }
     38 
     39 // Runs |callback| with arguments.
     40 void RunGetResourceEntryCallback(const GetResourceEntryCallback& callback,
     41                                  scoped_ptr<ResourceEntry> entry,
     42                                  FileError error) {
     43   DCHECK(!callback.is_null());
     44 
     45   if (error != FILE_ERROR_OK)
     46     entry.reset();
     47   callback.Run(error, entry.Pass());
     48 }
     49 
     50 // Runs |callback| with arguments.
     51 void RunReadDirectoryCallback(const ReadDirectoryCallback& callback,
     52                               scoped_ptr<ResourceEntryVector> entries,
     53                               FileError error) {
     54   DCHECK(!callback.is_null());
     55 
     56   if (error != FILE_ERROR_OK)
     57     entries.reset();
     58   callback.Run(error, entries.Pass());
     59 }
     60 
     61 }  // namespace
     62 
     63 namespace internal {
     64 
     65 ResourceMetadata::ResourceMetadata(
     66     ResourceMetadataStorage* storage,
     67     scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
     68     : blocking_task_runner_(blocking_task_runner),
     69       storage_(storage),
     70       weak_ptr_factory_(this) {
     71   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     72 }
     73 
     74 FileError ResourceMetadata::Initialize() {
     75   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
     76 
     77   if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
     78     return FILE_ERROR_NO_LOCAL_SPACE;
     79 
     80   if (!SetUpDefaultEntries())
     81     return FILE_ERROR_FAILED;
     82 
     83   return FILE_ERROR_OK;
     84 }
     85 
     86 void ResourceMetadata::Destroy() {
     87   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     88 
     89   weak_ptr_factory_.InvalidateWeakPtrs();
     90   blocking_task_runner_->PostTask(
     91       FROM_HERE,
     92       base::Bind(&ResourceMetadata::DestroyOnBlockingPool,
     93                  base::Unretained(this)));
     94 }
     95 
     96 FileError ResourceMetadata::Reset() {
     97   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
     98 
     99   if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    100     return FILE_ERROR_NO_LOCAL_SPACE;
    101 
    102   if (!storage_->SetLargestChangestamp(0) ||
    103       !RemoveEntryRecursively(util::kDriveGrandRootLocalId) ||
    104       !SetUpDefaultEntries())
    105     return FILE_ERROR_FAILED;
    106 
    107   return FILE_ERROR_OK;
    108 }
    109 
    110 ResourceMetadata::~ResourceMetadata() {
    111   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    112 }
    113 
    114 bool ResourceMetadata::SetUpDefaultEntries() {
    115   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    116 
    117   // Initialize "/drive", "/drive/other" and "drive/trash".
    118   ResourceEntry entry;
    119   if (!storage_->GetEntry(util::kDriveGrandRootLocalId, &entry)) {
    120     ResourceEntry root;
    121     root.mutable_file_info()->set_is_directory(true);
    122     // TODO(hashimoto): Stop setting dummy resource ID here.
    123     root.set_resource_id(util::kDriveGrandRootLocalId);
    124     root.set_local_id(util::kDriveGrandRootLocalId);
    125     root.set_title(util::kDriveGrandRootDirName);
    126     SetBaseNameFromTitle(&root);
    127     if (!storage_->PutEntry(root))
    128       return false;
    129   }
    130   if (!storage_->GetEntry(util::kDriveOtherDirLocalId, &entry)) {
    131     ResourceEntry other_dir;
    132     other_dir.mutable_file_info()->set_is_directory(true);
    133     // TODO(hashimoto): Stop setting dummy resource ID here.
    134     other_dir.set_resource_id(util::kDriveOtherDirLocalId);
    135     other_dir.set_local_id(util::kDriveOtherDirLocalId);
    136     other_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
    137     other_dir.set_title(util::kDriveOtherDirName);
    138     if (!PutEntryUnderDirectory(other_dir))
    139       return false;
    140   }
    141   if (!storage_->GetEntry(util::kDriveTrashDirLocalId, &entry)) {
    142     ResourceEntry trash_dir;
    143     trash_dir.mutable_file_info()->set_is_directory(true);
    144     trash_dir.set_local_id(util::kDriveTrashDirLocalId);
    145     trash_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
    146     trash_dir.set_title(util::kDriveTrashDirName);
    147     if (!PutEntryUnderDirectory(trash_dir))
    148       return false;
    149   }
    150   return true;
    151 }
    152 
    153 void ResourceMetadata::DestroyOnBlockingPool() {
    154   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    155   delete this;
    156 }
    157 
    158 int64 ResourceMetadata::GetLargestChangestamp() {
    159   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    160   return storage_->GetLargestChangestamp();
    161 }
    162 
    163 FileError ResourceMetadata::SetLargestChangestamp(int64 value) {
    164   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    165 
    166   if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    167     return FILE_ERROR_NO_LOCAL_SPACE;
    168 
    169   return storage_->SetLargestChangestamp(value) ?
    170       FILE_ERROR_OK : FILE_ERROR_FAILED;
    171 }
    172 
    173 FileError ResourceMetadata::AddEntry(const ResourceEntry& entry,
    174                                      std::string* out_id) {
    175   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    176   DCHECK(entry.local_id().empty());
    177 
    178   if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    179     return FILE_ERROR_NO_LOCAL_SPACE;
    180 
    181   ResourceEntry parent;
    182   if (!storage_->GetEntry(entry.parent_local_id(), &parent) ||
    183       !parent.file_info().is_directory())
    184     return FILE_ERROR_NOT_FOUND;
    185 
    186   // Multiple entries with the same resource ID should not be present.
    187   std::string local_id;
    188   ResourceEntry existing_entry;
    189   if (!entry.resource_id().empty() &&
    190       storage_->GetIdByResourceId(entry.resource_id(), &local_id) &&
    191       storage_->GetEntry(local_id, &existing_entry))
    192     return FILE_ERROR_EXISTS;
    193 
    194   // Generate unique local ID when needed.
    195   while (local_id.empty() || storage_->GetEntry(local_id, &existing_entry))
    196     local_id = base::GenerateGUID();
    197 
    198   ResourceEntry new_entry(entry);
    199   new_entry.set_local_id(local_id);
    200 
    201   if (!PutEntryUnderDirectory(new_entry))
    202     return FILE_ERROR_FAILED;
    203 
    204   *out_id = local_id;
    205   return FILE_ERROR_OK;
    206 }
    207 
    208 FileError ResourceMetadata::RemoveEntry(const std::string& id) {
    209   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    210 
    211   if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    212     return FILE_ERROR_NO_LOCAL_SPACE;
    213 
    214   // Disallow deletion of default entries.
    215   if (id == util::kDriveGrandRootLocalId ||
    216       id == util::kDriveOtherDirLocalId ||
    217       id == util::kDriveTrashDirLocalId)
    218     return FILE_ERROR_ACCESS_DENIED;
    219 
    220   ResourceEntry entry;
    221   if (!storage_->GetEntry(id, &entry))
    222     return FILE_ERROR_NOT_FOUND;
    223 
    224   if (!RemoveEntryRecursively(id))
    225     return FILE_ERROR_FAILED;
    226   return FILE_ERROR_OK;
    227 }
    228 
    229 FileError ResourceMetadata::GetResourceEntryById(const std::string& id,
    230                                                  ResourceEntry* out_entry) {
    231   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    232   DCHECK(!id.empty());
    233   DCHECK(out_entry);
    234 
    235   return storage_->GetEntry(id, out_entry) ?
    236       FILE_ERROR_OK : FILE_ERROR_NOT_FOUND;
    237 }
    238 
    239 void ResourceMetadata::GetResourceEntryByPathOnUIThread(
    240     const base::FilePath& file_path,
    241     const GetResourceEntryCallback& callback) {
    242   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    243   DCHECK(!callback.is_null());
    244 
    245   scoped_ptr<ResourceEntry> entry(new ResourceEntry);
    246   ResourceEntry* entry_ptr = entry.get();
    247   base::PostTaskAndReplyWithResult(
    248       blocking_task_runner_.get(),
    249       FROM_HERE,
    250       base::Bind(&ResourceMetadata::GetResourceEntryByPath,
    251                  base::Unretained(this),
    252                  file_path,
    253                  entry_ptr),
    254       base::Bind(&RunGetResourceEntryCallback, callback, base::Passed(&entry)));
    255 }
    256 
    257 FileError ResourceMetadata::GetResourceEntryByPath(const base::FilePath& path,
    258                                                    ResourceEntry* out_entry) {
    259   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    260   DCHECK(out_entry);
    261 
    262   std::string id;
    263   FileError error = GetIdByPath(path, &id);
    264   if (error != FILE_ERROR_OK)
    265     return error;
    266 
    267   return GetResourceEntryById(id, out_entry);
    268 }
    269 
    270 void ResourceMetadata::ReadDirectoryByPathOnUIThread(
    271     const base::FilePath& file_path,
    272     const ReadDirectoryCallback& callback) {
    273   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    274   DCHECK(!callback.is_null());
    275 
    276   scoped_ptr<ResourceEntryVector> entries(new ResourceEntryVector);
    277   ResourceEntryVector* entries_ptr = entries.get();
    278   base::PostTaskAndReplyWithResult(
    279       blocking_task_runner_.get(),
    280       FROM_HERE,
    281       base::Bind(&ResourceMetadata::ReadDirectoryByPath,
    282                  base::Unretained(this),
    283                  file_path,
    284                  entries_ptr),
    285       base::Bind(&RunReadDirectoryCallback, callback, base::Passed(&entries)));
    286 }
    287 
    288 FileError ResourceMetadata::ReadDirectoryByPath(
    289     const base::FilePath& path,
    290     ResourceEntryVector* out_entries) {
    291   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    292   DCHECK(out_entries);
    293 
    294   std::string id;
    295   FileError error = GetIdByPath(path, &id);
    296   if (error != FILE_ERROR_OK)
    297     return error;
    298 
    299   ResourceEntry entry;
    300   error = GetResourceEntryById(id, &entry);
    301   if (error != FILE_ERROR_OK)
    302     return error;
    303 
    304   if (!entry.file_info().is_directory())
    305     return FILE_ERROR_NOT_A_DIRECTORY;
    306 
    307   std::vector<std::string> children;
    308   storage_->GetChildren(id, &children);
    309 
    310   ResourceEntryVector entries(children.size());
    311   for (size_t i = 0; i < children.size(); ++i) {
    312     if (!storage_->GetEntry(children[i], &entries[i]))
    313       return FILE_ERROR_FAILED;
    314   }
    315   out_entries->swap(entries);
    316   return FILE_ERROR_OK;
    317 }
    318 
    319 FileError ResourceMetadata::RefreshEntry(const ResourceEntry& entry) {
    320   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    321   // TODO(hashimoto): Return an error if the operation will result in having
    322   // multiple entries with the same resource ID.
    323 
    324   if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    325     return FILE_ERROR_NO_LOCAL_SPACE;
    326 
    327   ResourceEntry old_entry;
    328   if (!storage_->GetEntry(entry.local_id(), &old_entry))
    329     return FILE_ERROR_NOT_FOUND;
    330 
    331   if (old_entry.parent_local_id().empty() ||  // Reject root.
    332       old_entry.file_info().is_directory() !=  // Reject incompatible input.
    333       entry.file_info().is_directory())
    334     return FILE_ERROR_INVALID_OPERATION;
    335 
    336   // Make sure that the new parent exists and it is a directory.
    337   ResourceEntry new_parent;
    338   if (!storage_->GetEntry(entry.parent_local_id(), &new_parent))
    339     return FILE_ERROR_NOT_FOUND;
    340 
    341   if (!new_parent.file_info().is_directory())
    342     return FILE_ERROR_NOT_A_DIRECTORY;
    343 
    344   // Remove from the old parent and add it to the new parent with the new data.
    345   if (!PutEntryUnderDirectory(entry))
    346     return FILE_ERROR_FAILED;
    347   return FILE_ERROR_OK;
    348 }
    349 
    350 void ResourceMetadata::GetSubDirectoriesRecursively(
    351     const std::string& id,
    352     std::set<base::FilePath>* sub_directories) {
    353   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    354 
    355   std::vector<std::string> children;
    356   storage_->GetChildren(id, &children);
    357   for (size_t i = 0; i < children.size(); ++i) {
    358     ResourceEntry entry;
    359     if (storage_->GetEntry(children[i], &entry) &&
    360         entry.file_info().is_directory()) {
    361       sub_directories->insert(GetFilePath(children[i]));
    362       GetSubDirectoriesRecursively(children[i], sub_directories);
    363     }
    364   }
    365 }
    366 
    367 std::string ResourceMetadata::GetChildId(const std::string& parent_local_id,
    368                                          const std::string& base_name) {
    369   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    370   return storage_->GetChild(parent_local_id, base_name);
    371 }
    372 
    373 scoped_ptr<ResourceMetadata::Iterator> ResourceMetadata::GetIterator() {
    374   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    375 
    376   return storage_->GetIterator();
    377 }
    378 
    379 base::FilePath ResourceMetadata::GetFilePath(const std::string& id) {
    380   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    381 
    382   base::FilePath path;
    383   ResourceEntry entry;
    384   if (storage_->GetEntry(id, &entry)) {
    385     if (!entry.parent_local_id().empty())
    386       path = GetFilePath(entry.parent_local_id());
    387     path = path.Append(base::FilePath::FromUTF8Unsafe(entry.base_name()));
    388   }
    389   return path;
    390 }
    391 
    392 FileError ResourceMetadata::GetIdByPath(const base::FilePath& file_path,
    393                                         std::string* out_id) {
    394   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    395 
    396   // Start from the root.
    397   std::vector<base::FilePath::StringType> components;
    398   file_path.GetComponents(&components);
    399   if (components.empty() || components[0] != util::kDriveGrandRootDirName)
    400     return FILE_ERROR_NOT_FOUND;
    401 
    402   // Iterate over the remaining components.
    403   std::string id = util::kDriveGrandRootLocalId;
    404   for (size_t i = 1; i < components.size(); ++i) {
    405     const std::string component = base::FilePath(components[i]).AsUTF8Unsafe();
    406     id = storage_->GetChild(id, component);
    407     if (id.empty())
    408       return FILE_ERROR_NOT_FOUND;
    409   }
    410   *out_id = id;
    411   return FILE_ERROR_OK;
    412 }
    413 
    414 FileError ResourceMetadata::GetIdByResourceId(const std::string& resource_id,
    415                                               std::string* out_local_id) {
    416   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    417 
    418   return storage_->GetIdByResourceId(resource_id, out_local_id) ?
    419       FILE_ERROR_OK : FILE_ERROR_NOT_FOUND;
    420 }
    421 
    422 bool ResourceMetadata::PutEntryUnderDirectory(const ResourceEntry& entry) {
    423   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    424   DCHECK(!entry.local_id().empty());
    425   DCHECK(!entry.parent_local_id().empty());
    426 
    427   ResourceEntry updated_entry(entry);
    428 
    429   // The entry name may have been changed due to prior name de-duplication.
    430   // We need to first restore the file name based on the title before going
    431   // through name de-duplication again when it is added to another directory.
    432   SetBaseNameFromTitle(&updated_entry);
    433 
    434   // Do file name de-duplication - Keep changing |entry|'s name until there is
    435   // no other entry with the same name under the parent.
    436   int modifier = 0;
    437   std::string new_base_name = updated_entry.base_name();
    438   while (true) {
    439     const std::string existing_entry_id =
    440         storage_->GetChild(entry.parent_local_id(), new_base_name);
    441     if (existing_entry_id.empty() || existing_entry_id == entry.local_id())
    442       break;
    443 
    444     base::FilePath new_path =
    445         base::FilePath::FromUTF8Unsafe(updated_entry.base_name());
    446     new_path =
    447         new_path.InsertBeforeExtension(base::StringPrintf(" (%d)", ++modifier));
    448     // The new filename must be different from the previous one.
    449     DCHECK_NE(new_base_name, new_path.AsUTF8Unsafe());
    450     new_base_name = new_path.AsUTF8Unsafe();
    451   }
    452   updated_entry.set_base_name(new_base_name);
    453 
    454   // Add the entry to resource map.
    455   return storage_->PutEntry(updated_entry);
    456 }
    457 
    458 bool ResourceMetadata::RemoveEntryRecursively(const std::string& id) {
    459   DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
    460 
    461   ResourceEntry entry;
    462   if (!storage_->GetEntry(id, &entry))
    463     return false;
    464 
    465   if (entry.file_info().is_directory()) {
    466     std::vector<std::string> children;
    467     storage_->GetChildren(id, &children);
    468     for (size_t i = 0; i < children.size(); ++i) {
    469       if (!RemoveEntryRecursively(children[i]))
    470         return false;
    471     }
    472   }
    473   return storage_->RemoveEntry(id);
    474 }
    475 
    476 }  // namespace internal
    477 }  // namespace drive
    478