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