create_file_operation.cc revision 424c4d7b64af9d0d8fd9624f381f469654d5e3d2
1// Copyright 2013 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/create_file_operation.h"
6
7#include <string>
8
9#include "base/file_util.h"
10#include "chrome/browser/chromeos/drive/drive.pb.h"
11#include "chrome/browser/chromeos/drive/file_cache.h"
12#include "chrome/browser/chromeos/drive/file_system/operation_observer.h"
13#include "chrome/browser/chromeos/drive/file_system_util.h"
14#include "chrome/browser/chromeos/drive/job_scheduler.h"
15#include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
16#include "chrome/browser/chromeos/drive/resource_metadata.h"
17#include "content/public/browser/browser_thread.h"
18#include "net/base/mime_util.h"
19
20using content::BrowserThread;
21
22namespace drive {
23namespace file_system {
24
25namespace {
26
27const char kMimeTypeOctetStream[] = "application/octet-stream";
28
29// Part of CreateFileOperation::CreateFile(), runs on |blocking_task_runner_|
30// of the operation, before server-side file creation.
31FileError CheckPreConditionForCreateFile(internal::ResourceMetadata* metadata,
32                                         const base::FilePath& file_path,
33                                         bool is_exclusive,
34                                         std::string* parent_resource_id,
35                                         std::string* mime_type) {
36  DCHECK(metadata);
37  DCHECK(parent_resource_id);
38  DCHECK(mime_type);
39
40  ResourceEntry entry;
41  FileError error = metadata->GetResourceEntryByPath(file_path, &entry);
42  if (error == FILE_ERROR_OK) {
43    // Error if an exclusive mode is requested, or the entry is not a file.
44    return (is_exclusive ||
45            entry.file_info().is_directory() ||
46            entry.file_specific_info().is_hosted_document()) ?
47        FILE_ERROR_EXISTS : FILE_ERROR_OK;
48  }
49
50  // If the file is not found, an actual request to create a new file will be
51  // sent to the server.
52  if (error == FILE_ERROR_NOT_FOUND) {
53    // If parent path is not a directory, it is an error.
54    ResourceEntry parent;
55    if (metadata->GetResourceEntryByPath(
56            file_path.DirName(), &parent) != FILE_ERROR_OK ||
57        !parent.file_info().is_directory())
58      return FILE_ERROR_NOT_A_DIRECTORY;
59
60    // In the request, parent_resource_id and mime_type are needed.
61    // Here, populate them.
62    *parent_resource_id = parent.resource_id();
63
64    // If mime_type is not set or "application/octet-stream", guess from the
65    // |file_path|. If it is still unsure, use octet-stream by default.
66    if ((mime_type->empty() || *mime_type == kMimeTypeOctetStream) &&
67        !net::GetMimeTypeFromFile(file_path, mime_type)) {
68      *mime_type = kMimeTypeOctetStream;
69    }
70  }
71
72  return error;
73}
74
75// Part of CreateFileOperation::CreateFile(), runs on |blocking_task_runner_|
76// of the operation, after server side file creation.
77FileError UpdateLocalStateForCreateFile(
78    internal::ResourceMetadata* metadata,
79    internal::FileCache* cache,
80    scoped_ptr<google_apis::ResourceEntry> resource_entry,
81    base::FilePath* file_path) {
82  DCHECK(metadata);
83  DCHECK(cache);
84  DCHECK(resource_entry);
85  DCHECK(file_path);
86
87  // Add the entry to the local resource metadata.
88  ResourceEntry entry;
89  std::string parent_resource_id;
90  if (!ConvertToResourceEntry(*resource_entry, &entry, &parent_resource_id))
91    return FILE_ERROR_NOT_A_FILE;
92
93  std::string parent_local_id;
94  FileError error = metadata->GetIdByResourceId(parent_resource_id,
95                                                &parent_local_id);
96  if (error != FILE_ERROR_OK)
97    return error;
98  entry.set_parent_local_id(parent_local_id);
99
100  std::string local_id;
101  error = metadata->AddEntry(entry, &local_id);
102
103  // Depending on timing, the metadata may have inserted via change list
104  // already. So, FILE_ERROR_EXISTS is not an error.
105  if (error == FILE_ERROR_EXISTS)
106    error = metadata->GetIdByResourceId(entry.resource_id(), &local_id);
107
108  if (error == FILE_ERROR_OK) {
109    // At this point, upload to the server is fully succeeded.
110    // Populate the |file_path| which will be used to notify the observer.
111    *file_path = metadata->GetFilePath(local_id);
112
113    // Also store an empty file to the cache.
114    // Here, failure is not a fatal error, so ignore the returned code.
115    FileError cache_store_error = FILE_ERROR_FAILED;
116    base::FilePath empty_file;
117    if (file_util::CreateTemporaryFile(&empty_file)) {
118      cache_store_error =  cache->Store(
119          local_id,
120          entry.file_specific_info().md5(),
121          empty_file,
122          internal::FileCache::FILE_OPERATION_MOVE);
123    }
124    DLOG_IF(WARNING, cache_store_error != FILE_ERROR_OK)
125        << "Failed to store a cache file: "
126        << FileErrorToString(cache_store_error)
127        << ", local_id: " << local_id;
128  }
129
130  return error;
131}
132
133}  // namespace
134
135CreateFileOperation::CreateFileOperation(
136    base::SequencedTaskRunner* blocking_task_runner,
137    OperationObserver* observer,
138    JobScheduler* scheduler,
139    internal::ResourceMetadata* metadata,
140    internal::FileCache* cache)
141    : blocking_task_runner_(blocking_task_runner),
142      observer_(observer),
143      scheduler_(scheduler),
144      metadata_(metadata),
145      cache_(cache),
146      weak_ptr_factory_(this) {
147  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
148}
149
150CreateFileOperation::~CreateFileOperation() {
151  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
152}
153
154void CreateFileOperation::CreateFile(const base::FilePath& file_path,
155                                     bool is_exclusive,
156                                     const std::string& mime_type,
157                                     const FileOperationCallback& callback) {
158  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
159  DCHECK(!callback.is_null());
160
161  std::string* parent_resource_id = new std::string;
162  std::string* determined_mime_type = new std::string(mime_type);
163  base::PostTaskAndReplyWithResult(
164      blocking_task_runner_.get(),
165      FROM_HERE,
166      base::Bind(&CheckPreConditionForCreateFile,
167                 metadata_,
168                 file_path,
169                 is_exclusive,
170                 parent_resource_id,
171                 determined_mime_type),
172      base::Bind(&CreateFileOperation::CreateFileAfterCheckPreCondition,
173                 weak_ptr_factory_.GetWeakPtr(),
174                 file_path,
175                 callback,
176                 base::Owned(parent_resource_id),
177                 base::Owned(determined_mime_type)));
178}
179
180void CreateFileOperation::CreateFileAfterCheckPreCondition(
181    const base::FilePath& file_path,
182    const FileOperationCallback& callback,
183    std::string* parent_resource_id,
184    std::string* mime_type,
185    FileError error) {
186  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
187  DCHECK(!callback.is_null());
188  DCHECK(parent_resource_id);
189  DCHECK(mime_type);
190
191  // If the file is found, or an error other than "not found" is found,
192  // runs callback and quit the operation.
193  if (error != FILE_ERROR_NOT_FOUND) {
194    callback.Run(error);
195    return;
196  }
197
198  scheduler_->CreateFile(
199      *parent_resource_id,
200      file_path,
201      file_path.BaseName().value(),
202      *mime_type,
203      ClientContext(USER_INITIATED),
204      base::Bind(&CreateFileOperation::CreateFileAfterUpload,
205                 weak_ptr_factory_.GetWeakPtr(),
206                 callback));
207}
208
209void CreateFileOperation::CreateFileAfterUpload(
210    const FileOperationCallback& callback,
211    google_apis::GDataErrorCode gdata_error,
212    scoped_ptr<google_apis::ResourceEntry> resource_entry) {
213  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
214  DCHECK(!callback.is_null());
215
216  FileError error = GDataToFileError(gdata_error);
217  if (error != FILE_ERROR_OK) {
218    callback.Run(error);
219    return;
220  }
221  DCHECK(resource_entry);
222
223  base::FilePath* file_path = new base::FilePath;
224  base::PostTaskAndReplyWithResult(
225      blocking_task_runner_.get(),
226      FROM_HERE,
227      base::Bind(&UpdateLocalStateForCreateFile,
228                 metadata_,
229                 cache_,
230                 base::Passed(&resource_entry),
231                 file_path),
232      base::Bind(&CreateFileOperation::CreateFileAfterUpdateLocalState,
233                 weak_ptr_factory_.GetWeakPtr(),
234                 callback,
235                 base::Owned(file_path)));
236}
237
238void CreateFileOperation::CreateFileAfterUpdateLocalState(
239    const FileOperationCallback& callback,
240    base::FilePath* file_path,
241    FileError error) {
242  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
243  DCHECK(!callback.is_null());
244  DCHECK(file_path);
245
246  // Notify observer if the file creation process is successfully done.
247  if (error == FILE_ERROR_OK)
248    observer_->OnDirectoryChangedByOperation(file_path->DirName());
249
250  callback.Run(error);
251}
252
253}  // namespace file_system
254}  // namespace drive
255