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