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 "chrome/browser/chromeos/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 "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/drive_api_error_codes.h"
23 using content::BrowserThread
;
26 namespace file_system
{
29 // Generates an unused file path with |extension| to |out_path|, as a descendant
30 // of |dir|, with its parent directory created.
31 bool 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(),
40 *out_path
= subdir
.Append(FILE_PATH_LITERAL("tmp") + extension
);
44 // Prepares for downloading the file. Allocates the enough space for the file
46 // If succeeded, returns FILE_ERROR_OK with |temp_download_file| storing the
47 // path to the file in the cache.
48 FileError
PrepareForDownloadFile(internal::FileCache
* cache
,
49 int64 expected_file_size
,
50 const base::FilePath
& temporary_file_directory
,
51 base::FilePath
* temp_download_file
) {
53 DCHECK(temp_download_file
);
55 // Ensure enough space in the cache.
56 if (!cache
->FreeDiskSpaceIfNeededFor(expected_file_size
))
57 return FILE_ERROR_NO_LOCAL_SPACE
;
59 return base::CreateTemporaryFileInDir(
60 temporary_file_directory
,
61 temp_download_file
) ? FILE_ERROR_OK
: FILE_ERROR_FAILED
;
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
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.
73 FileError
CheckPreConditionForEnsureFileDownloaded(
74 internal::ResourceMetadata
* metadata
,
75 internal::FileCache
* cache
,
76 const base::FilePath
& temporary_file_directory
,
77 const std::string
& local_id
,
79 base::FilePath
* cache_file_path
,
80 base::FilePath
* temp_download_file_path
) {
83 DCHECK(cache_file_path
);
85 FileError error
= metadata
->GetResourceEntryById(local_id
, entry
);
86 if (error
!= FILE_ERROR_OK
)
89 if (entry
->file_info().is_directory())
90 return FILE_ERROR_NOT_A_FILE
;
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
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
,
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
;
114 *cache_file_path
= gdoc_file_path
;
115 entry
->mutable_file_info()->set_size(file_info
.size
);
116 return FILE_ERROR_OK
;
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
124 return PrepareForDownloadFile(cache
, entry
->file_info().size(),
125 temporary_file_directory
,
126 temp_download_file_path
);
129 // This entry does not exist on the server, store an empty file and mark it
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
)
139 error
= metadata
->GetResourceEntryById(local_id
, entry
);
140 if (error
!= FILE_ERROR_OK
)
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
);
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
)
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
);
167 return FILE_ERROR_OK
;
170 struct CheckPreconditionForEnsureFileDownloadedParams
{
171 internal::ResourceMetadata
* metadata
;
172 internal::FileCache
* cache
;
173 base::FilePath temporary_file_directory
;
176 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
177 // the given ID. Also fills |drive_file_path| with the path of the entry.
178 FileError
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
)
188 return CheckPreConditionForEnsureFileDownloaded(
189 params
.metadata
, params
.cache
, params
.temporary_file_directory
, local_id
,
190 entry
, cache_file_path
, temp_download_file_path
);
193 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
194 // the given file path.
195 FileError
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
)
205 return CheckPreConditionForEnsureFileDownloaded(
206 params
.metadata
, params
.cache
, params
.temporary_file_directory
, local_id
,
207 entry
, cache_file_path
, temp_download_file_path
);
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|.
214 FileError
UpdateLocalStateForDownloadFile(
215 internal::ResourceMetadata
* metadata
,
216 internal::FileCache
* cache
,
217 const ResourceEntry
& entry_before_download
,
218 google_apis::DriveApiErrorCode gdata_error
,
219 const base::FilePath
& downloaded_file_path
,
220 ResourceEntry
* entry_after_update
,
221 base::FilePath
* cache_file_path
) {
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 */));
229 FileError error
= GDataToFileError(gdata_error
);
230 if (error
!= FILE_ERROR_OK
)
233 const std::string
& local_id
= entry_before_download
.local_id();
235 // Do not overwrite locally edited file with server side contents.
237 error
= metadata
->GetResourceEntryById(local_id
, &entry
);
238 if (error
!= FILE_ERROR_OK
)
240 if (entry
.file_specific_info().cache_state().is_dirty())
241 return FILE_ERROR_IN_USE
;
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
)
250 base::Closure unused_file_deleter_closure
= file_deleter
.Release();
252 error
= metadata
->GetResourceEntryById(local_id
, entry_after_update
);
253 if (error
!= FILE_ERROR_OK
)
256 return cache
->GetFile(local_id
, cache_file_path
);
261 class DownloadOperation::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());
278 base::Closure
GetCancelClosure() {
279 return base::Bind(&DownloadParams::Cancel
, weak_ptr_factory_
.GetWeakPtr());
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_
)));
287 completion_callback_
.Run(FILE_ERROR_OK
, cache_file_path
, entry_
.Pass());
290 void OnStartDownloading(const base::Closure
& cancel_download_closure
) {
291 cancel_download_closure_
= cancel_download_closure
;
292 if (initialized_callback_
.is_null()) {
297 initialized_callback_
.Run(FILE_ERROR_OK
, base::FilePath(),
298 make_scoped_ptr(new ResourceEntry(*entry_
)));
301 void OnError(FileError error
) const {
302 completion_callback_
.Run(
303 error
, base::FilePath(), scoped_ptr
<ResourceEntry
>());
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());
311 const google_apis::GetContentCallback
& get_content_callback() const {
312 return get_content_callback_
;
315 const ResourceEntry
& entry() const { return *entry_
; }
317 bool was_cancelled() const { return was_cancelled_
; }
321 was_cancelled_
= true;
322 if (!cancel_download_closure_
.is_null())
323 cancel_download_closure_
.Run();
326 const GetFileContentInitializedCallback initialized_callback_
;
327 const google_apis::GetContentCallback get_content_callback_
;
328 const GetFileCallback completion_callback_
;
330 scoped_ptr
<ResourceEntry
> entry_
;
331 base::Closure cancel_download_closure_
;
334 base::WeakPtrFactory
<DownloadParams
> weak_ptr_factory_
;
335 DISALLOW_COPY_AND_ASSIGN(DownloadParams
);
338 DownloadOperation::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
),
347 scheduler_(scheduler
),
350 temporary_file_directory_(temporary_file_directory
),
351 weak_ptr_factory_(this) {
354 DownloadOperation::~DownloadOperation() {
357 base::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_CURRENTLY_ON(BrowserThread::UI
);
364 DCHECK(!completion_callback
.is_null());
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(),
381 base::Bind(&CheckPreConditionForEnsureFileDownloadedByLocalId
,
386 temp_download_file_path
,
388 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition
,
389 weak_ptr_factory_
.GetWeakPtr(),
390 base::Passed(&download_params
),
392 base::Owned(drive_file_path
),
393 base::Owned(cache_file_path
),
394 base::Owned(temp_download_file_path
)));
395 return cancel_closure
;
398 base::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_CURRENTLY_ON(BrowserThread::UI
);
405 DCHECK(!completion_callback
.is_null());
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(),
422 base::Bind(&CheckPreConditionForEnsureFileDownloadedByPath
,
426 temp_download_file_path
,
428 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition
,
429 weak_ptr_factory_
.GetWeakPtr(),
430 base::Passed(&download_params
),
432 base::Owned(drive_file_path
),
433 base::Owned(cache_file_path
),
434 base::Owned(temp_download_file_path
)));
435 return cancel_closure
;
438 void 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
,
445 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
447 DCHECK(drive_file_path
);
448 DCHECK(cache_file_path
);
450 if (error
!= FILE_ERROR_OK
) {
451 // During precondition check, an error is found.
452 params
->OnError(error
);
456 if (!cache_file_path
->empty()) {
457 // The cache file is found.
458 params
->OnCacheFileFound(*cache_file_path
);
462 if (params
->was_cancelled()) {
463 params
->OnError(FILE_ERROR_ABORT
);
467 DCHECK(!params
->entry().resource_id().empty());
468 DownloadParams
* params_ptr
= params
.get();
469 JobID id
= scheduler_
->DownloadFile(
471 params_ptr
->entry().file_info().size(),
472 *temp_download_file_path
,
473 params_ptr
->entry().resource_id(),
475 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile
,
476 weak_ptr_factory_
.GetWeakPtr(),
478 base::Passed(¶ms
)),
479 params_ptr
->get_content_callback());
481 // Notify via |initialized_callback| if necessary.
482 params_ptr
->OnStartDownloading(
483 base::Bind(&DownloadOperation::CancelJob
,
484 weak_ptr_factory_
.GetWeakPtr(), id
));
487 void DownloadOperation::EnsureFileDownloadedAfterDownloadFile(
488 const base::FilePath
& drive_file_path
,
489 scoped_ptr
<DownloadParams
> params
,
490 google_apis::DriveApiErrorCode gdata_error
,
491 const base::FilePath
& downloaded_file_path
) {
492 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
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(),
500 base::Bind(&UpdateLocalStateForDownloadFile
,
505 downloaded_file_path
,
508 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState
,
509 weak_ptr_factory_
.GetWeakPtr(),
511 base::Passed(¶ms
),
512 base::Passed(make_scoped_ptr(entry_after_update
)),
513 base::Owned(cache_file_path
)));
516 void 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
,
522 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
524 if (error
!= FILE_ERROR_OK
) {
525 params
->OnError(error
);
528 DCHECK(!entry_after_update
->file_info().is_directory());
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());
538 void DownloadOperation::CancelJob(JobID job_id
) {
539 scheduler_
->CancelJob(job_id
);
542 } // namespace file_system