file_system_util.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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/file_system_util.h" 6 7#include <string> 8 9#include "base/basictypes.h" 10#include "base/bind.h" 11#include "base/bind_helpers.h" 12#include "base/file_util.h" 13#include "base/files/file_enumerator.h" 14#include "base/files/file_path.h" 15#include "base/i18n/icu_string_conversions.h" 16#include "base/json/json_file_value_serializer.h" 17#include "base/logging.h" 18#include "base/message_loop/message_loop_proxy.h" 19#include "base/strings/string_number_conversions.h" 20#include "base/strings/string_util.h" 21#include "base/strings/stringprintf.h" 22#include "base/threading/sequenced_worker_pool.h" 23#include "chrome/browser/chromeos/drive/drive.pb.h" 24#include "chrome/browser/chromeos/drive/drive_integration_service.h" 25#include "chrome/browser/chromeos/drive/file_system_interface.h" 26#include "chrome/browser/chromeos/drive/file_write_helper.h" 27#include "chrome/browser/chromeos/drive/job_list.h" 28#include "chrome/browser/google_apis/gdata_wapi_parser.h" 29#include "chrome/browser/profiles/profile.h" 30#include "chrome/common/chrome_constants.h" 31#include "chrome/common/chrome_paths_internal.h" 32#include "chrome/common/url_constants.h" 33#include "chromeos/chromeos_constants.h" 34#include "content/public/browser/browser_thread.h" 35#include "net/base/escape.h" 36#include "webkit/browser/fileapi/file_system_url.h" 37 38using content::BrowserThread; 39 40namespace drive { 41namespace util { 42 43namespace { 44 45const char kDriveMountPointPath[] = "/special/drive"; 46 47const base::FilePath::CharType kDriveMyDriveMountPointPath[] = 48 FILE_PATH_LITERAL("/special/drive/root"); 49 50const base::FilePath::CharType kDriveMyDriveRootPath[] = 51 FILE_PATH_LITERAL("drive/root"); 52 53const base::FilePath::CharType kFileCacheVersionDir[] = 54 FILE_PATH_LITERAL("v1"); 55 56const char kSlash[] = "/"; 57const char kEscapedSlash[] = "\xE2\x88\x95"; 58 59const base::FilePath& GetDriveMyDriveMountPointPath() { 60 CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_mydrive_mount_path, 61 (kDriveMyDriveMountPointPath)); 62 return drive_mydrive_mount_path; 63} 64 65FileSystemInterface* GetFileSystem(Profile* profile) { 66 DriveIntegrationService* integration_service = 67 DriveIntegrationServiceFactory::GetForProfile(profile); 68 return integration_service ? integration_service->file_system() : NULL; 69} 70 71FileWriteHelper* GetFileWriteHelper(Profile* profile) { 72 DriveIntegrationService* integration_service = 73 DriveIntegrationServiceFactory::GetForProfile(profile); 74 return integration_service ? integration_service->file_write_helper() : NULL; 75} 76 77std::string ReadStringFromGDocFile(const base::FilePath& file_path, 78 const std::string& key) { 79 const int64 kMaxGDocSize = 4096; 80 int64 file_size = 0; 81 if (!file_util::GetFileSize(file_path, &file_size) || 82 file_size > kMaxGDocSize) { 83 DLOG(INFO) << "File too large to be a GDoc file " << file_path.value(); 84 return std::string(); 85 } 86 87 JSONFileValueSerializer reader(file_path); 88 std::string error_message; 89 scoped_ptr<base::Value> root_value(reader.Deserialize(NULL, &error_message)); 90 if (!root_value) { 91 DLOG(INFO) << "Failed to parse " << file_path.value() << " as JSON." 92 << " error = " << error_message; 93 return std::string(); 94 } 95 96 base::DictionaryValue* dictionary_value = NULL; 97 std::string result; 98 if (!root_value->GetAsDictionary(&dictionary_value) || 99 !dictionary_value->GetString(key, &result)) { 100 DLOG(INFO) << "No value for the given key is stored in " 101 << file_path.value() << ". key = " << key; 102 return std::string(); 103 } 104 105 return result; 106} 107 108// Moves all files under |directory_from| to |directory_to|. 109void MoveAllFilesFromDirectory(const base::FilePath& directory_from, 110 const base::FilePath& directory_to) { 111 base::FileEnumerator enumerator(directory_from, false, // not recursive 112 base::FileEnumerator::FILES); 113 for (base::FilePath file_from = enumerator.Next(); !file_from.empty(); 114 file_from = enumerator.Next()) { 115 const base::FilePath file_to = directory_to.Append(file_from.BaseName()); 116 if (!file_util::PathExists(file_to)) // Do not overwrite existing files. 117 base::Move(file_from, file_to); 118 } 119} 120 121} // namespace 122 123const base::FilePath& GetDriveGrandRootPath() { 124 CR_DEFINE_STATIC_LOCAL(base::FilePath, grand_root_path, 125 (util::kDriveGrandRootDirName)); 126 return grand_root_path; 127} 128 129const base::FilePath& GetDriveMyDriveRootPath() { 130 CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_root_path, 131 (util::kDriveMyDriveRootPath)); 132 return drive_root_path; 133} 134 135const base::FilePath& GetDriveMountPointPath() { 136 CR_DEFINE_STATIC_LOCAL(base::FilePath, drive_mount_path, 137 (base::FilePath::FromUTF8Unsafe(kDriveMountPointPath))); 138 return drive_mount_path; 139} 140 141bool IsSpecialResourceId(const std::string& resource_id) { 142 return resource_id == kDriveGrandRootSpecialResourceId || 143 resource_id == kDriveOtherDirSpecialResourceId; 144} 145 146ResourceEntry CreateMyDriveRootEntry(const std::string& root_resource_id) { 147 ResourceEntry mydrive_root; 148 mydrive_root.mutable_file_info()->set_is_directory(true); 149 mydrive_root.set_resource_id(root_resource_id); 150 mydrive_root.set_parent_resource_id(util::kDriveGrandRootSpecialResourceId); 151 mydrive_root.set_title(util::kDriveMyDriveRootDirName); 152 return mydrive_root; 153} 154 155ResourceEntry CreateOtherDirEntry() { 156 ResourceEntry other_dir; 157 other_dir.mutable_file_info()->set_is_directory(true); 158 other_dir.set_resource_id(util::kDriveOtherDirSpecialResourceId); 159 other_dir.set_parent_resource_id(util::kDriveGrandRootSpecialResourceId); 160 other_dir.set_title(util::kDriveOtherDirName); 161 return other_dir; 162} 163 164const std::string& GetDriveMountPointPathAsString() { 165 CR_DEFINE_STATIC_LOCAL(std::string, drive_mount_path_string, 166 (kDriveMountPointPath)); 167 return drive_mount_path_string; 168} 169 170GURL FilePathToDriveURL(const base::FilePath& path) { 171 std::string url(base::StringPrintf("%s:%s", 172 chrome::kDriveScheme, 173 path.AsUTF8Unsafe().c_str())); 174 return GURL(url); 175} 176 177base::FilePath DriveURLToFilePath(const GURL& url) { 178 if (!url.is_valid() || url.scheme() != chrome::kDriveScheme) 179 return base::FilePath(); 180 std::string path_string = net::UnescapeURLComponent( 181 url.path(), net::UnescapeRule::NORMAL); 182 return base::FilePath::FromUTF8Unsafe(path_string); 183} 184 185void MaybeSetDriveURL(Profile* profile, const base::FilePath& path, GURL* url) { 186 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 187 188 if (!IsUnderDriveMountPoint(path)) 189 return; 190 191 FileSystemInterface* file_system = GetFileSystem(profile); 192 if (!file_system) 193 return; 194 195 *url = FilePathToDriveURL(util::ExtractDrivePath(path)); 196} 197 198bool IsUnderDriveMountPoint(const base::FilePath& path) { 199 return GetDriveMountPointPath() == path || 200 GetDriveMountPointPath().IsParent(path); 201} 202 203bool NeedsNamespaceMigration(const base::FilePath& path) { 204 // Before migration, "My Drive" which was represented as "drive. 205 // The user might use some path pointing a directory in "My Drive". 206 // e.g. "drive/downloads_dir" 207 // We changed the path for the "My Drive" to "drive/root", hence the user pref 208 // pointing to the old path needs update to the new path. 209 // e.g. "drive/root/downloads_dir" 210 // If |path| already points to some directory in "drive/root", there's no need 211 // to update it. 212 return IsUnderDriveMountPoint(path) && 213 !(GetDriveMyDriveMountPointPath() == path || 214 GetDriveMyDriveMountPointPath().IsParent(path)); 215} 216 217base::FilePath ConvertToMyDriveNamespace(const base::FilePath& path) { 218 DCHECK(NeedsNamespaceMigration(path)); 219 220 // Need to migrate "/special/drive(.*)" to "/special/drive/root(.*)". 221 // Append the relative path from "/special/drive". 222 base::FilePath new_path(GetDriveMyDriveMountPointPath()); 223 GetDriveMountPointPath().AppendRelativePath(path, &new_path); 224 DVLOG(1) << "Migrate download.default_directory setting from " 225 << path.AsUTF8Unsafe() << " to " << new_path.AsUTF8Unsafe(); 226 DCHECK(!NeedsNamespaceMigration(new_path)); 227 return new_path; 228} 229 230base::FilePath ExtractDrivePath(const base::FilePath& path) { 231 if (!IsUnderDriveMountPoint(path)) 232 return base::FilePath(); 233 234 base::FilePath drive_path = GetDriveGrandRootPath(); 235 GetDriveMountPointPath().AppendRelativePath(path, &drive_path); 236 return drive_path; 237} 238 239base::FilePath ExtractDrivePathFromFileSystemUrl( 240 const fileapi::FileSystemURL& url) { 241 if (!url.is_valid() || url.type() != fileapi::kFileSystemTypeDrive) 242 return base::FilePath(); 243 return ExtractDrivePath(url.path()); 244} 245 246base::FilePath GetCacheRootPath(Profile* profile) { 247 base::FilePath cache_base_path; 248 chrome::GetUserCacheDirectory(profile->GetPath(), &cache_base_path); 249 base::FilePath cache_root_path = 250 cache_base_path.Append(chromeos::kDriveCacheDirname); 251 return cache_root_path.Append(kFileCacheVersionDir); 252} 253 254std::string EscapeCacheFileName(const std::string& filename) { 255 // This is based on net/base/escape.cc: net::(anonymous namespace)::Escape 256 std::string escaped; 257 for (size_t i = 0; i < filename.size(); ++i) { 258 char c = filename[i]; 259 if (c == '%' || c == '.' || c == '/') { 260 base::StringAppendF(&escaped, "%%%02X", c); 261 } else { 262 escaped.push_back(c); 263 } 264 } 265 return escaped; 266} 267 268std::string UnescapeCacheFileName(const std::string& filename) { 269 std::string unescaped; 270 for (size_t i = 0; i < filename.size(); ++i) { 271 char c = filename[i]; 272 if (c == '%' && i + 2 < filename.length()) { 273 c = (HexDigitToInt(filename[i + 1]) << 4) + 274 HexDigitToInt(filename[i + 2]); 275 i += 2; 276 } 277 unescaped.push_back(c); 278 } 279 return unescaped; 280} 281 282std::string NormalizeFileName(const std::string& input) { 283 DCHECK(IsStringUTF8(input)); 284 285 std::string output; 286 if (!base::ConvertToUtf8AndNormalize(input, base::kCodepageUTF8, &output)) 287 output = input; 288 ReplaceChars(output, kSlash, std::string(kEscapedSlash), &output); 289 return output; 290} 291 292void ParseCacheFilePath(const base::FilePath& path, 293 std::string* resource_id, 294 std::string* md5) { 295 DCHECK(resource_id); 296 DCHECK(md5); 297 298 // Extract up to one extension from the right. 299 base::FilePath base_name = path.BaseName(); 300 base::FilePath::StringType extension = base_name.Extension(); 301 if (!extension.empty()) { 302 // base::FilePath::Extension returns ".", so strip it. 303 extension = UnescapeCacheFileName(extension.substr(1)); 304 base_name = base_name.RemoveExtension(); 305 } 306 307 // The base_name here is already stripped of extensions in the loop above. 308 *resource_id = UnescapeCacheFileName(base_name.value()); 309 *md5 = extension; 310} 311 312void MigrateCacheFilesFromOldDirectories( 313 const base::FilePath& cache_root_directory) { 314 const base::FilePath persistent_directory = 315 cache_root_directory.AppendASCII("persistent"); 316 const base::FilePath tmp_directory = 317 cache_root_directory.AppendASCII("tmp"); 318 if (!file_util::PathExists(persistent_directory)) 319 return; 320 321 const base::FilePath cache_file_directory = 322 cache_root_directory.Append(kCacheFileDirectory); 323 324 // Move all files inside "persistent" to "files". 325 MoveAllFilesFromDirectory(persistent_directory, cache_file_directory); 326 base::Delete(persistent_directory, true /* recursive */); 327 328 // Move all files inside "tmp" to "files". 329 MoveAllFilesFromDirectory(tmp_directory, cache_file_directory); 330} 331 332void PrepareWritableFileAndRun(Profile* profile, 333 const base::FilePath& path, 334 const OpenFileCallback& callback) { 335 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 336 DCHECK(!callback.is_null()); 337 if (IsUnderDriveMountPoint(path)) { 338 FileWriteHelper* file_write_helper = GetFileWriteHelper(profile); 339 if (!file_write_helper) 340 return; 341 base::FilePath remote_path(ExtractDrivePath(path)); 342 file_write_helper->PrepareWritableFileAndRun(remote_path, callback); 343 } else { 344 content::BrowserThread::GetBlockingPool()->PostTask( 345 FROM_HERE, base::Bind(callback, FILE_ERROR_OK, path)); 346 } 347} 348 349void EnsureDirectoryExists(Profile* profile, 350 const base::FilePath& directory, 351 const FileOperationCallback& callback) { 352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 353 DCHECK(!callback.is_null()); 354 if (IsUnderDriveMountPoint(directory)) { 355 FileSystemInterface* file_system = GetFileSystem(profile); 356 DCHECK(file_system); 357 file_system->CreateDirectory( 358 ExtractDrivePath(directory), 359 true /* is_exclusive */, 360 true /* is_recursive */, 361 callback); 362 } else { 363 base::MessageLoopProxy::current()->PostTask( 364 FROM_HERE, base::Bind(callback, FILE_ERROR_OK)); 365 } 366} 367 368FileError GDataToFileError(google_apis::GDataErrorCode status) { 369 switch (status) { 370 case google_apis::HTTP_SUCCESS: 371 case google_apis::HTTP_CREATED: 372 case google_apis::HTTP_NO_CONTENT: 373 return FILE_ERROR_OK; 374 case google_apis::HTTP_UNAUTHORIZED: 375 case google_apis::HTTP_FORBIDDEN: 376 return FILE_ERROR_ACCESS_DENIED; 377 case google_apis::HTTP_NOT_FOUND: 378 return FILE_ERROR_NOT_FOUND; 379 case google_apis::HTTP_NOT_IMPLEMENTED: 380 return FILE_ERROR_INVALID_OPERATION; 381 case google_apis::GDATA_CANCELLED: 382 return FILE_ERROR_ABORT; 383 case google_apis::GDATA_NO_CONNECTION: 384 return FILE_ERROR_NO_CONNECTION; 385 default: 386 return FILE_ERROR_FAILED; 387 } 388} 389 390void ConvertResourceEntryToPlatformFileInfo( 391 const PlatformFileInfoProto& entry, 392 base::PlatformFileInfo* file_info) { 393 file_info->size = entry.size(); 394 file_info->is_directory = entry.is_directory(); 395 file_info->is_symbolic_link = entry.is_symbolic_link(); 396 file_info->last_modified = base::Time::FromInternalValue( 397 entry.last_modified()); 398 file_info->last_accessed = base::Time::FromInternalValue( 399 entry.last_accessed()); 400 file_info->creation_time = base::Time::FromInternalValue( 401 entry.creation_time()); 402} 403 404void ConvertPlatformFileInfoToResourceEntry( 405 const base::PlatformFileInfo& file_info, 406 PlatformFileInfoProto* entry) { 407 entry->set_size(file_info.size); 408 entry->set_is_directory(file_info.is_directory); 409 entry->set_is_symbolic_link(file_info.is_symbolic_link); 410 entry->set_last_modified(file_info.last_modified.ToInternalValue()); 411 entry->set_last_accessed(file_info.last_accessed.ToInternalValue()); 412 entry->set_creation_time(file_info.creation_time.ToInternalValue()); 413} 414 415void EmptyFileOperationCallback(FileError error) { 416} 417 418bool CreateGDocFile(const base::FilePath& file_path, 419 const GURL& url, 420 const std::string& resource_id) { 421 std::string content = base::StringPrintf( 422 "{\"url\": \"%s\", \"resource_id\": \"%s\"}", 423 url.spec().c_str(), resource_id.c_str()); 424 return file_util::WriteFile(file_path, content.data(), content.size()) == 425 static_cast<int>(content.size()); 426} 427 428bool HasGDocFileExtension(const base::FilePath& file_path) { 429 return google_apis::ResourceEntry::ClassifyEntryKindByFileExtension( 430 file_path) & 431 google_apis::ResourceEntry::KIND_OF_HOSTED_DOCUMENT; 432} 433 434GURL ReadUrlFromGDocFile(const base::FilePath& file_path) { 435 return GURL(ReadStringFromGDocFile(file_path, "url")); 436} 437 438std::string ReadResourceIdFromGDocFile(const base::FilePath& file_path) { 439 return ReadStringFromGDocFile(file_path, "resource_id"); 440} 441 442} // namespace util 443} // namespace drive 444