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/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"
22 using content::BrowserThread
;
25 namespace file_system
{
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
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.
37 FileError
CheckPreConditionForEnsureFileDownloaded(
38 internal::ResourceMetadata
* metadata
,
39 internal::FileCache
* cache
,
40 const base::FilePath
& temporary_file_directory
,
41 const std::string
& local_id
,
43 base::FilePath
* cache_file_path
) {
46 DCHECK(cache_file_path
);
48 FileError error
= metadata
->GetResourceEntryById(local_id
, entry
);
49 if (error
!= FILE_ERROR_OK
)
52 if (entry
->file_info().is_directory())
53 return FILE_ERROR_NOT_A_FILE
;
55 // The file's entry should have its file specific info.
56 DCHECK(entry
->has_file_specific_info());
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
62 if (entry
->file_specific_info().is_hosted_document()) {
63 base::FilePath gdoc_file_path
;
64 // TODO(rvargas): Convert this code to use base::File::Info.
65 base::PlatformFileInfo file_info
;
66 if (!base::CreateTemporaryFileInDir(temporary_file_directory
,
68 !util::CreateGDocFile(gdoc_file_path
,
69 GURL(entry
->file_specific_info().alternate_url()),
70 entry
->resource_id()) ||
71 !base::GetFileInfo(gdoc_file_path
,
72 reinterpret_cast<base::File::Info
*>(&file_info
)))
73 return FILE_ERROR_FAILED
;
75 *cache_file_path
= gdoc_file_path
;
76 SetPlatformFileInfoToResourceEntry(file_info
, entry
);
80 // Leave |cache_file_path| empty when no cache entry is found.
81 FileCacheEntry cache_entry
;
82 if (!cache
->GetCacheEntry(local_id
, &cache_entry
))
85 // Leave |cache_file_path| empty when the stored file is obsolete and has no
86 // local modification.
87 if (!cache_entry
.is_dirty() &&
88 entry
->file_specific_info().md5() != cache_entry
.md5())
91 // Fill |cache_file_path| with the path to the cached file.
92 error
= cache
->GetFile(local_id
, cache_file_path
);
93 if (error
!= FILE_ERROR_OK
)
96 // If the cache file is dirty, the modified file info needs to be stored in
98 // TODO(kinaba): crbug.com/246469. The logic below is a duplicate of that in
99 // drive::FileSystem::CheckLocalModificationAndRun. We should merge them once
100 // the drive::FS side is also converted to run fully on blocking pool.
101 if (cache_entry
.is_dirty()) {
102 base::PlatformFileInfo file_info
;
103 if (base::GetFileInfo(*cache_file_path
,
104 reinterpret_cast<base::File::Info
*>(&file_info
)))
105 SetPlatformFileInfoToResourceEntry(file_info
, entry
);
108 return FILE_ERROR_OK
;
111 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
112 // the given ID. Also fills |drive_file_path| with the path of the entry.
113 FileError
CheckPreConditionForEnsureFileDownloadedByLocalId(
114 internal::ResourceMetadata
* metadata
,
115 internal::FileCache
* cache
,
116 const std::string
& local_id
,
117 const base::FilePath
& temporary_file_directory
,
118 base::FilePath
* drive_file_path
,
119 base::FilePath
* cache_file_path
,
120 ResourceEntry
* entry
) {
121 *drive_file_path
= metadata
->GetFilePath(local_id
);
122 return CheckPreConditionForEnsureFileDownloaded(
123 metadata
, cache
, temporary_file_directory
, local_id
, entry
,
127 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
128 // the given file path.
129 FileError
CheckPreConditionForEnsureFileDownloadedByPath(
130 internal::ResourceMetadata
* metadata
,
131 internal::FileCache
* cache
,
132 const base::FilePath
& file_path
,
133 const base::FilePath
& temporary_file_directory
,
134 base::FilePath
* cache_file_path
,
135 ResourceEntry
* entry
) {
136 std::string local_id
;
137 FileError error
= metadata
->GetIdByPath(file_path
, &local_id
);
138 if (error
!= FILE_ERROR_OK
)
140 return CheckPreConditionForEnsureFileDownloaded(
141 metadata
, cache
, temporary_file_directory
, local_id
, entry
,
145 // Creates a file with unique name in |dir| and stores the path to |temp_file|.
146 // Additionally, sets the permission of the file to allow read access from
147 // others and group member users (i.e, "-rw-r--r--").
148 // We need this wrapper because Drive cache files may be read from other
149 // processes (e.g., cros_disks for mounting zip files).
150 bool CreateTemporaryReadableFileInDir(const base::FilePath
& dir
,
151 base::FilePath
* temp_file
) {
152 if (!base::CreateTemporaryFileInDir(dir
, temp_file
))
154 return base::SetPosixFilePermissions(
156 base::FILE_PERMISSION_READ_BY_USER
|
157 base::FILE_PERMISSION_WRITE_BY_USER
|
158 base::FILE_PERMISSION_READ_BY_GROUP
|
159 base::FILE_PERMISSION_READ_BY_OTHERS
);
162 // Prepares for downloading the file. Allocates the enough space for the file
164 // If succeeded, returns FILE_ERROR_OK with |temp_download_file| storing the
165 // path to the file in the cache.
166 FileError
PrepareForDownloadFile(internal::FileCache
* cache
,
167 int64 expected_file_size
,
168 const base::FilePath
& temporary_file_directory
,
169 base::FilePath
* temp_download_file
) {
171 DCHECK(temp_download_file
);
173 // Ensure enough space in the cache.
174 if (!cache
->FreeDiskSpaceIfNeededFor(expected_file_size
))
175 return FILE_ERROR_NO_LOCAL_SPACE
;
177 // Create the temporary file which will store the downloaded content.
178 return CreateTemporaryReadableFileInDir(
179 temporary_file_directory
,
180 temp_download_file
) ? FILE_ERROR_OK
: FILE_ERROR_FAILED
;
183 // Stores the downloaded file at |downloaded_file_path| into |cache|.
184 // If succeeded, returns FILE_ERROR_OK with |cache_file_path| storing the
185 // path to the cache file.
186 // If failed, returns an error code with deleting |downloaded_file_path|.
187 FileError
UpdateLocalStateForDownloadFile(
188 internal::FileCache
* cache
,
189 const std::string
& local_id
,
190 const std::string
& md5
,
191 google_apis::GDataErrorCode gdata_error
,
192 const base::FilePath
& downloaded_file_path
,
193 base::FilePath
* cache_file_path
) {
196 FileError error
= GDataToFileError(gdata_error
);
197 if (error
!= FILE_ERROR_OK
) {
198 base::DeleteFile(downloaded_file_path
, false /* recursive */);
202 // Here the download is completed successfully, so store it into the cache.
203 error
= cache
->Store(local_id
, md5
, downloaded_file_path
,
204 internal::FileCache::FILE_OPERATION_MOVE
);
205 if (error
!= FILE_ERROR_OK
) {
206 base::DeleteFile(downloaded_file_path
, false /* recursive */);
210 return cache
->GetFile(local_id
, cache_file_path
);
215 class DownloadOperation::DownloadParams
{
218 const GetFileContentInitializedCallback initialized_callback
,
219 const google_apis::GetContentCallback get_content_callback
,
220 const GetFileCallback completion_callback
,
221 scoped_ptr
<ResourceEntry
> entry
)
222 : initialized_callback_(initialized_callback
),
223 get_content_callback_(get_content_callback
),
224 completion_callback_(completion_callback
),
225 entry_(entry
.Pass()) {
226 DCHECK(!completion_callback_
.is_null());
230 void OnCacheFileFound(const base::FilePath
& cache_file_path
) const {
231 if (initialized_callback_
.is_null())
235 initialized_callback_
.Run(
236 FILE_ERROR_OK
, make_scoped_ptr(new ResourceEntry(*entry_
)),
237 cache_file_path
, base::Closure());
240 void OnStartDownloading(const base::Closure
& cancel_download_closure
) const {
241 if (initialized_callback_
.is_null()) {
246 initialized_callback_
.Run(
247 FILE_ERROR_OK
, make_scoped_ptr(new ResourceEntry(*entry_
)),
248 base::FilePath(), cancel_download_closure
);
251 void OnError(FileError error
) const {
252 completion_callback_
.Run(
253 error
, base::FilePath(), scoped_ptr
<ResourceEntry
>());
256 void OnComplete(const base::FilePath
& cache_file_path
) {
257 completion_callback_
.Run(FILE_ERROR_OK
, cache_file_path
, entry_
.Pass());
260 const google_apis::GetContentCallback
& get_content_callback() const {
261 return get_content_callback_
;
264 const ResourceEntry
& entry() const { return *entry_
; }
267 const GetFileContentInitializedCallback initialized_callback_
;
268 const google_apis::GetContentCallback get_content_callback_
;
269 const GetFileCallback completion_callback_
;
271 scoped_ptr
<ResourceEntry
> entry_
;
273 DISALLOW_COPY_AND_ASSIGN(DownloadParams
);
276 DownloadOperation::DownloadOperation(
277 base::SequencedTaskRunner
* blocking_task_runner
,
278 OperationObserver
* observer
,
279 JobScheduler
* scheduler
,
280 internal::ResourceMetadata
* metadata
,
281 internal::FileCache
* cache
,
282 const base::FilePath
& temporary_file_directory
)
283 : blocking_task_runner_(blocking_task_runner
),
285 scheduler_(scheduler
),
288 temporary_file_directory_(temporary_file_directory
),
289 weak_ptr_factory_(this) {
292 DownloadOperation::~DownloadOperation() {
295 void DownloadOperation::EnsureFileDownloadedByLocalId(
296 const std::string
& local_id
,
297 const ClientContext
& context
,
298 const GetFileContentInitializedCallback
& initialized_callback
,
299 const google_apis::GetContentCallback
& get_content_callback
,
300 const GetFileCallback
& completion_callback
) {
301 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
302 DCHECK(!completion_callback
.is_null());
304 base::FilePath
* drive_file_path
= new base::FilePath
;
305 base::FilePath
* cache_file_path
= new base::FilePath
;
306 ResourceEntry
* entry
= new ResourceEntry
;
307 scoped_ptr
<DownloadParams
> params(new DownloadParams(
308 initialized_callback
, get_content_callback
, completion_callback
,
309 make_scoped_ptr(entry
)));
310 base::PostTaskAndReplyWithResult(
311 blocking_task_runner_
.get(),
313 base::Bind(&CheckPreConditionForEnsureFileDownloadedByLocalId
,
314 base::Unretained(metadata_
),
315 base::Unretained(cache_
),
317 temporary_file_directory_
,
321 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition
,
322 weak_ptr_factory_
.GetWeakPtr(),
323 base::Passed(¶ms
),
325 base::Owned(drive_file_path
),
326 base::Owned(cache_file_path
)));
329 void DownloadOperation::EnsureFileDownloadedByPath(
330 const base::FilePath
& file_path
,
331 const ClientContext
& context
,
332 const GetFileContentInitializedCallback
& initialized_callback
,
333 const google_apis::GetContentCallback
& get_content_callback
,
334 const GetFileCallback
& completion_callback
) {
335 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
336 DCHECK(!completion_callback
.is_null());
338 base::FilePath
* drive_file_path
= new base::FilePath(file_path
);
339 base::FilePath
* cache_file_path
= new base::FilePath
;
340 ResourceEntry
* entry
= new ResourceEntry
;
341 scoped_ptr
<DownloadParams
> params(new DownloadParams(
342 initialized_callback
, get_content_callback
, completion_callback
,
343 make_scoped_ptr(entry
)));
344 base::PostTaskAndReplyWithResult(
345 blocking_task_runner_
.get(),
347 base::Bind(&CheckPreConditionForEnsureFileDownloadedByPath
,
348 base::Unretained(metadata_
),
349 base::Unretained(cache_
),
351 temporary_file_directory_
,
354 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition
,
355 weak_ptr_factory_
.GetWeakPtr(),
356 base::Passed(¶ms
),
358 base::Owned(drive_file_path
),
359 base::Owned(cache_file_path
)));
362 void DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition(
363 scoped_ptr
<DownloadParams
> params
,
364 const ClientContext
& context
,
365 base::FilePath
* drive_file_path
,
366 base::FilePath
* cache_file_path
,
368 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
370 DCHECK(drive_file_path
);
371 DCHECK(cache_file_path
);
373 if (error
!= FILE_ERROR_OK
) {
374 // During precondition check, an error is found.
375 params
->OnError(error
);
379 if (!cache_file_path
->empty()) {
380 // The cache file is found.
381 params
->OnCacheFileFound(*cache_file_path
);
382 params
->OnComplete(*cache_file_path
);
386 // If cache file is not found, try to download the file from the server
387 // instead. Check if we have enough space, based on the expected file size.
388 // - if we don't have enough space, try to free up the disk space
389 // - if we still don't have enough space, return "no space" error
390 // - if we have enough space, start downloading the file from the server
391 int64 size
= params
->entry().file_info().size();
392 base::FilePath
* temp_download_file_path
= new base::FilePath
;
393 base::PostTaskAndReplyWithResult(
394 blocking_task_runner_
.get(),
396 base::Bind(&PrepareForDownloadFile
,
397 base::Unretained(cache_
),
399 temporary_file_directory_
,
400 temp_download_file_path
),
402 &DownloadOperation::EnsureFileDownloadedAfterPrepareForDownloadFile
,
403 weak_ptr_factory_
.GetWeakPtr(),
404 base::Passed(¶ms
),
407 base::Owned(temp_download_file_path
)));
410 void DownloadOperation::EnsureFileDownloadedAfterPrepareForDownloadFile(
411 scoped_ptr
<DownloadParams
> params
,
412 const ClientContext
& context
,
413 const base::FilePath
& drive_file_path
,
414 base::FilePath
* temp_download_file_path
,
416 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
418 DCHECK(temp_download_file_path
);
420 if (error
!= FILE_ERROR_OK
) {
421 params
->OnError(error
);
425 DownloadParams
* params_ptr
= params
.get();
426 JobID id
= scheduler_
->DownloadFile(
428 params_ptr
->entry().file_info().size(),
429 *temp_download_file_path
,
430 params_ptr
->entry().resource_id(),
432 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile
,
433 weak_ptr_factory_
.GetWeakPtr(),
435 base::Passed(¶ms
)),
436 params_ptr
->get_content_callback());
438 // Notify via |initialized_callback| if necessary.
439 params_ptr
->OnStartDownloading(
440 base::Bind(&DownloadOperation::CancelJob
,
441 weak_ptr_factory_
.GetWeakPtr(), id
));
444 void DownloadOperation::EnsureFileDownloadedAfterDownloadFile(
445 const base::FilePath
& drive_file_path
,
446 scoped_ptr
<DownloadParams
> params
,
447 google_apis::GDataErrorCode gdata_error
,
448 const base::FilePath
& downloaded_file_path
) {
449 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
451 const std::string
& local_id
= params
->entry().local_id();
452 const std::string
& md5
= params
->entry().file_specific_info().md5();
453 base::FilePath
* cache_file_path
= new base::FilePath
;
454 base::PostTaskAndReplyWithResult(
455 blocking_task_runner_
.get(),
457 base::Bind(&UpdateLocalStateForDownloadFile
,
458 base::Unretained(cache_
),
462 downloaded_file_path
,
464 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState
,
465 weak_ptr_factory_
.GetWeakPtr(),
467 base::Passed(¶ms
),
468 base::Owned(cache_file_path
)));
471 void DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState(
472 const base::FilePath
& file_path
,
473 scoped_ptr
<DownloadParams
> params
,
474 base::FilePath
* cache_file_path
,
476 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
478 if (error
!= FILE_ERROR_OK
) {
479 params
->OnError(error
);
483 // Storing to cache changes the "offline available" status, hence notify.
484 observer_
->OnDirectoryChangedByOperation(file_path
.DirName());
485 params
->OnComplete(*cache_file_path
);
488 void DownloadOperation::CancelJob(JobID job_id
) {
489 scheduler_
->CancelJob(job_id
);
492 } // namespace file_system