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.
5 #include "chrome/browser/chromeos/drive/file_system_util.h"
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"
43 using content::BrowserThread
;
50 const char kDriveMountPointPath
[] = "/special/drive";
52 const base::FilePath::CharType kDriveMyDriveMountPointPath
[] =
53 FILE_PATH_LITERAL("/special/drive/root");
55 const base::FilePath::CharType kDriveMyDriveRootPath
[] =
56 FILE_PATH_LITERAL("drive/root");
58 const base::FilePath::CharType kFileCacheVersionDir
[] =
59 FILE_PATH_LITERAL("v1");
61 const char kSlash
[] = "/";
62 const char kDot
= '.';
63 const char kEscapedChars
[] = "_";
65 const base::FilePath
& GetDriveMyDriveMountPointPath() {
66 CR_DEFINE_STATIC_LOCAL(base::FilePath
, drive_mydrive_mount_path
,
67 (kDriveMyDriveMountPointPath
));
68 return drive_mydrive_mount_path
;
71 std::string
ReadStringFromGDocFile(const base::FilePath
& file_path
,
72 const std::string
& key
) {
73 const int64 kMaxGDocSize
= 4096;
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();
81 JSONFileValueSerializer
reader(file_path
);
82 std::string error_message
;
83 scoped_ptr
<base::Value
> root_value(reader
.Deserialize(NULL
, &error_message
));
85 LOG(WARNING
) << "Failed to parse " << file_path
.value() << " as JSON."
86 << " error = " << error_message
;
90 base::DictionaryValue
* dictionary_value
= NULL
;
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
;
102 // Returns DriveIntegrationService instance, if Drive is enabled.
104 DriveIntegrationService
* GetIntegrationServiceByProfile(Profile
* profile
) {
105 DriveIntegrationService
* service
=
106 DriveIntegrationServiceFactory::FindForProfile(profile
);
107 if (!service
|| !service
->IsMounted())
112 void CheckDirectoryExistsAfterGetResourceEntry(
113 const FileOperationCallback
& callback
,
115 scoped_ptr
<ResourceEntry
> entry
) {
116 if (error
== FILE_ERROR_OK
&& !entry
->file_info().is_directory())
117 error
= FILE_ERROR_NOT_A_DIRECTORY
;
123 const base::FilePath
& GetDriveGrandRootPath() {
124 CR_DEFINE_STATIC_LOCAL(base::FilePath
, grand_root_path
,
125 (util::kDriveGrandRootDirName
));
126 return grand_root_path
;
129 const base::FilePath
& GetDriveMyDriveRootPath() {
130 CR_DEFINE_STATIC_LOCAL(base::FilePath
, drive_root_path
,
131 (util::kDriveMyDriveRootPath
));
132 return drive_root_path
;
135 const base::FilePath
& GetDriveMountPointPath() {
136 CR_DEFINE_STATIC_LOCAL(base::FilePath
, drive_mount_path
,
137 (base::FilePath::FromUTF8Unsafe(kDriveMountPointPath
)));
138 return drive_mount_path
;
141 FileSystemInterface
* GetFileSystemByProfile(Profile
* profile
) {
142 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
144 DriveIntegrationService
* integration_service
=
145 GetIntegrationServiceByProfile(profile
);
146 return integration_service
? integration_service
->file_system() : NULL
;
149 FileSystemInterface
* GetFileSystemByProfileId(void* profile_id
) {
150 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
152 // |profile_id| needs to be checked with ProfileManager::IsValidProfile
154 Profile
* profile
= reinterpret_cast<Profile
*>(profile_id
);
155 if (!g_browser_process
->profile_manager()->IsValidProfile(profile
))
157 return GetFileSystemByProfile(profile
);
160 DriveAppRegistry
* GetDriveAppRegistryByProfile(Profile
* profile
) {
161 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
163 DriveIntegrationService
* integration_service
=
164 GetIntegrationServiceByProfile(profile
);
165 return integration_service
?
166 integration_service
->drive_app_registry() :
170 DriveServiceInterface
* GetDriveServiceByProfile(Profile
* profile
) {
171 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
173 DriveIntegrationService
* integration_service
=
174 GetIntegrationServiceByProfile(profile
);
175 return integration_service
? integration_service
->drive_service() : NULL
;
178 const std::string
& GetDriveMountPointPathAsString() {
179 CR_DEFINE_STATIC_LOCAL(std::string
, drive_mount_path_string
,
180 (kDriveMountPointPath
));
181 return drive_mount_path_string
;
184 GURL
FilePathToDriveURL(const base::FilePath
& path
) {
185 std::string
url(base::StringPrintf("%s:%s",
186 chrome::kDriveScheme
,
187 path
.AsUTF8Unsafe().c_str()));
191 base::FilePath
DriveURLToFilePath(const GURL
& url
) {
192 if (!url
.is_valid() || url
.scheme() != chrome::kDriveScheme
)
193 return base::FilePath();
194 std::string path_string
= net::UnescapeURLComponent(
195 url
.GetContent(), net::UnescapeRule::NORMAL
);
196 return base::FilePath::FromUTF8Unsafe(path_string
);
199 void MaybeSetDriveURL(Profile
* profile
, const base::FilePath
& path
, GURL
* url
) {
200 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
202 if (!IsUnderDriveMountPoint(path
))
205 FileSystemInterface
* file_system
= GetFileSystemByProfile(profile
);
209 *url
= FilePathToDriveURL(util::ExtractDrivePath(path
));
212 bool IsUnderDriveMountPoint(const base::FilePath
& path
) {
213 return GetDriveMountPointPath() == path
||
214 GetDriveMountPointPath().IsParent(path
);
217 bool NeedsNamespaceMigration(const base::FilePath
& path
) {
218 // Before migration, "My Drive" which was represented as "drive.
219 // The user might use some path pointing a directory in "My Drive".
220 // e.g. "drive/downloads_dir"
221 // We changed the path for the "My Drive" to "drive/root", hence the user pref
222 // pointing to the old path needs update to the new path.
223 // e.g. "drive/root/downloads_dir"
224 // If |path| already points to some directory in "drive/root", there's no need
226 return IsUnderDriveMountPoint(path
) &&
227 !(GetDriveMyDriveMountPointPath() == path
||
228 GetDriveMyDriveMountPointPath().IsParent(path
));
231 base::FilePath
ConvertToMyDriveNamespace(const base::FilePath
& path
) {
232 DCHECK(NeedsNamespaceMigration(path
));
234 // Need to migrate "/special/drive(.*)" to "/special/drive/root(.*)".
235 // Append the relative path from "/special/drive".
236 base::FilePath
new_path(GetDriveMyDriveMountPointPath());
237 GetDriveMountPointPath().AppendRelativePath(path
, &new_path
);
238 DVLOG(1) << "Migrate download.default_directory setting from "
239 << path
.AsUTF8Unsafe() << " to " << new_path
.AsUTF8Unsafe();
240 DCHECK(!NeedsNamespaceMigration(new_path
));
244 base::FilePath
ExtractDrivePath(const base::FilePath
& path
) {
245 if (!IsUnderDriveMountPoint(path
))
246 return base::FilePath();
248 base::FilePath drive_path
= GetDriveGrandRootPath();
249 GetDriveMountPointPath().AppendRelativePath(path
, &drive_path
);
253 base::FilePath
ExtractDrivePathFromFileSystemUrl(
254 const fileapi::FileSystemURL
& url
) {
255 if (!url
.is_valid() || url
.type() != fileapi::kFileSystemTypeDrive
)
256 return base::FilePath();
257 return ExtractDrivePath(url
.path());
260 base::FilePath
GetCacheRootPath(Profile
* profile
) {
261 base::FilePath cache_base_path
;
262 chrome::GetUserCacheDirectory(profile
->GetPath(), &cache_base_path
);
263 base::FilePath cache_root_path
=
264 cache_base_path
.Append(chromeos::kDriveCacheDirname
);
265 return cache_root_path
.Append(kFileCacheVersionDir
);
268 std::string
EscapeCacheFileName(const std::string
& filename
) {
269 // This is based on net/base/escape.cc: net::(anonymous namespace)::Escape
271 for (size_t i
= 0; i
< filename
.size(); ++i
) {
272 char c
= filename
[i
];
273 if (c
== '%' || c
== '.' || c
== '/') {
274 base::StringAppendF(&escaped
, "%%%02X", c
);
276 escaped
.push_back(c
);
282 std::string
UnescapeCacheFileName(const std::string
& filename
) {
283 std::string unescaped
;
284 for (size_t i
= 0; i
< filename
.size(); ++i
) {
285 char c
= filename
[i
];
286 if (c
== '%' && i
+ 2 < filename
.length()) {
287 c
= (HexDigitToInt(filename
[i
+ 1]) << 4) +
288 HexDigitToInt(filename
[i
+ 2]);
291 unescaped
.push_back(c
);
296 std::string
NormalizeFileName(const std::string
& input
) {
297 DCHECK(IsStringUTF8(input
));
300 if (!base::ConvertToUtf8AndNormalize(input
, base::kCodepageUTF8
, &output
))
302 base::ReplaceChars(output
, kSlash
, std::string(kEscapedChars
), &output
);
303 if (!output
.empty() && output
.find_first_not_of(kDot
, 0) == std::string::npos
)
304 output
= kEscapedChars
;
308 void PrepareWritableFileAndRun(Profile
* profile
,
309 const base::FilePath
& path
,
310 const PrepareWritableFileCallback
& callback
) {
311 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
312 DCHECK(!callback
.is_null());
314 FileSystemInterface
* file_system
= GetFileSystemByProfile(profile
);
315 if (!file_system
|| !IsUnderDriveMountPoint(path
)) {
316 content::BrowserThread::GetBlockingPool()->PostTask(
317 FROM_HERE
, base::Bind(callback
, FILE_ERROR_FAILED
, base::FilePath()));
321 WriteOnCacheFile(file_system
,
322 ExtractDrivePath(path
),
323 std::string(), // mime_type
327 void EnsureDirectoryExists(Profile
* profile
,
328 const base::FilePath
& directory
,
329 const FileOperationCallback
& callback
) {
330 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
331 DCHECK(!callback
.is_null());
332 if (IsUnderDriveMountPoint(directory
)) {
333 FileSystemInterface
* file_system
= GetFileSystemByProfile(profile
);
335 file_system
->CreateDirectory(
336 ExtractDrivePath(directory
),
337 true /* is_exclusive */,
338 true /* is_recursive */,
341 base::MessageLoopProxy::current()->PostTask(
342 FROM_HERE
, base::Bind(callback
, FILE_ERROR_OK
));
346 void CheckDirectoryExists(Profile
* profile
,
347 const base::FilePath
& directory
,
348 const FileOperationCallback
& callback
) {
349 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
350 DCHECK(!callback
.is_null());
352 FileSystemInterface
* file_system
= GetFileSystemByProfile(profile
);
355 file_system
->GetResourceEntry(
356 ExtractDrivePath(directory
),
357 base::Bind(&CheckDirectoryExistsAfterGetResourceEntry
, callback
));
360 void EmptyFileOperationCallback(FileError error
) {
363 bool CreateGDocFile(const base::FilePath
& file_path
,
365 const std::string
& resource_id
) {
366 std::string content
= base::StringPrintf(
367 "{\"url\": \"%s\", \"resource_id\": \"%s\"}",
368 url
.spec().c_str(), resource_id
.c_str());
369 return file_util::WriteFile(file_path
, content
.data(), content
.size()) ==
370 static_cast<int>(content
.size());
373 bool HasGDocFileExtension(const base::FilePath
& file_path
) {
374 return google_apis::ResourceEntry::ClassifyEntryKindByFileExtension(
376 google_apis::ResourceEntry::KIND_OF_HOSTED_DOCUMENT
;
379 GURL
ReadUrlFromGDocFile(const base::FilePath
& file_path
) {
380 return GURL(ReadStringFromGDocFile(file_path
, "url"));
383 std::string
ReadResourceIdFromGDocFile(const base::FilePath
& file_path
) {
384 return ReadStringFromGDocFile(file_path
, "resource_id");
387 bool IsDriveEnabledForProfile(Profile
* profile
) {
388 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
390 if (!chromeos::IsProfileAssociatedWithGaiaAccount(profile
))
393 // Disable Drive if preference is set. This can happen with commandline flag
394 // --disable-drive or enterprise policy, or with user settings.
395 if (profile
->GetPrefs()->GetBoolean(prefs::kDisableDrive
))
401 ConnectionStatusType
GetDriveConnectionStatus(Profile
* profile
) {
402 drive::DriveServiceInterface
* const drive_service
=
403 drive::util::GetDriveServiceByProfile(profile
);
406 return DRIVE_DISCONNECTED_NOSERVICE
;
407 if (net::NetworkChangeNotifier::IsOffline())
408 return DRIVE_DISCONNECTED_NONETWORK
;
409 if (!drive_service
->CanSendRequest())
410 return DRIVE_DISCONNECTED_NOTREADY
;
412 const bool is_connection_cellular
=
413 net::NetworkChangeNotifier::IsConnectionCellular(
414 net::NetworkChangeNotifier::GetConnectionType());
415 const bool disable_sync_over_celluar
=
416 profile
->GetPrefs()->GetBoolean(prefs::kDisableDriveOverCellular
);
418 if (is_connection_cellular
&& disable_sync_over_celluar
)
419 return DRIVE_CONNECTED_METERED
;
420 return DRIVE_CONNECTED
;