blob: c1838834f9f3b021a9a89b7bbac8e9c949bde255 [file] [log] [blame]
// 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/gdata/drive_files.h"
#include "base/platform_file.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/chromeos/gdata/drive.pb.h"
#include "chrome/browser/chromeos/gdata/drive_file_system_util.h"
#include "chrome/browser/chromeos/gdata/drive_resource_metadata.h"
#include "chrome/browser/google_apis/gdata_wapi_parser.h"
#include "net/base/escape.h"
namespace gdata {
// DriveEntry class.
DriveEntry::DriveEntry(DriveResourceMetadata* resource_metadata)
: parent_(NULL),
resource_metadata_(resource_metadata),
deleted_(false) {
DCHECK(resource_metadata);
}
DriveEntry::~DriveEntry() {
}
DriveFile* DriveEntry::AsDriveFile() {
return NULL;
}
DriveDirectory* DriveEntry::AsDriveDirectory() {
return NULL;
}
void DriveEntry::InitFromDocumentEntry(const DocumentEntry& doc) {
// For regular files, the 'filename' and 'title' attribute in the metadata
// may be different (e.g. due to rename). To be consistent with the web
// interface and other client to use the 'title' attribute, instead of
// 'filename', as the file name in the local snapshot.
title_ = UTF16ToUTF8(doc.title());
// SetBaseNameFromTitle() must be called after |title_| is set.
SetBaseNameFromTitle();
// TODO(satorux): The last modified and the last accessed time shouldn't
// be treated as the same thing: crbug.com/148434
file_info_.last_modified = doc.updated_time();
file_info_.last_accessed = doc.updated_time();
file_info_.creation_time = doc.published_time();
resource_id_ = doc.resource_id();
content_url_ = doc.content_url();
deleted_ = doc.deleted();
const Link* edit_link = doc.GetLinkByType(Link::LINK_EDIT);
if (edit_link)
edit_url_ = edit_link->href();
const Link* parent_link = doc.GetLinkByType(Link::LINK_PARENT);
if (parent_link)
parent_resource_id_ = util::ExtractResourceIdFromUrl(parent_link->href());
}
const DriveFile* DriveEntry::AsDriveFileConst() const {
// cast away const and call the non-const version. This is safe.
return const_cast<DriveEntry*>(this)->AsDriveFile();
}
const DriveDirectory* DriveEntry::AsDriveDirectoryConst() const {
// cast away const and call the non-const version. This is safe.
return const_cast<DriveEntry*>(this)->AsDriveDirectory();
}
FilePath DriveEntry::GetFilePath() const {
FilePath path;
if (parent())
path = parent()->GetFilePath();
path = path.Append(base_name());
return path;
}
void DriveEntry::SetParent(DriveDirectory* parent) {
parent_ = parent;
parent_resource_id_ = parent ? parent->resource_id() : "";
}
void DriveEntry::SetBaseNameFromTitle() {
base_name_ = util::EscapeUtf8FileName(title_);
}
// DriveFile class implementation.
DriveFile::DriveFile(DriveResourceMetadata* resource_metadata)
: DriveEntry(resource_metadata),
kind_(ENTRY_KIND_UNKNOWN),
is_hosted_document_(false) {
file_info_.is_directory = false;
}
DriveFile::~DriveFile() {
}
DriveFile* DriveFile::AsDriveFile() {
return this;
}
void DriveFile::SetBaseNameFromTitle() {
if (is_hosted_document_) {
base_name_ = util::EscapeUtf8FileName(title_ + document_extension_);
} else {
DriveEntry::SetBaseNameFromTitle();
}
}
void DriveFile::InitFromDocumentEntry(const DocumentEntry& doc) {
DriveEntry::InitFromDocumentEntry(doc);
// Check if this entry is a true file, or...
if (doc.is_file()) {
file_info_.size = doc.file_size();
file_md5_ = doc.file_md5();
// The resumable-edit-media link should only be present for regular
// files as hosted documents are not uploadable.
const Link* upload_link =
doc.GetLinkByType(Link::LINK_RESUMABLE_EDIT_MEDIA);
if (upload_link)
upload_url_ = upload_link->href();
} else {
// ... a hosted document.
// Attach .g<something> extension to hosted documents so we can special
// case their handling in UI.
// TODO(zelidrag): Figure out better way how to pass entry info like kind
// to UI through the File API stack.
document_extension_ = doc.GetHostedDocumentExtension();
// We don't know the size of hosted docs and it does not matter since
// is has no effect on the quota.
file_info_.size = 0;
}
kind_ = doc.kind();
content_mime_type_ = doc.content_mime_type();
is_hosted_document_ = doc.is_hosted_document();
// SetBaseNameFromTitle() must be called after |title_|,
// |is_hosted_document_| and |document_extension_| are set.
SetBaseNameFromTitle();
const Link* thumbnail_link = doc.GetLinkByType(Link::LINK_THUMBNAIL);
if (thumbnail_link)
thumbnail_url_ = thumbnail_link->href();
const Link* alternate_link = doc.GetLinkByType(Link::LINK_ALTERNATE);
if (alternate_link)
alternate_url_ = alternate_link->href();
}
// DriveDirectory class implementation.
DriveDirectory::DriveDirectory(DriveResourceMetadata* resource_metadata)
: DriveEntry(resource_metadata) {
file_info_.is_directory = true;
}
DriveDirectory::~DriveDirectory() {
RemoveChildren();
}
DriveDirectory* DriveDirectory::AsDriveDirectory() {
return this;
}
void DriveDirectory::InitFromDocumentEntry(const DocumentEntry& doc) {
DriveEntry::InitFromDocumentEntry(doc);
const Link* upload_link =
doc.GetLinkByType(Link::LINK_RESUMABLE_CREATE_MEDIA);
if (upload_link)
upload_url_ = upload_link->href();
}
void DriveDirectory::AddEntry(DriveEntry* entry) {
DCHECK(!entry->parent());
// 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.
entry->SetBaseNameFromTitle();
// Do file name de-duplication - find files with the same name and
// append a name modifier to the name.
int max_modifier = 1;
FilePath full_file_name(entry->base_name());
const std::string extension = full_file_name.Extension();
const std::string file_name = full_file_name.RemoveExtension().value();
while (!FindChild(full_file_name.value()).empty()) {
if (!extension.empty()) {
full_file_name = FilePath(base::StringPrintf("%s (%d)%s",
file_name.c_str(),
++max_modifier,
extension.c_str()));
} else {
full_file_name = FilePath(base::StringPrintf("%s (%d)",
file_name.c_str(),
++max_modifier));
}
}
entry->set_base_name(full_file_name.value());
DVLOG(1) << "AddEntry: dir = " << GetFilePath().value()
<< ", file = " + entry->base_name()
<< ", parent resource = " << entry->parent_resource_id()
<< ", resource = " + entry->resource_id();
// Add entry to resource map.
resource_metadata_->AddEntryToResourceMap(entry);
// Setup child and parent links.
if (entry->AsDriveFile())
child_files_.insert(std::make_pair(entry->base_name(),
entry->resource_id()));
if (entry->AsDriveDirectory())
child_directories_.insert(std::make_pair(entry->base_name(),
entry->resource_id()));
entry->SetParent(this);
}
void DriveDirectory::TakeOverEntries(DriveDirectory* dir) {
for (ChildMap::const_iterator iter = dir->child_files_.begin();
iter != dir->child_files_.end(); ++iter) {
TakeOverEntry(iter->second);
}
dir->child_files_.clear();
for (ChildMap::iterator iter = dir->child_directories_.begin();
iter != dir->child_directories_.end(); ++iter) {
TakeOverEntry(iter->second);
}
dir->child_directories_.clear();
}
void DriveDirectory::TakeOverEntry(const std::string& resource_id) {
DriveEntry* entry = resource_metadata_->GetEntryByResourceId(resource_id);
DCHECK(entry);
resource_metadata_->RemoveEntryFromResourceMap(resource_id);
entry->SetParent(NULL);
AddEntry(entry);
}
void DriveDirectory::RemoveEntry(DriveEntry* entry) {
DCHECK(entry);
RemoveChild(entry);
delete entry;
}
std::string DriveDirectory::FindChild(
const FilePath::StringType& file_name) const {
ChildMap::const_iterator iter = child_files_.find(file_name);
if (iter != child_files_.end())
return iter->second;
iter = child_directories_.find(file_name);
if (iter != child_directories_.end())
return iter->second;
return std::string();
}
void DriveDirectory::RemoveChild(DriveEntry* entry) {
DCHECK(entry);
const std::string& base_name(entry->base_name());
// entry must be present in this directory.
DCHECK_EQ(entry->resource_id(), FindChild(base_name));
// Remove entry from resource map first.
resource_metadata_->RemoveEntryFromResourceMap(entry->resource_id());
// Then delete it from tree.
child_files_.erase(base_name);
child_directories_.erase(base_name);
entry->SetParent(NULL);
}
void DriveDirectory::RemoveChildren() {
RemoveChildFiles();
RemoveChildDirectories();
}
void DriveDirectory::RemoveChildFiles() {
DVLOG(1) << "RemoveChildFiles " << resource_id();
for (ChildMap::const_iterator iter = child_files_.begin();
iter != child_files_.end(); ++iter) {
DriveEntry* child = resource_metadata_->GetEntryByResourceId(iter->second);
DCHECK(child);
resource_metadata_->RemoveEntryFromResourceMap(iter->second);
delete child;
}
child_files_.clear();
}
void DriveDirectory::RemoveChildDirectories() {
for (ChildMap::iterator iter = child_directories_.begin();
iter != child_directories_.end(); ++iter) {
DriveDirectory* dir = resource_metadata_->GetEntryByResourceId(
iter->second)->AsDriveDirectory();
DCHECK(dir);
// Remove directories recursively.
dir->RemoveChildren();
resource_metadata_->RemoveEntryFromResourceMap(iter->second);
delete dir;
}
child_directories_.clear();
}
void DriveDirectory::GetChildDirectoryPaths(std::set<FilePath>* child_dirs) {
for (ChildMap::const_iterator iter = child_directories_.begin();
iter != child_directories_.end(); ++iter) {
DriveDirectory* dir = resource_metadata_->GetEntryByResourceId(
iter->second)->AsDriveDirectory();
DCHECK(dir);
child_dirs->insert(dir->GetFilePath());
dir->GetChildDirectoryPaths(child_dirs);
}
}
// Convert to/from proto.
// static
void DriveEntry::ConvertProtoToPlatformFileInfo(
const PlatformFileInfoProto& proto,
base::PlatformFileInfo* file_info) {
file_info->size = proto.size();
file_info->is_directory = proto.is_directory();
file_info->is_symbolic_link = proto.is_symbolic_link();
file_info->last_modified = base::Time::FromInternalValue(
proto.last_modified());
file_info->last_accessed = base::Time::FromInternalValue(
proto.last_accessed());
file_info->creation_time = base::Time::FromInternalValue(
proto.creation_time());
}
// static
void DriveEntry::ConvertPlatformFileInfoToProto(
const base::PlatformFileInfo& file_info,
PlatformFileInfoProto* proto) {
proto->set_size(file_info.size);
proto->set_is_directory(file_info.is_directory);
proto->set_is_symbolic_link(file_info.is_symbolic_link);
proto->set_last_modified(file_info.last_modified.ToInternalValue());
proto->set_last_accessed(file_info.last_accessed.ToInternalValue());
proto->set_creation_time(file_info.creation_time.ToInternalValue());
}
void DriveEntry::FromProto(const DriveEntryProto& proto) {
ConvertProtoToPlatformFileInfo(proto.file_info(), &file_info_);
// Don't copy from proto.base_name() as base_name_ is computed in
// SetBaseNameFromTitle().
title_ = proto.title();
resource_id_ = proto.resource_id();
parent_resource_id_ = proto.parent_resource_id();
edit_url_ = GURL(proto.edit_url());
content_url_ = GURL(proto.content_url());
upload_url_ = GURL(proto.upload_url());
SetBaseNameFromTitle();
}
void DriveEntry::ToProto(DriveEntryProto* proto) const {
ConvertPlatformFileInfoToProto(file_info_, proto->mutable_file_info());
// The base_name field is used in GetFileInfoByPathAsync(). As shown in
// FromProto(), the value is discarded when deserializing from proto.
proto->set_base_name(base_name_);
proto->set_title(title_);
proto->set_resource_id(resource_id_);
proto->set_parent_resource_id(parent_resource_id_);
proto->set_edit_url(edit_url_.spec());
proto->set_content_url(content_url_.spec());
proto->set_upload_url(upload_url_.spec());
}
void DriveEntry::ToProtoFull(DriveEntryProto* proto) const {
if (AsDriveFileConst()) {
AsDriveFileConst()->ToProto(proto);
} else if (AsDriveDirectoryConst()) {
// Unlike files, directories don't have directory specific info, so just
// calling DriveEntry::ToProto().
ToProto(proto);
} else {
NOTREACHED();
}
}
void DriveFile::FromProto(const DriveEntryProto& proto) {
DCHECK(!proto.file_info().is_directory());
DriveEntry::FromProto(proto);
thumbnail_url_ = GURL(proto.file_specific_info().thumbnail_url());
alternate_url_ = GURL(proto.file_specific_info().alternate_url());
content_mime_type_ = proto.file_specific_info().content_mime_type();
file_md5_ = proto.file_specific_info().file_md5();
document_extension_ = proto.file_specific_info().document_extension();
is_hosted_document_ = proto.file_specific_info().is_hosted_document();
}
void DriveFile::ToProto(DriveEntryProto* proto) const {
DriveEntry::ToProto(proto);
DCHECK(!proto->file_info().is_directory());
DriveFileSpecificInfo* file_specific_info =
proto->mutable_file_specific_info();
file_specific_info->set_thumbnail_url(thumbnail_url_.spec());
file_specific_info->set_alternate_url(alternate_url_.spec());
file_specific_info->set_content_mime_type(content_mime_type_);
file_specific_info->set_file_md5(file_md5_);
file_specific_info->set_document_extension(document_extension_);
file_specific_info->set_is_hosted_document(is_hosted_document_);
}
void DriveDirectory::FromProto(const DriveDirectoryProto& proto) {
DCHECK(proto.drive_entry().file_info().is_directory());
DCHECK(!proto.drive_entry().has_file_specific_info());
for (int i = 0; i < proto.child_files_size(); ++i) {
scoped_ptr<DriveFile> file(resource_metadata_->CreateDriveFile());
file->FromProto(proto.child_files(i));
AddEntry(file.release());
}
for (int i = 0; i < proto.child_directories_size(); ++i) {
scoped_ptr<DriveDirectory> dir(resource_metadata_->CreateDriveDirectory());
dir->FromProto(proto.child_directories(i));
AddEntry(dir.release());
}
// The states of the directory should be updated after children are
// handled successfully, so that incomplete states are not left.
DriveEntry::FromProto(proto.drive_entry());
}
void DriveDirectory::ToProto(DriveDirectoryProto* proto) const {
DriveEntry::ToProto(proto->mutable_drive_entry());
DCHECK(proto->drive_entry().file_info().is_directory());
for (ChildMap::const_iterator iter = child_files_.begin();
iter != child_files_.end(); ++iter) {
DriveFile* file = resource_metadata_->GetEntryByResourceId(
iter->second)->AsDriveFile();
DCHECK(file);
file->ToProto(proto->add_child_files());
}
for (ChildMap::const_iterator iter = child_directories_.begin();
iter != child_directories_.end(); ++iter) {
DriveDirectory* dir = resource_metadata_->GetEntryByResourceId(
iter->second)->AsDriveDirectory();
DCHECK(dir);
dir->ToProto(proto->add_child_directories());
}
}
scoped_ptr<DriveEntryProtoVector> DriveDirectory::ToProtoVector() const {
scoped_ptr<DriveEntryProtoVector> entries(new DriveEntryProtoVector);
// Use ToProtoFull, as we don't want to include children in |proto|.
for (ChildMap::const_iterator iter = child_files_.begin();
iter != child_files_.end(); ++iter) {
DriveEntryProto proto;
resource_metadata_->GetEntryByResourceId(iter->second)->ToProtoFull(&proto);
entries->push_back(proto);
}
for (ChildMap::const_iterator iter = child_directories_.begin();
iter != child_directories_.end(); ++iter) {
DriveEntryProto proto;
resource_metadata_->GetEntryByResourceId(iter->second)->ToProtoFull(&proto);
entries->push_back(proto);
}
return entries.Pass();
}
void DriveEntry::SerializeToString(std::string* serialized_proto) const {
const DriveFile* file = AsDriveFileConst();
const DriveDirectory* dir = AsDriveDirectoryConst();
if (file) {
DriveEntryProto entry_proto;
file->ToProto(&entry_proto);
const bool ok = entry_proto.SerializeToString(serialized_proto);
DCHECK(ok);
} else if (dir) {
DriveDirectoryProto dir_proto;
dir->ToProto(&dir_proto);
const bool ok = dir_proto.SerializeToString(serialized_proto);
DCHECK(ok);
}
}
} // namespace gdata