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