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