普通文本  |  478行  |  15.89 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/drive/resource_metadata.h"

#include "base/guid.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "chrome/browser/chromeos/drive/drive.pb.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/drive/resource_metadata_storage.h"
#include "content/public/browser/browser_thread.h"

using content::BrowserThread;

namespace drive {
namespace {

// Sets entry's base name from its title and other attributes.
void SetBaseNameFromTitle(ResourceEntry* entry) {
  std::string base_name = entry->title();
  if (entry->has_file_specific_info() &&
      entry->file_specific_info().is_hosted_document()) {
    base_name += entry->file_specific_info().document_extension();
  }
  entry->set_base_name(util::NormalizeFileName(base_name));
}

// Returns true if enough disk space is available for DB operation.
// TODO(hashimoto): Merge this with FileCache's FreeDiskSpaceGetterInterface.
bool EnoughDiskSpaceIsAvailableForDBOperation(const base::FilePath& path) {
  const int64 kRequiredDiskSpaceInMB = 128;  // 128 MB seems to be large enough.
  return base::SysInfo::AmountOfFreeDiskSpace(path) >=
      kRequiredDiskSpaceInMB * (1 << 20);
}

// Runs |callback| with arguments.
void RunGetResourceEntryCallback(const GetResourceEntryCallback& callback,
                                 scoped_ptr<ResourceEntry> entry,
                                 FileError error) {
  DCHECK(!callback.is_null());

  if (error != FILE_ERROR_OK)
    entry.reset();
  callback.Run(error, entry.Pass());
}

// Runs |callback| with arguments.
void RunReadDirectoryCallback(const ReadDirectoryCallback& callback,
                              scoped_ptr<ResourceEntryVector> entries,
                              FileError error) {
  DCHECK(!callback.is_null());

  if (error != FILE_ERROR_OK)
    entries.reset();
  callback.Run(error, entries.Pass());
}

}  // namespace

namespace internal {

ResourceMetadata::ResourceMetadata(
    ResourceMetadataStorage* storage,
    scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
    : blocking_task_runner_(blocking_task_runner),
      storage_(storage),
      weak_ptr_factory_(this) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}

FileError ResourceMetadata::Initialize() {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());

  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    return FILE_ERROR_NO_LOCAL_SPACE;

  if (!SetUpDefaultEntries())
    return FILE_ERROR_FAILED;

  return FILE_ERROR_OK;
}

void ResourceMetadata::Destroy() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  weak_ptr_factory_.InvalidateWeakPtrs();
  blocking_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&ResourceMetadata::DestroyOnBlockingPool,
                 base::Unretained(this)));
}

FileError ResourceMetadata::Reset() {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());

  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    return FILE_ERROR_NO_LOCAL_SPACE;

  if (!storage_->SetLargestChangestamp(0) ||
      !RemoveEntryRecursively(util::kDriveGrandRootLocalId) ||
      !SetUpDefaultEntries())
    return FILE_ERROR_FAILED;

  return FILE_ERROR_OK;
}

ResourceMetadata::~ResourceMetadata() {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
}

bool ResourceMetadata::SetUpDefaultEntries() {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());

  // Initialize "/drive", "/drive/other" and "drive/trash".
  ResourceEntry entry;
  if (!storage_->GetEntry(util::kDriveGrandRootLocalId, &entry)) {
    ResourceEntry root;
    root.mutable_file_info()->set_is_directory(true);
    // TODO(hashimoto): Stop setting dummy resource ID here.
    root.set_resource_id(util::kDriveGrandRootLocalId);
    root.set_local_id(util::kDriveGrandRootLocalId);
    root.set_title(util::kDriveGrandRootDirName);
    SetBaseNameFromTitle(&root);
    if (!storage_->PutEntry(root))
      return false;
  }
  if (!storage_->GetEntry(util::kDriveOtherDirLocalId, &entry)) {
    ResourceEntry other_dir;
    other_dir.mutable_file_info()->set_is_directory(true);
    // TODO(hashimoto): Stop setting dummy resource ID here.
    other_dir.set_resource_id(util::kDriveOtherDirLocalId);
    other_dir.set_local_id(util::kDriveOtherDirLocalId);
    other_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
    other_dir.set_title(util::kDriveOtherDirName);
    if (!PutEntryUnderDirectory(other_dir))
      return false;
  }
  if (!storage_->GetEntry(util::kDriveTrashDirLocalId, &entry)) {
    ResourceEntry trash_dir;
    trash_dir.mutable_file_info()->set_is_directory(true);
    trash_dir.set_local_id(util::kDriveTrashDirLocalId);
    trash_dir.set_parent_local_id(util::kDriveGrandRootLocalId);
    trash_dir.set_title(util::kDriveTrashDirName);
    if (!PutEntryUnderDirectory(trash_dir))
      return false;
  }
  return true;
}

void ResourceMetadata::DestroyOnBlockingPool() {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
  delete this;
}

int64 ResourceMetadata::GetLargestChangestamp() {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
  return storage_->GetLargestChangestamp();
}

FileError ResourceMetadata::SetLargestChangestamp(int64 value) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());

  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    return FILE_ERROR_NO_LOCAL_SPACE;

  return storage_->SetLargestChangestamp(value) ?
      FILE_ERROR_OK : FILE_ERROR_FAILED;
}

FileError ResourceMetadata::AddEntry(const ResourceEntry& entry,
                                     std::string* out_id) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
  DCHECK(entry.local_id().empty());

  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    return FILE_ERROR_NO_LOCAL_SPACE;

  ResourceEntry parent;
  if (!storage_->GetEntry(entry.parent_local_id(), &parent) ||
      !parent.file_info().is_directory())
    return FILE_ERROR_NOT_FOUND;

  // Multiple entries with the same resource ID should not be present.
  std::string local_id;
  ResourceEntry existing_entry;
  if (!entry.resource_id().empty() &&
      storage_->GetIdByResourceId(entry.resource_id(), &local_id) &&
      storage_->GetEntry(local_id, &existing_entry))
    return FILE_ERROR_EXISTS;

  // Generate unique local ID when needed.
  while (local_id.empty() || storage_->GetEntry(local_id, &existing_entry))
    local_id = base::GenerateGUID();

  ResourceEntry new_entry(entry);
  new_entry.set_local_id(local_id);

  if (!PutEntryUnderDirectory(new_entry))
    return FILE_ERROR_FAILED;

  *out_id = local_id;
  return FILE_ERROR_OK;
}

FileError ResourceMetadata::RemoveEntry(const std::string& id) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());

  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    return FILE_ERROR_NO_LOCAL_SPACE;

  // Disallow deletion of default entries.
  if (id == util::kDriveGrandRootLocalId ||
      id == util::kDriveOtherDirLocalId ||
      id == util::kDriveTrashDirLocalId)
    return FILE_ERROR_ACCESS_DENIED;

  ResourceEntry entry;
  if (!storage_->GetEntry(id, &entry))
    return FILE_ERROR_NOT_FOUND;

  if (!RemoveEntryRecursively(id))
    return FILE_ERROR_FAILED;
  return FILE_ERROR_OK;
}

FileError ResourceMetadata::GetResourceEntryById(const std::string& id,
                                                 ResourceEntry* out_entry) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
  DCHECK(!id.empty());
  DCHECK(out_entry);

  return storage_->GetEntry(id, out_entry) ?
      FILE_ERROR_OK : FILE_ERROR_NOT_FOUND;
}

void ResourceMetadata::GetResourceEntryByPathOnUIThread(
    const base::FilePath& file_path,
    const GetResourceEntryCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  scoped_ptr<ResourceEntry> entry(new ResourceEntry);
  ResourceEntry* entry_ptr = entry.get();
  base::PostTaskAndReplyWithResult(
      blocking_task_runner_.get(),
      FROM_HERE,
      base::Bind(&ResourceMetadata::GetResourceEntryByPath,
                 base::Unretained(this),
                 file_path,
                 entry_ptr),
      base::Bind(&RunGetResourceEntryCallback, callback, base::Passed(&entry)));
}

FileError ResourceMetadata::GetResourceEntryByPath(const base::FilePath& path,
                                                   ResourceEntry* out_entry) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
  DCHECK(out_entry);

  std::string id;
  FileError error = GetIdByPath(path, &id);
  if (error != FILE_ERROR_OK)
    return error;

  return GetResourceEntryById(id, out_entry);
}

void ResourceMetadata::ReadDirectoryByPathOnUIThread(
    const base::FilePath& file_path,
    const ReadDirectoryCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(!callback.is_null());

  scoped_ptr<ResourceEntryVector> entries(new ResourceEntryVector);
  ResourceEntryVector* entries_ptr = entries.get();
  base::PostTaskAndReplyWithResult(
      blocking_task_runner_.get(),
      FROM_HERE,
      base::Bind(&ResourceMetadata::ReadDirectoryByPath,
                 base::Unretained(this),
                 file_path,
                 entries_ptr),
      base::Bind(&RunReadDirectoryCallback, callback, base::Passed(&entries)));
}

FileError ResourceMetadata::ReadDirectoryByPath(
    const base::FilePath& path,
    ResourceEntryVector* out_entries) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
  DCHECK(out_entries);

  std::string id;
  FileError error = GetIdByPath(path, &id);
  if (error != FILE_ERROR_OK)
    return error;

  ResourceEntry entry;
  error = GetResourceEntryById(id, &entry);
  if (error != FILE_ERROR_OK)
    return error;

  if (!entry.file_info().is_directory())
    return FILE_ERROR_NOT_A_DIRECTORY;

  std::vector<std::string> children;
  storage_->GetChildren(id, &children);

  ResourceEntryVector entries(children.size());
  for (size_t i = 0; i < children.size(); ++i) {
    if (!storage_->GetEntry(children[i], &entries[i]))
      return FILE_ERROR_FAILED;
  }
  out_entries->swap(entries);
  return FILE_ERROR_OK;
}

FileError ResourceMetadata::RefreshEntry(const ResourceEntry& entry) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
  // TODO(hashimoto): Return an error if the operation will result in having
  // multiple entries with the same resource ID.

  if (!EnoughDiskSpaceIsAvailableForDBOperation(storage_->directory_path()))
    return FILE_ERROR_NO_LOCAL_SPACE;

  ResourceEntry old_entry;
  if (!storage_->GetEntry(entry.local_id(), &old_entry))
    return FILE_ERROR_NOT_FOUND;

  if (old_entry.parent_local_id().empty() ||  // Reject root.
      old_entry.file_info().is_directory() !=  // Reject incompatible input.
      entry.file_info().is_directory())
    return FILE_ERROR_INVALID_OPERATION;

  // Make sure that the new parent exists and it is a directory.
  ResourceEntry new_parent;
  if (!storage_->GetEntry(entry.parent_local_id(), &new_parent))
    return FILE_ERROR_NOT_FOUND;

  if (!new_parent.file_info().is_directory())
    return FILE_ERROR_NOT_A_DIRECTORY;

  // Remove from the old parent and add it to the new parent with the new data.
  if (!PutEntryUnderDirectory(entry))
    return FILE_ERROR_FAILED;
  return FILE_ERROR_OK;
}

void ResourceMetadata::GetSubDirectoriesRecursively(
    const std::string& id,
    std::set<base::FilePath>* sub_directories) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());

  std::vector<std::string> children;
  storage_->GetChildren(id, &children);
  for (size_t i = 0; i < children.size(); ++i) {
    ResourceEntry entry;
    if (storage_->GetEntry(children[i], &entry) &&
        entry.file_info().is_directory()) {
      sub_directories->insert(GetFilePath(children[i]));
      GetSubDirectoriesRecursively(children[i], sub_directories);
    }
  }
}

std::string ResourceMetadata::GetChildId(const std::string& parent_local_id,
                                         const std::string& base_name) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
  return storage_->GetChild(parent_local_id, base_name);
}

scoped_ptr<ResourceMetadata::Iterator> ResourceMetadata::GetIterator() {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());

  return storage_->GetIterator();
}

base::FilePath ResourceMetadata::GetFilePath(const std::string& id) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());

  base::FilePath path;
  ResourceEntry entry;
  if (storage_->GetEntry(id, &entry)) {
    if (!entry.parent_local_id().empty())
      path = GetFilePath(entry.parent_local_id());
    path = path.Append(base::FilePath::FromUTF8Unsafe(entry.base_name()));
  }
  return path;
}

FileError ResourceMetadata::GetIdByPath(const base::FilePath& file_path,
                                        std::string* out_id) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());

  // Start from the root.
  std::vector<base::FilePath::StringType> components;
  file_path.GetComponents(&components);
  if (components.empty() || components[0] != util::kDriveGrandRootDirName)
    return FILE_ERROR_NOT_FOUND;

  // Iterate over the remaining components.
  std::string id = util::kDriveGrandRootLocalId;
  for (size_t i = 1; i < components.size(); ++i) {
    const std::string component = base::FilePath(components[i]).AsUTF8Unsafe();
    id = storage_->GetChild(id, component);
    if (id.empty())
      return FILE_ERROR_NOT_FOUND;
  }
  *out_id = id;
  return FILE_ERROR_OK;
}

FileError ResourceMetadata::GetIdByResourceId(const std::string& resource_id,
                                              std::string* out_local_id) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());

  return storage_->GetIdByResourceId(resource_id, out_local_id) ?
      FILE_ERROR_OK : FILE_ERROR_NOT_FOUND;
}

bool ResourceMetadata::PutEntryUnderDirectory(const ResourceEntry& entry) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());
  DCHECK(!entry.local_id().empty());
  DCHECK(!entry.parent_local_id().empty());

  ResourceEntry updated_entry(entry);

  // The entry name may have been changed due to prior name de-duplication.
  // We need to first restore the file name based on the title before going
  // through name de-duplication again when it is added to another directory.
  SetBaseNameFromTitle(&updated_entry);

  // Do file name de-duplication - Keep changing |entry|'s name until there is
  // no other entry with the same name under the parent.
  int modifier = 0;
  std::string new_base_name = updated_entry.base_name();
  while (true) {
    const std::string existing_entry_id =
        storage_->GetChild(entry.parent_local_id(), new_base_name);
    if (existing_entry_id.empty() || existing_entry_id == entry.local_id())
      break;

    base::FilePath new_path =
        base::FilePath::FromUTF8Unsafe(updated_entry.base_name());
    new_path =
        new_path.InsertBeforeExtension(base::StringPrintf(" (%d)", ++modifier));
    // The new filename must be different from the previous one.
    DCHECK_NE(new_base_name, new_path.AsUTF8Unsafe());
    new_base_name = new_path.AsUTF8Unsafe();
  }
  updated_entry.set_base_name(new_base_name);

  // Add the entry to resource map.
  return storage_->PutEntry(updated_entry);
}

bool ResourceMetadata::RemoveEntryRecursively(const std::string& id) {
  DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread());

  ResourceEntry entry;
  if (!storage_->GetEntry(id, &entry))
    return false;

  if (entry.file_info().is_directory()) {
    std::vector<std::string> children;
    storage_->GetChildren(id, &children);
    for (size_t i = 0; i < children.size(); ++i) {
      if (!RemoveEntryRecursively(children[i]))
        return false;
    }
  }
  return storage_->RemoveEntry(id);
}

}  // namespace internal
}  // namespace drive