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