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