Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / drive / file_system / download_operation.cc
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/download_operation.h"
6
7 #include "base/callback_helpers.h"
8 #include "base/file_util.h"
9 #include "base/files/file_path.h"
10 #include "base/logging.h"
11 #include "base/task_runner_util.h"
12 #include "chrome/browser/chromeos/drive/drive.pb.h"
13 #include "chrome/browser/chromeos/drive/file_cache.h"
14 #include "chrome/browser/chromeos/drive/file_change.h"
15 #include "chrome/browser/chromeos/drive/file_errors.h"
16 #include "chrome/browser/chromeos/drive/file_system/operation_delegate.h"
17 #include "chrome/browser/chromeos/drive/file_system_util.h"
18 #include "chrome/browser/chromeos/drive/job_scheduler.h"
19 #include "chrome/browser/chromeos/drive/resource_metadata.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "google_apis/drive/gdata_errorcode.h"
22
23 using content::BrowserThread;
24
25 namespace drive {
26 namespace file_system {
27 namespace {
28
29 // Generates an unused file path with |extension| to |out_path|, as a descendant
30 // of |dir|, with its parent directory created.
31 bool GeneratesUniquePathWithExtension(
32     const base::FilePath& dir,
33     const base::FilePath::StringType& extension,
34     base::FilePath* out_path) {
35   base::FilePath subdir;
36   if (!base::CreateTemporaryDirInDir(dir, base::FilePath::StringType(),
37                                      &subdir)) {
38     return false;
39   }
40   *out_path = subdir.Append(FILE_PATH_LITERAL("tmp") + extension);
41   return true;
42 }
43
44 // Prepares for downloading the file. Allocates the enough space for the file
45 // in the cache.
46 // If succeeded, returns FILE_ERROR_OK with |temp_download_file| storing the
47 // path to the file in the cache.
48 FileError PrepareForDownloadFile(internal::FileCache* cache,
49                                  int64 expected_file_size,
50                                  const base::FilePath& temporary_file_directory,
51                                  base::FilePath* temp_download_file) {
52   DCHECK(cache);
53   DCHECK(temp_download_file);
54
55   // Ensure enough space in the cache.
56   if (!cache->FreeDiskSpaceIfNeededFor(expected_file_size))
57     return FILE_ERROR_NO_LOCAL_SPACE;
58
59   return base::CreateTemporaryFileInDir(
60       temporary_file_directory,
61       temp_download_file) ? FILE_ERROR_OK : FILE_ERROR_FAILED;
62 }
63
64 // If the resource is a hosted document, creates a JSON file representing the
65 // resource locally, and returns FILE_ERROR_OK with |cache_file_path| storing
66 // the path to the JSON file.
67 // If the resource is a regular file and its local cache is available,
68 // returns FILE_ERROR_OK with |cache_file_path| storing the path to the
69 // cache file.
70 // If the resource is a regular file but its local cache is NOT available,
71 // returns FILE_ERROR_OK, but |cache_file_path| is kept empty.
72 // Otherwise returns error code.
73 FileError CheckPreConditionForEnsureFileDownloaded(
74     internal::ResourceMetadata* metadata,
75     internal::FileCache* cache,
76     const base::FilePath& temporary_file_directory,
77     const std::string& local_id,
78     ResourceEntry* entry,
79     base::FilePath* cache_file_path,
80     base::FilePath* temp_download_file_path) {
81   DCHECK(metadata);
82   DCHECK(cache);
83   DCHECK(cache_file_path);
84
85   FileError error = metadata->GetResourceEntryById(local_id, entry);
86   if (error != FILE_ERROR_OK)
87     return error;
88
89   if (entry->file_info().is_directory())
90     return FILE_ERROR_NOT_A_FILE;
91
92   // For a hosted document, we create a special JSON file to represent the
93   // document instead of fetching the document content in one of the exported
94   // formats. The JSON file contains the edit URL and resource ID of the
95   // document.
96   if (entry->file_specific_info().is_hosted_document()) {
97     base::FilePath::StringType extension = base::FilePath::FromUTF8Unsafe(
98         entry->file_specific_info().document_extension()).value();
99     base::FilePath gdoc_file_path;
100     base::File::Info file_info;
101     // We add the gdoc file extension in the temporary file, so that in cross
102     // profile drag-and-drop between Drive folders, the destination profiles's
103     // CopyOperation can detect the special JSON file only by the path.
104     if (!GeneratesUniquePathWithExtension(temporary_file_directory,
105                                           extension,
106                                           &gdoc_file_path) ||
107         !util::CreateGDocFile(gdoc_file_path,
108                               GURL(entry->file_specific_info().alternate_url()),
109                               entry->resource_id()) ||
110         !base::GetFileInfo(gdoc_file_path,
111                            reinterpret_cast<base::File::Info*>(&file_info)))
112       return FILE_ERROR_FAILED;
113
114     *cache_file_path = gdoc_file_path;
115     entry->mutable_file_info()->set_size(file_info.size);
116     return FILE_ERROR_OK;
117   }
118
119   if (!entry->file_specific_info().cache_state().is_present()) {
120     // This file has no cache file.
121     if (!entry->resource_id().empty()) {
122       // This entry exists on the server, leave |cache_file_path| empty to
123       // start download.
124       return PrepareForDownloadFile(cache, entry->file_info().size(),
125                                     temporary_file_directory,
126                                     temp_download_file_path);
127     }
128
129     // This entry does not exist on the server, store an empty file and mark it
130     // as dirty.
131     base::FilePath empty_file;
132     if (!base::CreateTemporaryFileInDir(temporary_file_directory, &empty_file))
133       return FILE_ERROR_FAILED;
134     error = cache->Store(local_id, std::string(), empty_file,
135                          internal::FileCache::FILE_OPERATION_MOVE);
136     if (error != FILE_ERROR_OK)
137       return error;
138
139     error = metadata->GetResourceEntryById(local_id, entry);
140     if (error != FILE_ERROR_OK)
141       return error;
142   }
143
144   // Leave |cache_file_path| empty when the stored file is obsolete and has no
145   // local modification.
146   if (!entry->file_specific_info().cache_state().is_dirty() &&
147       entry->file_specific_info().md5() !=
148       entry->file_specific_info().cache_state().md5()) {
149     return PrepareForDownloadFile(cache, entry->file_info().size(),
150                                   temporary_file_directory,
151                                   temp_download_file_path);
152   }
153
154   // Fill |cache_file_path| with the path to the cached file.
155   error = cache->GetFile(local_id, cache_file_path);
156   if (error != FILE_ERROR_OK)
157     return error;
158
159   // If the cache file is to be returned as the download result, the file info
160   // of the cache needs to be returned via |entry|.
161   // TODO(kinaba): crbug.com/246469. The logic below is similar to that in
162   // drive::FileSystem::CheckLocalModificationAndRun. We should merge them.
163   base::File::Info file_info;
164   if (base::GetFileInfo(*cache_file_path, &file_info))
165     entry->mutable_file_info()->set_size(file_info.size);
166
167   return FILE_ERROR_OK;
168 }
169
170 struct CheckPreconditionForEnsureFileDownloadedParams {
171   internal::ResourceMetadata* metadata;
172   internal::FileCache* cache;
173   base::FilePath temporary_file_directory;
174 };
175
176 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
177 // the given ID. Also fills |drive_file_path| with the path of the entry.
178 FileError CheckPreConditionForEnsureFileDownloadedByLocalId(
179     const CheckPreconditionForEnsureFileDownloadedParams& params,
180     const std::string& local_id,
181     base::FilePath* drive_file_path,
182     base::FilePath* cache_file_path,
183     base::FilePath* temp_download_file_path,
184     ResourceEntry* entry) {
185   FileError error = params.metadata->GetFilePath(local_id, drive_file_path);
186   if (error != FILE_ERROR_OK)
187     return error;
188   return CheckPreConditionForEnsureFileDownloaded(
189       params.metadata, params.cache, params.temporary_file_directory, local_id,
190       entry, cache_file_path, temp_download_file_path);
191 }
192
193 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
194 // the given file path.
195 FileError CheckPreConditionForEnsureFileDownloadedByPath(
196     const CheckPreconditionForEnsureFileDownloadedParams& params,
197     const base::FilePath& file_path,
198     base::FilePath* cache_file_path,
199     base::FilePath* temp_download_file_path,
200     ResourceEntry* entry) {
201   std::string local_id;
202   FileError error = params.metadata->GetIdByPath(file_path, &local_id);
203   if (error != FILE_ERROR_OK)
204     return error;
205   return CheckPreConditionForEnsureFileDownloaded(
206       params.metadata, params.cache, params.temporary_file_directory, local_id,
207       entry, cache_file_path, temp_download_file_path);
208 }
209
210 // Stores the downloaded file at |downloaded_file_path| into |cache|.
211 // If succeeded, returns FILE_ERROR_OK with |cache_file_path| storing the
212 // path to the cache file.
213 // If failed, returns an error code with deleting |downloaded_file_path|.
214 FileError UpdateLocalStateForDownloadFile(
215     internal::ResourceMetadata* metadata,
216     internal::FileCache* cache,
217     const ResourceEntry& entry_before_download,
218     google_apis::GDataErrorCode gdata_error,
219     const base::FilePath& downloaded_file_path,
220     ResourceEntry* entry_after_update,
221     base::FilePath* cache_file_path) {
222   DCHECK(cache);
223
224   // Downloaded file should be deleted on errors.
225   base::ScopedClosureRunner file_deleter(base::Bind(
226       base::IgnoreResult(&base::DeleteFile),
227       downloaded_file_path, false /* recursive */));
228
229   FileError error = GDataToFileError(gdata_error);
230   if (error != FILE_ERROR_OK)
231     return error;
232
233   const std::string& local_id = entry_before_download.local_id();
234
235   // Do not overwrite locally edited file with server side contents.
236   ResourceEntry entry;
237   error = metadata->GetResourceEntryById(local_id, &entry);
238   if (error != FILE_ERROR_OK)
239     return error;
240   if (entry.file_specific_info().cache_state().is_dirty())
241     return FILE_ERROR_IN_USE;
242
243   // Here the download is completed successfully, so store it into the cache.
244   error = cache->Store(local_id,
245                        entry_before_download.file_specific_info().md5(),
246                        downloaded_file_path,
247                        internal::FileCache::FILE_OPERATION_MOVE);
248   if (error != FILE_ERROR_OK)
249     return error;
250   base::Closure unused_file_deleter_closure = file_deleter.Release();
251
252   error = metadata->GetResourceEntryById(local_id, entry_after_update);
253   if (error != FILE_ERROR_OK)
254     return error;
255
256   return cache->GetFile(local_id, cache_file_path);
257 }
258
259 }  // namespace
260
261 class DownloadOperation::DownloadParams {
262  public:
263   DownloadParams(
264       const GetFileContentInitializedCallback initialized_callback,
265       const google_apis::GetContentCallback get_content_callback,
266       const GetFileCallback completion_callback,
267       scoped_ptr<ResourceEntry> entry)
268       : initialized_callback_(initialized_callback),
269         get_content_callback_(get_content_callback),
270         completion_callback_(completion_callback),
271         entry_(entry.Pass()),
272         was_cancelled_(false),
273         weak_ptr_factory_(this) {
274     DCHECK(!completion_callback_.is_null());
275     DCHECK(entry_);
276   }
277
278   base::Closure GetCancelClosure() {
279     return base::Bind(&DownloadParams::Cancel, weak_ptr_factory_.GetWeakPtr());
280   }
281
282   void OnCacheFileFound(const base::FilePath& cache_file_path) {
283     if (!initialized_callback_.is_null()) {
284       initialized_callback_.Run(FILE_ERROR_OK, cache_file_path,
285                                 make_scoped_ptr(new ResourceEntry(*entry_)));
286     }
287     completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry_.Pass());
288   }
289
290   void OnStartDownloading(const base::Closure& cancel_download_closure) {
291     cancel_download_closure_ = cancel_download_closure;
292     if (initialized_callback_.is_null()) {
293       return;
294     }
295
296     DCHECK(entry_);
297     initialized_callback_.Run(FILE_ERROR_OK, base::FilePath(),
298                               make_scoped_ptr(new ResourceEntry(*entry_)));
299   }
300
301   void OnError(FileError error) const {
302     completion_callback_.Run(
303         error, base::FilePath(), scoped_ptr<ResourceEntry>());
304   }
305
306   void OnDownloadCompleted(const base::FilePath& cache_file_path,
307                            scoped_ptr<ResourceEntry> entry) const {
308     completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry.Pass());
309   }
310
311   const google_apis::GetContentCallback& get_content_callback() const {
312     return get_content_callback_;
313   }
314
315   const ResourceEntry& entry() const { return *entry_; }
316
317   bool was_cancelled() const { return was_cancelled_; }
318
319  private:
320   void Cancel() {
321     was_cancelled_ = true;
322     if (!cancel_download_closure_.is_null())
323       cancel_download_closure_.Run();
324   }
325
326   const GetFileContentInitializedCallback initialized_callback_;
327   const google_apis::GetContentCallback get_content_callback_;
328   const GetFileCallback completion_callback_;
329
330   scoped_ptr<ResourceEntry> entry_;
331   base::Closure cancel_download_closure_;
332   bool was_cancelled_;
333
334   base::WeakPtrFactory<DownloadParams> weak_ptr_factory_;
335   DISALLOW_COPY_AND_ASSIGN(DownloadParams);
336 };
337
338 DownloadOperation::DownloadOperation(
339     base::SequencedTaskRunner* blocking_task_runner,
340     OperationDelegate* delegate,
341     JobScheduler* scheduler,
342     internal::ResourceMetadata* metadata,
343     internal::FileCache* cache,
344     const base::FilePath& temporary_file_directory)
345     : blocking_task_runner_(blocking_task_runner),
346       delegate_(delegate),
347       scheduler_(scheduler),
348       metadata_(metadata),
349       cache_(cache),
350       temporary_file_directory_(temporary_file_directory),
351       weak_ptr_factory_(this) {
352 }
353
354 DownloadOperation::~DownloadOperation() {
355 }
356
357 base::Closure DownloadOperation::EnsureFileDownloadedByLocalId(
358     const std::string& local_id,
359     const ClientContext& context,
360     const GetFileContentInitializedCallback& initialized_callback,
361     const google_apis::GetContentCallback& get_content_callback,
362     const GetFileCallback& completion_callback) {
363   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
364   DCHECK(!completion_callback.is_null());
365
366   CheckPreconditionForEnsureFileDownloadedParams params;
367   params.metadata = metadata_;
368   params.cache = cache_;
369   params.temporary_file_directory = temporary_file_directory_;
370   base::FilePath* drive_file_path = new base::FilePath;
371   base::FilePath* cache_file_path = new base::FilePath;
372   base::FilePath* temp_download_file_path = new base::FilePath;
373   ResourceEntry* entry = new ResourceEntry;
374   scoped_ptr<DownloadParams> download_params(new DownloadParams(
375       initialized_callback, get_content_callback, completion_callback,
376       make_scoped_ptr(entry)));
377   base::Closure cancel_closure = download_params->GetCancelClosure();
378   base::PostTaskAndReplyWithResult(
379       blocking_task_runner_.get(),
380       FROM_HERE,
381       base::Bind(&CheckPreConditionForEnsureFileDownloadedByLocalId,
382                  params,
383                  local_id,
384                  drive_file_path,
385                  cache_file_path,
386                  temp_download_file_path,
387                  entry),
388       base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
389                  weak_ptr_factory_.GetWeakPtr(),
390                  base::Passed(&download_params),
391                  context,
392                  base::Owned(drive_file_path),
393                  base::Owned(cache_file_path),
394                  base::Owned(temp_download_file_path)));
395   return cancel_closure;
396 }
397
398 base::Closure DownloadOperation::EnsureFileDownloadedByPath(
399     const base::FilePath& file_path,
400     const ClientContext& context,
401     const GetFileContentInitializedCallback& initialized_callback,
402     const google_apis::GetContentCallback& get_content_callback,
403     const GetFileCallback& completion_callback) {
404   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
405   DCHECK(!completion_callback.is_null());
406
407   CheckPreconditionForEnsureFileDownloadedParams params;
408   params.metadata = metadata_;
409   params.cache = cache_;
410   params.temporary_file_directory = temporary_file_directory_;
411   base::FilePath* drive_file_path = new base::FilePath(file_path);
412   base::FilePath* cache_file_path = new base::FilePath;
413   base::FilePath* temp_download_file_path = new base::FilePath;
414   ResourceEntry* entry = new ResourceEntry;
415   scoped_ptr<DownloadParams> download_params(new DownloadParams(
416       initialized_callback, get_content_callback, completion_callback,
417       make_scoped_ptr(entry)));
418   base::Closure cancel_closure = download_params->GetCancelClosure();
419   base::PostTaskAndReplyWithResult(
420       blocking_task_runner_.get(),
421       FROM_HERE,
422       base::Bind(&CheckPreConditionForEnsureFileDownloadedByPath,
423                  params,
424                  file_path,
425                  cache_file_path,
426                  temp_download_file_path,
427                  entry),
428       base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
429                  weak_ptr_factory_.GetWeakPtr(),
430                  base::Passed(&download_params),
431                  context,
432                  base::Owned(drive_file_path),
433                  base::Owned(cache_file_path),
434                  base::Owned(temp_download_file_path)));
435   return cancel_closure;
436 }
437
438 void DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition(
439     scoped_ptr<DownloadParams> params,
440     const ClientContext& context,
441     base::FilePath* drive_file_path,
442     base::FilePath* cache_file_path,
443     base::FilePath* temp_download_file_path,
444     FileError error) {
445   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
446   DCHECK(params);
447   DCHECK(drive_file_path);
448   DCHECK(cache_file_path);
449
450   if (error != FILE_ERROR_OK) {
451     // During precondition check, an error is found.
452     params->OnError(error);
453     return;
454   }
455
456   if (!cache_file_path->empty()) {
457     // The cache file is found.
458     params->OnCacheFileFound(*cache_file_path);
459     return;
460   }
461
462   if (params->was_cancelled()) {
463     params->OnError(FILE_ERROR_ABORT);
464     return;
465   }
466
467   DCHECK(!params->entry().resource_id().empty());
468   DownloadParams* params_ptr = params.get();
469   JobID id = scheduler_->DownloadFile(
470       *drive_file_path,
471       params_ptr->entry().file_info().size(),
472       *temp_download_file_path,
473       params_ptr->entry().resource_id(),
474       context,
475       base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile,
476                  weak_ptr_factory_.GetWeakPtr(),
477                  *drive_file_path,
478                  base::Passed(&params)),
479       params_ptr->get_content_callback());
480
481   // Notify via |initialized_callback| if necessary.
482   params_ptr->OnStartDownloading(
483       base::Bind(&DownloadOperation::CancelJob,
484                  weak_ptr_factory_.GetWeakPtr(), id));
485 }
486
487 void DownloadOperation::EnsureFileDownloadedAfterDownloadFile(
488     const base::FilePath& drive_file_path,
489     scoped_ptr<DownloadParams> params,
490     google_apis::GDataErrorCode gdata_error,
491     const base::FilePath& downloaded_file_path) {
492   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
493
494   DownloadParams* params_ptr = params.get();
495   ResourceEntry* entry_after_update = new ResourceEntry;
496   base::FilePath* cache_file_path = new base::FilePath;
497   base::PostTaskAndReplyWithResult(
498       blocking_task_runner_.get(),
499       FROM_HERE,
500       base::Bind(&UpdateLocalStateForDownloadFile,
501                  metadata_,
502                  cache_,
503                  params_ptr->entry(),
504                  gdata_error,
505                  downloaded_file_path,
506                  entry_after_update,
507                  cache_file_path),
508       base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState,
509                  weak_ptr_factory_.GetWeakPtr(),
510                  drive_file_path,
511                  base::Passed(&params),
512                  base::Passed(make_scoped_ptr(entry_after_update)),
513                  base::Owned(cache_file_path)));
514 }
515
516 void DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState(
517     const base::FilePath& file_path,
518     scoped_ptr<DownloadParams> params,
519     scoped_ptr<ResourceEntry> entry_after_update,
520     base::FilePath* cache_file_path,
521     FileError error) {
522   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
523
524   if (error != FILE_ERROR_OK) {
525     params->OnError(error);
526     return;
527   }
528   DCHECK(!entry_after_update->file_info().is_directory());
529
530   FileChange changed_files;
531   changed_files.Update(
532       file_path, FileChange::FILE_TYPE_FILE, FileChange::ADD_OR_UPDATE);
533   // Storing to cache changes the "offline available" status, hence notify.
534   delegate_->OnFileChangedByOperation(changed_files);
535   params->OnDownloadCompleted(*cache_file_path, entry_after_update.Pass());
536 }
537
538 void DownloadOperation::CancelJob(JobID job_id) {
539   scheduler_->CancelJob(job_id);
540 }
541
542 }  // namespace file_system
543 }  // namespace drive