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/files/file_path.h"
9#include "base/files/file_util.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
23using content::BrowserThread;
24
25namespace drive {
26namespace file_system {
27namespace {
28
29// Generates an unused file path with |extension| to |out_path|, as a descendant
30// of |dir|, with its parent directory created.
31bool 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.
48FileError 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.
73FileError 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
170struct 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.
178FileError 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.
195FileError 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|.
214FileError 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
261class 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
338DownloadOperation::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
354DownloadOperation::~DownloadOperation() {
355}
356
357base::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
398base::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
438void 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
487void 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
516void 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
538void DownloadOperation::CancelJob(JobID job_id) {
539  scheduler_->CancelJob(job_id);
540}
541
542}  // namespace file_system
543}  // namespace drive
544