Home | History | Annotate | Download | only in common
      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/common/chrome_paths_internal.h"
      6 
      7 #import <Foundation/Foundation.h>
      8 #include <string.h>
      9 
     10 #include <string>
     11 
     12 #include "base/base_paths.h"
     13 #include "base/logging.h"
     14 #import "base/mac/foundation_util.h"
     15 #import "base/mac/scoped_nsautorelease_pool.h"
     16 #include "base/memory/scoped_ptr.h"
     17 #include "base/path_service.h"
     18 #include "chrome/common/chrome_constants.h"
     19 
     20 #if !defined(OS_IOS)
     21 #import "base/mac/mac_util.h"
     22 #endif
     23 
     24 namespace {
     25 
     26 #if !defined(OS_IOS)
     27 const base::FilePath* g_override_versioned_directory = NULL;
     28 
     29 // Return a retained (NOT autoreleased) NSBundle* as the internal
     30 // implementation of chrome::OuterAppBundle(), which should be the only
     31 // caller.
     32 NSBundle* OuterAppBundleInternal() {
     33   base::mac::ScopedNSAutoreleasePool pool;
     34 
     35   if (!base::mac::AmIBundled()) {
     36     // If unbundled (as in a test), there's no app bundle.
     37     return nil;
     38   }
     39 
     40   if (!base::mac::IsBackgroundOnlyProcess()) {
     41     // Shortcut: in the browser process, just return the main app bundle.
     42     return [[NSBundle mainBundle] retain];
     43   }
     44 
     45   // From C.app/Contents/Versions/1.2.3.4, go up three steps to get to C.app.
     46   base::FilePath versioned_dir = chrome::GetVersionedDirectory();
     47   base::FilePath outer_app_dir = versioned_dir.DirName().DirName().DirName();
     48   const char* outer_app_dir_c = outer_app_dir.value().c_str();
     49   NSString* outer_app_dir_ns = [NSString stringWithUTF8String:outer_app_dir_c];
     50 
     51   return [[NSBundle bundleWithPath:outer_app_dir_ns] retain];
     52 }
     53 #endif  // !defined(OS_IOS)
     54 
     55 char* ProductDirNameForBundle(NSBundle* chrome_bundle) {
     56   const char* product_dir_name = NULL;
     57 #if !defined(OS_IOS)
     58   base::mac::ScopedNSAutoreleasePool pool;
     59 
     60   NSString* product_dir_name_ns =
     61       [chrome_bundle objectForInfoDictionaryKey:@"CrProductDirName"];
     62   product_dir_name = [product_dir_name_ns fileSystemRepresentation];
     63 #else
     64   DCHECK(!chrome_bundle);
     65 #endif
     66 
     67   if (!product_dir_name) {
     68 #if defined(GOOGLE_CHROME_BUILD)
     69     product_dir_name = "Google/Chrome";
     70 #else
     71     product_dir_name = "Chromium";
     72 #endif
     73   }
     74 
     75   // Leaked, but the only caller initializes a static with this result, so it
     76   // only happens once, and that's OK.
     77   return strdup(product_dir_name);
     78 }
     79 
     80 // ProductDirName returns the name of the directory inside
     81 // ~/Library/Application Support that should hold the product application
     82 // data. This can be overridden by setting the CrProductDirName key in the
     83 // outer browser .app's Info.plist. The default is "Google/Chrome" for
     84 // officially-branded builds, and "Chromium" for unbranded builds. For the
     85 // official canary channel, the Info.plist will have CrProductDirName set
     86 // to "Google/Chrome Canary".
     87 std::string ProductDirName() {
     88 #if defined(OS_IOS)
     89   static const char* product_dir_name = ProductDirNameForBundle(nil);
     90 #else
     91   // Use OuterAppBundle() to get the main app's bundle. This key needs to live
     92   // in the main app's bundle because it will be set differently on the canary
     93   // channel, and the autoupdate system dictates that there can be no
     94   // differences between channels within the versioned directory. This would
     95   // normally use base::mac::FrameworkBundle(), but that references the
     96   // framework bundle within the versioned directory. Ordinarily, the profile
     97   // should not be accessed from non-browser processes, but those processes do
     98   // attempt to get the profile directory, so direct them to look in the outer
     99   // browser .app's Info.plist for the CrProductDirName key.
    100   static const char* product_dir_name =
    101       ProductDirNameForBundle(chrome::OuterAppBundle());
    102 #endif
    103   return std::string(product_dir_name);
    104 }
    105 
    106 bool GetDefaultUserDataDirectoryForProduct(const std::string& product_dir,
    107                                            base::FilePath* result) {
    108   bool success = false;
    109   if (result && PathService::Get(base::DIR_APP_DATA, result)) {
    110     *result = result->Append(product_dir);
    111     success = true;
    112   }
    113   return success;
    114 }
    115 
    116 }  // namespace
    117 
    118 namespace chrome {
    119 
    120 bool GetDefaultUserDataDirectory(base::FilePath* result) {
    121   return GetDefaultUserDataDirectoryForProduct(ProductDirName(), result);
    122 }
    123 
    124 bool GetUserDocumentsDirectory(base::FilePath* result) {
    125   return base::mac::GetUserDirectory(NSDocumentDirectory, result);
    126 }
    127 
    128 void GetUserCacheDirectory(const base::FilePath& profile_dir,
    129                            base::FilePath* result) {
    130   // If the profile directory is under ~/Library/Application Support,
    131   // use a suitable cache directory under ~/Library/Caches.  For
    132   // example, a profile directory of ~/Library/Application
    133   // Support/Google/Chrome/MyProfileName would use the cache directory
    134   // ~/Library/Caches/Google/Chrome/MyProfileName.
    135 
    136   // Default value in cases where any of the following fails.
    137   *result = profile_dir;
    138 
    139   base::FilePath app_data_dir;
    140   if (!PathService::Get(base::DIR_APP_DATA, &app_data_dir))
    141     return;
    142   base::FilePath cache_dir;
    143   if (!PathService::Get(base::DIR_CACHE, &cache_dir))
    144     return;
    145   if (!app_data_dir.AppendRelativePath(profile_dir, &cache_dir))
    146     return;
    147 
    148   *result = cache_dir;
    149 }
    150 
    151 bool GetUserDownloadsDirectory(base::FilePath* result) {
    152   return base::mac::GetUserDirectory(NSDownloadsDirectory, result);
    153 }
    154 
    155 bool GetUserMusicDirectory(base::FilePath* result) {
    156   return base::mac::GetUserDirectory(NSMusicDirectory, result);
    157 }
    158 
    159 bool GetUserPicturesDirectory(base::FilePath* result) {
    160   return base::mac::GetUserDirectory(NSPicturesDirectory, result);
    161 }
    162 
    163 bool GetUserVideosDirectory(base::FilePath* result) {
    164   return base::mac::GetUserDirectory(NSMoviesDirectory, result);
    165 }
    166 
    167 #if !defined(OS_IOS)
    168 
    169 base::FilePath GetVersionedDirectory() {
    170   if (g_override_versioned_directory)
    171     return *g_override_versioned_directory;
    172 
    173   // Start out with the path to the running executable.
    174   base::FilePath path;
    175   PathService::Get(base::FILE_EXE, &path);
    176 
    177   // One step up to MacOS, another to Contents.
    178   path = path.DirName().DirName();
    179   DCHECK_EQ(path.BaseName().value(), "Contents");
    180 
    181   if (base::mac::IsBackgroundOnlyProcess()) {
    182     // path identifies the helper .app's Contents directory in the browser
    183     // .app's versioned directory.  Go up two steps to get to the browser
    184     // .app's versioned directory.
    185     path = path.DirName().DirName();
    186     DCHECK_EQ(path.BaseName().value(), kChromeVersion);
    187   } else {
    188     // Go into the versioned directory.
    189     path = path.Append("Versions").Append(kChromeVersion);
    190   }
    191 
    192   return path;
    193 }
    194 
    195 void SetOverrideVersionedDirectory(const base::FilePath* path) {
    196   if (path != g_override_versioned_directory) {
    197     delete g_override_versioned_directory;
    198     g_override_versioned_directory = path;
    199   }
    200 }
    201 
    202 base::FilePath GetFrameworkBundlePath() {
    203   // It's tempting to use +[NSBundle bundleWithIdentifier:], but it's really
    204   // slow (about 30ms on 10.5 and 10.6), despite Apple's documentation stating
    205   // that it may be more efficient than +bundleForClass:.  +bundleForClass:
    206   // itself takes 1-2ms.  Getting an NSBundle from a path, on the other hand,
    207   // essentially takes no time at all, at least when the bundle has already
    208   // been loaded as it will have been in this case.  The FilePath operations
    209   // needed to compute the framework's path are also effectively free, so that
    210   // is the approach that is used here.  NSBundle is also documented as being
    211   // not thread-safe, and thread safety may be a concern here.
    212 
    213   // The framework bundle is at a known path and name from the browser .app's
    214   // versioned directory.
    215   return GetVersionedDirectory().Append(kFrameworkName);
    216 }
    217 
    218 bool GetLocalLibraryDirectory(base::FilePath* result) {
    219   return base::mac::GetLocalDirectory(NSLibraryDirectory, result);
    220 }
    221 
    222 bool GetGlobalApplicationSupportDirectory(base::FilePath* result) {
    223   return base::mac::GetLocalDirectory(NSApplicationSupportDirectory, result);
    224 }
    225 
    226 NSBundle* OuterAppBundle() {
    227   // Cache this. Foundation leaks it anyway, and this should be the only call
    228   // to OuterAppBundleInternal().
    229   static NSBundle* bundle = OuterAppBundleInternal();
    230   return bundle;
    231 }
    232 
    233 bool GetUserDataDirectoryForBrowserBundle(NSBundle* bundle,
    234                                           base::FilePath* result) {
    235   scoped_ptr_malloc<char> product_dir_name(ProductDirNameForBundle(bundle));
    236   return GetDefaultUserDataDirectoryForProduct(product_dir_name.get(), result);
    237 }
    238 
    239 #endif  // !defined(OS_IOS)
    240 
    241 bool ProcessNeedsProfileDir(const std::string& process_type) {
    242   // For now we have no reason to forbid this on other MacOS as we don't
    243   // have the roaming profile troubles there.
    244   return true;
    245 }
    246 
    247 }  // namespace chrome
    248