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.
5 #include "components/drive/file_system/download_operation.h"
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 "components/drive/drive.pb.h"
13 #include "components/drive/file_cache.h"
14 #include "components/drive/file_change.h"
15 #include "components/drive/file_errors.h"
16 #include "components/drive/file_system/operation_delegate.h"
17 #include "components/drive/file_system_core_util.h"
18 #include "components/drive/job_scheduler.h"
19 #include "components/drive/resource_metadata.h"
20 #include "google_apis/drive/drive_api_error_codes.h"
23 namespace file_system
{
26 // Generates an unused file path with |extension| to |out_path|, as a descendant
27 // of |dir|, with its parent directory created.
28 bool GeneratesUniquePathWithExtension(
29 const base::FilePath
& dir
,
30 const base::FilePath::StringType
& extension
,
31 base::FilePath
* out_path
) {
32 base::FilePath subdir
;
33 if (!base::CreateTemporaryDirInDir(dir
, base::FilePath::StringType(),
37 *out_path
= subdir
.Append(FILE_PATH_LITERAL("tmp") + extension
);
41 // Prepares for downloading the file. Allocates the enough space for the file
43 // If succeeded, returns FILE_ERROR_OK with |temp_download_file| storing the
44 // path to the file in the cache.
45 FileError
PrepareForDownloadFile(internal::FileCache
* cache
,
46 int64 expected_file_size
,
47 const base::FilePath
& temporary_file_directory
,
48 base::FilePath
* temp_download_file
) {
50 DCHECK(temp_download_file
);
52 // Ensure enough space in the cache.
53 if (!cache
->FreeDiskSpaceIfNeededFor(expected_file_size
))
54 return FILE_ERROR_NO_LOCAL_SPACE
;
56 return base::CreateTemporaryFileInDir(
57 temporary_file_directory
,
58 temp_download_file
) ? FILE_ERROR_OK
: FILE_ERROR_FAILED
;
61 // If the resource is a hosted document, creates a JSON file representing the
62 // resource locally, and returns FILE_ERROR_OK with |cache_file_path| storing
63 // the path to the JSON file.
64 // If the resource is a regular file and its local cache is available,
65 // returns FILE_ERROR_OK with |cache_file_path| storing the path to the
67 // If the resource is a regular file but its local cache is NOT available,
68 // returns FILE_ERROR_OK, but |cache_file_path| is kept empty.
69 // Otherwise returns error code.
70 FileError
CheckPreConditionForEnsureFileDownloaded(
71 internal::ResourceMetadata
* metadata
,
72 internal::FileCache
* cache
,
73 const base::FilePath
& temporary_file_directory
,
74 const std::string
& local_id
,
76 base::FilePath
* cache_file_path
,
77 base::FilePath
* temp_download_file_path
) {
80 DCHECK(cache_file_path
);
82 FileError error
= metadata
->GetResourceEntryById(local_id
, entry
);
83 if (error
!= FILE_ERROR_OK
)
86 if (entry
->file_info().is_directory())
87 return FILE_ERROR_NOT_A_FILE
;
89 // For a hosted document, we create a special JSON file to represent the
90 // document instead of fetching the document content in one of the exported
91 // formats. The JSON file contains the edit URL and resource ID of the
93 if (entry
->file_specific_info().is_hosted_document()) {
94 base::FilePath::StringType extension
= base::FilePath::FromUTF8Unsafe(
95 entry
->file_specific_info().document_extension()).value();
96 base::FilePath gdoc_file_path
;
97 base::File::Info file_info
;
98 // We add the gdoc file extension in the temporary file, so that in cross
99 // profile drag-and-drop between Drive folders, the destination profiles's
100 // CopyOperation can detect the special JSON file only by the path.
101 if (!GeneratesUniquePathWithExtension(temporary_file_directory
,
104 !util::CreateGDocFile(gdoc_file_path
,
105 GURL(entry
->file_specific_info().alternate_url()),
106 entry
->resource_id()) ||
107 !base::GetFileInfo(gdoc_file_path
,
108 reinterpret_cast<base::File::Info
*>(&file_info
)))
109 return FILE_ERROR_FAILED
;
111 *cache_file_path
= gdoc_file_path
;
112 entry
->mutable_file_info()->set_size(file_info
.size
);
113 return FILE_ERROR_OK
;
116 if (!entry
->file_specific_info().cache_state().is_present()) {
117 // This file has no cache file.
118 if (!entry
->resource_id().empty()) {
119 // This entry exists on the server, leave |cache_file_path| empty to
121 return PrepareForDownloadFile(cache
, entry
->file_info().size(),
122 temporary_file_directory
,
123 temp_download_file_path
);
126 // This entry does not exist on the server, store an empty file and mark it
128 base::FilePath empty_file
;
129 if (!base::CreateTemporaryFileInDir(temporary_file_directory
, &empty_file
))
130 return FILE_ERROR_FAILED
;
131 error
= cache
->Store(local_id
, std::string(), empty_file
,
132 internal::FileCache::FILE_OPERATION_MOVE
);
133 if (error
!= FILE_ERROR_OK
)
136 error
= metadata
->GetResourceEntryById(local_id
, entry
);
137 if (error
!= FILE_ERROR_OK
)
141 // Leave |cache_file_path| empty when the stored file is obsolete and has no
142 // local modification.
143 if (!entry
->file_specific_info().cache_state().is_dirty() &&
144 entry
->file_specific_info().md5() !=
145 entry
->file_specific_info().cache_state().md5()) {
146 return PrepareForDownloadFile(cache
, entry
->file_info().size(),
147 temporary_file_directory
,
148 temp_download_file_path
);
151 // Fill |cache_file_path| with the path to the cached file.
152 error
= cache
->GetFile(local_id
, cache_file_path
);
153 if (error
!= FILE_ERROR_OK
)
156 // If the cache file is to be returned as the download result, the file info
157 // of the cache needs to be returned via |entry|.
158 // TODO(kinaba): crbug.com/246469. The logic below is similar to that in
159 // drive::FileSystem::CheckLocalModificationAndRun. We should merge them.
160 base::File::Info file_info
;
161 if (base::GetFileInfo(*cache_file_path
, &file_info
))
162 entry
->mutable_file_info()->set_size(file_info
.size
);
164 return FILE_ERROR_OK
;
167 struct CheckPreconditionForEnsureFileDownloadedParams
{
168 internal::ResourceMetadata
* metadata
;
169 internal::FileCache
* cache
;
170 base::FilePath temporary_file_directory
;
173 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
174 // the given ID. Also fills |drive_file_path| with the path of the entry.
175 FileError
CheckPreConditionForEnsureFileDownloadedByLocalId(
176 const CheckPreconditionForEnsureFileDownloadedParams
& params
,
177 const std::string
& local_id
,
178 base::FilePath
* drive_file_path
,
179 base::FilePath
* cache_file_path
,
180 base::FilePath
* temp_download_file_path
,
181 ResourceEntry
* entry
) {
182 FileError error
= params
.metadata
->GetFilePath(local_id
, drive_file_path
);
183 if (error
!= FILE_ERROR_OK
)
185 return CheckPreConditionForEnsureFileDownloaded(
186 params
.metadata
, params
.cache
, params
.temporary_file_directory
, local_id
,
187 entry
, cache_file_path
, temp_download_file_path
);
190 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
191 // the given file path.
192 FileError
CheckPreConditionForEnsureFileDownloadedByPath(
193 const CheckPreconditionForEnsureFileDownloadedParams
& params
,
194 const base::FilePath
& file_path
,
195 base::FilePath
* cache_file_path
,
196 base::FilePath
* temp_download_file_path
,
197 ResourceEntry
* entry
) {
198 std::string local_id
;
199 FileError error
= params
.metadata
->GetIdByPath(file_path
, &local_id
);
200 if (error
!= FILE_ERROR_OK
)
202 return CheckPreConditionForEnsureFileDownloaded(
203 params
.metadata
, params
.cache
, params
.temporary_file_directory
, local_id
,
204 entry
, cache_file_path
, temp_download_file_path
);
207 // Stores the downloaded file at |downloaded_file_path| into |cache|.
208 // If succeeded, returns FILE_ERROR_OK with |cache_file_path| storing the
209 // path to the cache file.
210 // If failed, returns an error code with deleting |downloaded_file_path|.
211 FileError
UpdateLocalStateForDownloadFile(
212 internal::ResourceMetadata
* metadata
,
213 internal::FileCache
* cache
,
214 const ResourceEntry
& entry_before_download
,
215 google_apis::DriveApiErrorCode gdata_error
,
216 const base::FilePath
& downloaded_file_path
,
217 ResourceEntry
* entry_after_update
,
218 base::FilePath
* cache_file_path
) {
221 // Downloaded file should be deleted on errors.
222 base::ScopedClosureRunner
file_deleter(base::Bind(
223 base::IgnoreResult(&base::DeleteFile
),
224 downloaded_file_path
, false /* recursive */));
226 FileError error
= GDataToFileError(gdata_error
);
227 if (error
!= FILE_ERROR_OK
)
230 const std::string
& local_id
= entry_before_download
.local_id();
232 // Do not overwrite locally edited file with server side contents.
234 error
= metadata
->GetResourceEntryById(local_id
, &entry
);
235 if (error
!= FILE_ERROR_OK
)
237 if (entry
.file_specific_info().cache_state().is_dirty())
238 return FILE_ERROR_IN_USE
;
240 // Here the download is completed successfully, so store it into the cache.
241 error
= cache
->Store(local_id
,
242 entry_before_download
.file_specific_info().md5(),
243 downloaded_file_path
,
244 internal::FileCache::FILE_OPERATION_MOVE
);
245 if (error
!= FILE_ERROR_OK
)
247 base::Closure unused_file_deleter_closure
= file_deleter
.Release();
249 error
= metadata
->GetResourceEntryById(local_id
, entry_after_update
);
250 if (error
!= FILE_ERROR_OK
)
253 return cache
->GetFile(local_id
, cache_file_path
);
258 class DownloadOperation::DownloadParams
{
261 const GetFileContentInitializedCallback initialized_callback
,
262 const google_apis::GetContentCallback get_content_callback
,
263 const GetFileCallback completion_callback
,
264 scoped_ptr
<ResourceEntry
> entry
)
265 : initialized_callback_(initialized_callback
),
266 get_content_callback_(get_content_callback
),
267 completion_callback_(completion_callback
),
268 entry_(entry
.Pass()),
269 was_cancelled_(false),
270 weak_ptr_factory_(this) {
271 DCHECK(!completion_callback_
.is_null());
275 base::Closure
GetCancelClosure() {
276 return base::Bind(&DownloadParams::Cancel
, weak_ptr_factory_
.GetWeakPtr());
279 void OnCacheFileFound(const base::FilePath
& cache_file_path
) {
280 if (!initialized_callback_
.is_null()) {
281 initialized_callback_
.Run(FILE_ERROR_OK
, cache_file_path
,
282 make_scoped_ptr(new ResourceEntry(*entry_
)));
284 completion_callback_
.Run(FILE_ERROR_OK
, cache_file_path
, entry_
.Pass());
287 void OnStartDownloading(const base::Closure
& cancel_download_closure
) {
288 cancel_download_closure_
= cancel_download_closure
;
289 if (initialized_callback_
.is_null()) {
294 initialized_callback_
.Run(FILE_ERROR_OK
, base::FilePath(),
295 make_scoped_ptr(new ResourceEntry(*entry_
)));
298 void OnError(FileError error
) const {
299 completion_callback_
.Run(
300 error
, base::FilePath(), scoped_ptr
<ResourceEntry
>());
303 void OnDownloadCompleted(const base::FilePath
& cache_file_path
,
304 scoped_ptr
<ResourceEntry
> entry
) const {
305 completion_callback_
.Run(FILE_ERROR_OK
, cache_file_path
, entry
.Pass());
308 const google_apis::GetContentCallback
& get_content_callback() const {
309 return get_content_callback_
;
312 const ResourceEntry
& entry() const { return *entry_
; }
314 bool was_cancelled() const { return was_cancelled_
; }
318 was_cancelled_
= true;
319 if (!cancel_download_closure_
.is_null())
320 cancel_download_closure_
.Run();
323 const GetFileContentInitializedCallback initialized_callback_
;
324 const google_apis::GetContentCallback get_content_callback_
;
325 const GetFileCallback completion_callback_
;
327 scoped_ptr
<ResourceEntry
> entry_
;
328 base::Closure cancel_download_closure_
;
331 base::WeakPtrFactory
<DownloadParams
> weak_ptr_factory_
;
332 DISALLOW_COPY_AND_ASSIGN(DownloadParams
);
335 DownloadOperation::DownloadOperation(
336 base::SequencedTaskRunner
* blocking_task_runner
,
337 OperationDelegate
* delegate
,
338 JobScheduler
* scheduler
,
339 internal::ResourceMetadata
* metadata
,
340 internal::FileCache
* cache
,
341 const base::FilePath
& temporary_file_directory
)
342 : blocking_task_runner_(blocking_task_runner
),
344 scheduler_(scheduler
),
347 temporary_file_directory_(temporary_file_directory
),
348 weak_ptr_factory_(this) {
351 DownloadOperation::~DownloadOperation() {
354 base::Closure
DownloadOperation::EnsureFileDownloadedByLocalId(
355 const std::string
& local_id
,
356 const ClientContext
& context
,
357 const GetFileContentInitializedCallback
& initialized_callback
,
358 const google_apis::GetContentCallback
& get_content_callback
,
359 const GetFileCallback
& completion_callback
) {
360 DCHECK(thread_checker_
.CalledOnValidThread());
361 DCHECK(!completion_callback
.is_null());
363 CheckPreconditionForEnsureFileDownloadedParams params
;
364 params
.metadata
= metadata_
;
365 params
.cache
= cache_
;
366 params
.temporary_file_directory
= temporary_file_directory_
;
367 base::FilePath
* drive_file_path
= new base::FilePath
;
368 base::FilePath
* cache_file_path
= new base::FilePath
;
369 base::FilePath
* temp_download_file_path
= new base::FilePath
;
370 ResourceEntry
* entry
= new ResourceEntry
;
371 scoped_ptr
<DownloadParams
> download_params(new DownloadParams(
372 initialized_callback
, get_content_callback
, completion_callback
,
373 make_scoped_ptr(entry
)));
374 base::Closure cancel_closure
= download_params
->GetCancelClosure();
375 base::PostTaskAndReplyWithResult(
376 blocking_task_runner_
.get(),
378 base::Bind(&CheckPreConditionForEnsureFileDownloadedByLocalId
,
383 temp_download_file_path
,
385 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition
,
386 weak_ptr_factory_
.GetWeakPtr(),
387 base::Passed(&download_params
),
389 base::Owned(drive_file_path
),
390 base::Owned(cache_file_path
),
391 base::Owned(temp_download_file_path
)));
392 return cancel_closure
;
395 base::Closure
DownloadOperation::EnsureFileDownloadedByPath(
396 const base::FilePath
& file_path
,
397 const ClientContext
& context
,
398 const GetFileContentInitializedCallback
& initialized_callback
,
399 const google_apis::GetContentCallback
& get_content_callback
,
400 const GetFileCallback
& completion_callback
) {
401 DCHECK(thread_checker_
.CalledOnValidThread());
402 DCHECK(!completion_callback
.is_null());
404 CheckPreconditionForEnsureFileDownloadedParams params
;
405 params
.metadata
= metadata_
;
406 params
.cache
= cache_
;
407 params
.temporary_file_directory
= temporary_file_directory_
;
408 base::FilePath
* drive_file_path
= new base::FilePath(file_path
);
409 base::FilePath
* cache_file_path
= new base::FilePath
;
410 base::FilePath
* temp_download_file_path
= new base::FilePath
;
411 ResourceEntry
* entry
= new ResourceEntry
;
412 scoped_ptr
<DownloadParams
> download_params(new DownloadParams(
413 initialized_callback
, get_content_callback
, completion_callback
,
414 make_scoped_ptr(entry
)));
415 base::Closure cancel_closure
= download_params
->GetCancelClosure();
416 base::PostTaskAndReplyWithResult(
417 blocking_task_runner_
.get(),
419 base::Bind(&CheckPreConditionForEnsureFileDownloadedByPath
,
423 temp_download_file_path
,
425 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition
,
426 weak_ptr_factory_
.GetWeakPtr(),
427 base::Passed(&download_params
),
429 base::Owned(drive_file_path
),
430 base::Owned(cache_file_path
),
431 base::Owned(temp_download_file_path
)));
432 return cancel_closure
;
435 void DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition(
436 scoped_ptr
<DownloadParams
> params
,
437 const ClientContext
& context
,
438 base::FilePath
* drive_file_path
,
439 base::FilePath
* cache_file_path
,
440 base::FilePath
* temp_download_file_path
,
442 DCHECK(thread_checker_
.CalledOnValidThread());
444 DCHECK(drive_file_path
);
445 DCHECK(cache_file_path
);
447 if (error
!= FILE_ERROR_OK
) {
448 // During precondition check, an error is found.
449 params
->OnError(error
);
453 if (!cache_file_path
->empty()) {
454 // The cache file is found.
455 params
->OnCacheFileFound(*cache_file_path
);
459 if (params
->was_cancelled()) {
460 params
->OnError(FILE_ERROR_ABORT
);
464 DCHECK(!params
->entry().resource_id().empty());
465 DownloadParams
* params_ptr
= params
.get();
466 JobID id
= scheduler_
->DownloadFile(
468 params_ptr
->entry().file_info().size(),
469 *temp_download_file_path
,
470 params_ptr
->entry().resource_id(),
472 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile
,
473 weak_ptr_factory_
.GetWeakPtr(),
475 base::Passed(¶ms
)),
476 params_ptr
->get_content_callback());
478 // Notify via |initialized_callback| if necessary.
479 params_ptr
->OnStartDownloading(
480 base::Bind(&DownloadOperation::CancelJob
,
481 weak_ptr_factory_
.GetWeakPtr(), id
));
484 void DownloadOperation::EnsureFileDownloadedAfterDownloadFile(
485 const base::FilePath
& drive_file_path
,
486 scoped_ptr
<DownloadParams
> params
,
487 google_apis::DriveApiErrorCode gdata_error
,
488 const base::FilePath
& downloaded_file_path
) {
489 DCHECK(thread_checker_
.CalledOnValidThread());
491 DownloadParams
* params_ptr
= params
.get();
492 ResourceEntry
* entry_after_update
= new ResourceEntry
;
493 base::FilePath
* cache_file_path
= new base::FilePath
;
494 base::PostTaskAndReplyWithResult(
495 blocking_task_runner_
.get(),
497 base::Bind(&UpdateLocalStateForDownloadFile
,
502 downloaded_file_path
,
505 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState
,
506 weak_ptr_factory_
.GetWeakPtr(),
508 base::Passed(¶ms
),
509 base::Passed(make_scoped_ptr(entry_after_update
)),
510 base::Owned(cache_file_path
)));
513 void DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState(
514 const base::FilePath
& file_path
,
515 scoped_ptr
<DownloadParams
> params
,
516 scoped_ptr
<ResourceEntry
> entry_after_update
,
517 base::FilePath
* cache_file_path
,
519 DCHECK(thread_checker_
.CalledOnValidThread());
521 if (error
!= FILE_ERROR_OK
) {
522 params
->OnError(error
);
525 DCHECK(!entry_after_update
->file_info().is_directory());
527 FileChange changed_files
;
528 changed_files
.Update(file_path
, FileChange::FILE_TYPE_FILE
,
529 FileChange::CHANGE_TYPE_ADD_OR_UPDATE
);
530 // Storing to cache changes the "offline available" status, hence notify.
531 delegate_
->OnFileChangedByOperation(changed_files
);
532 params
->OnDownloadCompleted(*cache_file_path
, entry_after_update
.Pass());
535 void DownloadOperation::CancelJob(JobID job_id
) {
536 scheduler_
->CancelJob(job_id
);
539 } // namespace file_system