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/file_util.h"
9 #include "base/files/file_path.h"
10 #include "base/logging.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/task_runner_util.h"
13 #include "chrome/browser/chromeos/drive/drive.pb.h"
14 #include "chrome/browser/chromeos/drive/file_cache.h"
15 #include "chrome/browser/chromeos/drive/file_errors.h"
16 #include "chrome/browser/chromeos/drive/file_system/operation_observer.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_entry_conversion.h"
20 #include "chrome/browser/chromeos/drive/resource_metadata.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "google_apis/drive/gdata_errorcode.h"
24 using content::BrowserThread;
27 namespace file_system {
30 // Generates an unused file path with |extension| to |out_path|, as a descendant
31 // of |dir|, with its parent directory created.
32 bool GeneratesUniquePathWithExtension(
33 const base::FilePath& dir,
34 const base::FilePath::StringType& extension,
35 base::FilePath* out_path) {
36 base::FilePath subdir;
37 if (!base::CreateTemporaryDirInDir(dir, base::FilePath::StringType(),
41 *out_path = subdir.Append(FILE_PATH_LITERAL("tmp") + extension);
45 // If the resource is a hosted document, creates a JSON file representing the
46 // resource locally, and returns FILE_ERROR_OK with |cache_file_path| storing
47 // the path to the JSON file.
48 // If the resource is a regular file and its local cache is available,
49 // returns FILE_ERROR_OK with |cache_file_path| storing the path to the
51 // If the resource is a regular file but its local cache is NOT available,
52 // returns FILE_ERROR_OK, but |cache_file_path| is kept empty.
53 // Otherwise returns error code.
54 FileError CheckPreConditionForEnsureFileDownloaded(
55 internal::ResourceMetadata* metadata,
56 internal::FileCache* cache,
57 const base::FilePath& temporary_file_directory,
58 const std::string& local_id,
60 base::FilePath* cache_file_path) {
63 DCHECK(cache_file_path);
65 FileError error = metadata->GetResourceEntryById(local_id, entry);
66 if (error != FILE_ERROR_OK)
69 if (entry->file_info().is_directory())
70 return FILE_ERROR_NOT_A_FILE;
72 // For a hosted document, we create a special JSON file to represent the
73 // document instead of fetching the document content in one of the exported
74 // formats. The JSON file contains the edit URL and resource ID of the
76 if (entry->file_specific_info().is_hosted_document()) {
77 base::FilePath::StringType extension = base::FilePath::FromUTF8Unsafe(
78 entry->file_specific_info().document_extension()).value();
79 base::FilePath gdoc_file_path;
80 // TODO(rvargas): Convert this code to use base::File::Info.
81 base::File::Info file_info;
82 // We add the gdoc file extension in the temporary file, so that in cross
83 // profile drag-and-drop between Drive folders, the destination profiles's
84 // CopyOperation can detect the special JSON file only by the path.
85 if (!GeneratesUniquePathWithExtension(temporary_file_directory,
88 !util::CreateGDocFile(gdoc_file_path,
89 GURL(entry->file_specific_info().alternate_url()),
90 entry->resource_id()) ||
91 !base::GetFileInfo(gdoc_file_path,
92 reinterpret_cast<base::File::Info*>(&file_info)))
93 return FILE_ERROR_FAILED;
95 *cache_file_path = gdoc_file_path;
96 SetPlatformFileInfoToResourceEntry(file_info, entry);
100 FileCacheEntry cache_entry;
101 if (!cache->GetCacheEntry(local_id, &cache_entry) ||
102 !cache_entry.is_present()) { // This file has no cache file.
103 if (!entry->resource_id().empty()) {
104 // This entry exists on the server, leave |cache_file_path| empty to
106 return FILE_ERROR_OK;
109 // This entry does not exist on the server, store an empty file and mark it
111 base::FilePath empty_file;
112 if (!base::CreateTemporaryFileInDir(temporary_file_directory, &empty_file))
113 return FILE_ERROR_FAILED;
114 error = cache->Store(local_id, std::string(), empty_file,
115 internal::FileCache::FILE_OPERATION_MOVE);
116 if (error != FILE_ERROR_OK)
119 if (!cache->GetCacheEntry(local_id, &cache_entry))
120 return FILE_ERROR_NOT_FOUND;
123 // Leave |cache_file_path| empty when the stored file is obsolete and has no
124 // local modification.
125 if (!cache_entry.is_dirty() &&
126 entry->file_specific_info().md5() != cache_entry.md5())
127 return FILE_ERROR_OK;
129 // Fill |cache_file_path| with the path to the cached file.
130 error = cache->GetFile(local_id, cache_file_path);
131 if (error != FILE_ERROR_OK)
134 // If the cache file is to be returned as the download result, the file info
135 // of the cache needs to be returned via |entry|.
136 // TODO(kinaba): crbug.com/246469. The logic below is similar to that in
137 // drive::FileSystem::CheckLocalModificationAndRun. We should merge them.
138 base::File::Info file_info;
139 if (base::GetFileInfo(*cache_file_path, &file_info))
140 SetPlatformFileInfoToResourceEntry(file_info, entry);
142 return FILE_ERROR_OK;
145 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
146 // the given ID. Also fills |drive_file_path| with the path of the entry.
147 FileError CheckPreConditionForEnsureFileDownloadedByLocalId(
148 internal::ResourceMetadata* metadata,
149 internal::FileCache* cache,
150 const std::string& local_id,
151 const base::FilePath& temporary_file_directory,
152 base::FilePath* drive_file_path,
153 base::FilePath* cache_file_path,
154 ResourceEntry* entry) {
155 *drive_file_path = metadata->GetFilePath(local_id);
156 return CheckPreConditionForEnsureFileDownloaded(
157 metadata, cache, temporary_file_directory, local_id, entry,
161 // Calls CheckPreConditionForEnsureFileDownloaded() with the entry specified by
162 // the given file path.
163 FileError CheckPreConditionForEnsureFileDownloadedByPath(
164 internal::ResourceMetadata* metadata,
165 internal::FileCache* cache,
166 const base::FilePath& file_path,
167 const base::FilePath& temporary_file_directory,
168 base::FilePath* cache_file_path,
169 ResourceEntry* entry) {
170 std::string local_id;
171 FileError error = metadata->GetIdByPath(file_path, &local_id);
172 if (error != FILE_ERROR_OK)
174 return CheckPreConditionForEnsureFileDownloaded(
175 metadata, cache, temporary_file_directory, local_id, entry,
179 // Creates a file with unique name in |dir| and stores the path to |temp_file|.
180 // Additionally, sets the permission of the file to allow read access from
181 // others and group member users (i.e, "-rw-r--r--").
182 // We need this wrapper because Drive cache files may be read from other
183 // processes (e.g., cros_disks for mounting zip files).
184 bool CreateTemporaryReadableFileInDir(const base::FilePath& dir,
185 base::FilePath* temp_file) {
186 if (!base::CreateTemporaryFileInDir(dir, temp_file))
188 return base::SetPosixFilePermissions(
190 base::FILE_PERMISSION_READ_BY_USER |
191 base::FILE_PERMISSION_WRITE_BY_USER |
192 base::FILE_PERMISSION_READ_BY_GROUP |
193 base::FILE_PERMISSION_READ_BY_OTHERS);
196 // Prepares for downloading the file. Allocates the enough space for the file
198 // If succeeded, returns FILE_ERROR_OK with |temp_download_file| storing the
199 // path to the file in the cache.
200 FileError PrepareForDownloadFile(internal::FileCache* cache,
201 int64 expected_file_size,
202 const base::FilePath& temporary_file_directory,
203 base::FilePath* temp_download_file) {
205 DCHECK(temp_download_file);
207 // Ensure enough space in the cache.
208 if (!cache->FreeDiskSpaceIfNeededFor(expected_file_size))
209 return FILE_ERROR_NO_LOCAL_SPACE;
211 // Create the temporary file which will store the downloaded content.
212 return CreateTemporaryReadableFileInDir(
213 temporary_file_directory,
214 temp_download_file) ? FILE_ERROR_OK : FILE_ERROR_FAILED;
217 // Stores the downloaded file at |downloaded_file_path| into |cache|.
218 // If succeeded, returns FILE_ERROR_OK with |cache_file_path| storing the
219 // path to the cache file.
220 // If failed, returns an error code with deleting |downloaded_file_path|.
221 FileError UpdateLocalStateForDownloadFile(
222 internal::FileCache* cache,
223 const std::string& local_id,
224 const std::string& md5,
225 google_apis::GDataErrorCode gdata_error,
226 const base::FilePath& downloaded_file_path,
227 base::FilePath* cache_file_path) {
230 // Downloaded file should be deleted on errors.
231 base::ScopedClosureRunner file_deleter(base::Bind(
232 base::IgnoreResult(&base::DeleteFile),
233 downloaded_file_path, false /* recursive */));
235 FileError error = GDataToFileError(gdata_error);
236 if (error != FILE_ERROR_OK)
239 // Do not overwrite locally edited file with server side contents.
240 FileCacheEntry cache_entry;
241 if (cache->GetCacheEntry(local_id, &cache_entry) && cache_entry.is_dirty())
242 return FILE_ERROR_IN_USE;
244 // Here the download is completed successfully, so store it into the cache.
245 error = cache->Store(local_id, md5, downloaded_file_path,
246 internal::FileCache::FILE_OPERATION_MOVE);
247 if (error != FILE_ERROR_OK)
249 base::Closure unused_file_deleter_closure = file_deleter.Release();
251 return cache->GetFile(local_id, cache_file_path);
256 class DownloadOperation::DownloadParams {
259 const GetFileContentInitializedCallback initialized_callback,
260 const google_apis::GetContentCallback get_content_callback,
261 const GetFileCallback completion_callback,
262 scoped_ptr<ResourceEntry> entry)
263 : initialized_callback_(initialized_callback),
264 get_content_callback_(get_content_callback),
265 completion_callback_(completion_callback),
266 entry_(entry.Pass()) {
267 DCHECK(!completion_callback_.is_null());
271 void OnCacheFileFound(const base::FilePath& cache_file_path) const {
272 if (initialized_callback_.is_null())
276 initialized_callback_.Run(
277 FILE_ERROR_OK, make_scoped_ptr(new ResourceEntry(*entry_)),
278 cache_file_path, base::Closure());
281 void OnStartDownloading(const base::Closure& cancel_download_closure) const {
282 if (initialized_callback_.is_null()) {
287 initialized_callback_.Run(
288 FILE_ERROR_OK, make_scoped_ptr(new ResourceEntry(*entry_)),
289 base::FilePath(), cancel_download_closure);
292 void OnError(FileError error) const {
293 completion_callback_.Run(
294 error, base::FilePath(), scoped_ptr<ResourceEntry>());
297 void OnComplete(const base::FilePath& cache_file_path) {
298 completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry_.Pass());
301 const google_apis::GetContentCallback& get_content_callback() const {
302 return get_content_callback_;
305 const ResourceEntry& entry() const { return *entry_; }
308 const GetFileContentInitializedCallback initialized_callback_;
309 const google_apis::GetContentCallback get_content_callback_;
310 const GetFileCallback completion_callback_;
312 scoped_ptr<ResourceEntry> entry_;
314 DISALLOW_COPY_AND_ASSIGN(DownloadParams);
317 DownloadOperation::DownloadOperation(
318 base::SequencedTaskRunner* blocking_task_runner,
319 OperationObserver* observer,
320 JobScheduler* scheduler,
321 internal::ResourceMetadata* metadata,
322 internal::FileCache* cache,
323 const base::FilePath& temporary_file_directory)
324 : blocking_task_runner_(blocking_task_runner),
326 scheduler_(scheduler),
329 temporary_file_directory_(temporary_file_directory),
330 weak_ptr_factory_(this) {
333 DownloadOperation::~DownloadOperation() {
336 void DownloadOperation::EnsureFileDownloadedByLocalId(
337 const std::string& local_id,
338 const ClientContext& context,
339 const GetFileContentInitializedCallback& initialized_callback,
340 const google_apis::GetContentCallback& get_content_callback,
341 const GetFileCallback& completion_callback) {
342 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
343 DCHECK(!completion_callback.is_null());
345 base::FilePath* drive_file_path = new base::FilePath;
346 base::FilePath* cache_file_path = new base::FilePath;
347 ResourceEntry* entry = new ResourceEntry;
348 scoped_ptr<DownloadParams> params(new DownloadParams(
349 initialized_callback, get_content_callback, completion_callback,
350 make_scoped_ptr(entry)));
351 base::PostTaskAndReplyWithResult(
352 blocking_task_runner_.get(),
354 base::Bind(&CheckPreConditionForEnsureFileDownloadedByLocalId,
355 base::Unretained(metadata_),
356 base::Unretained(cache_),
358 temporary_file_directory_,
362 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
363 weak_ptr_factory_.GetWeakPtr(),
364 base::Passed(¶ms),
366 base::Owned(drive_file_path),
367 base::Owned(cache_file_path)));
370 void DownloadOperation::EnsureFileDownloadedByPath(
371 const base::FilePath& file_path,
372 const ClientContext& context,
373 const GetFileContentInitializedCallback& initialized_callback,
374 const google_apis::GetContentCallback& get_content_callback,
375 const GetFileCallback& completion_callback) {
376 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
377 DCHECK(!completion_callback.is_null());
379 base::FilePath* drive_file_path = new base::FilePath(file_path);
380 base::FilePath* cache_file_path = new base::FilePath;
381 ResourceEntry* entry = new ResourceEntry;
382 scoped_ptr<DownloadParams> params(new DownloadParams(
383 initialized_callback, get_content_callback, completion_callback,
384 make_scoped_ptr(entry)));
385 base::PostTaskAndReplyWithResult(
386 blocking_task_runner_.get(),
388 base::Bind(&CheckPreConditionForEnsureFileDownloadedByPath,
389 base::Unretained(metadata_),
390 base::Unretained(cache_),
392 temporary_file_directory_,
395 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
396 weak_ptr_factory_.GetWeakPtr(),
397 base::Passed(¶ms),
399 base::Owned(drive_file_path),
400 base::Owned(cache_file_path)));
403 void DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition(
404 scoped_ptr<DownloadParams> params,
405 const ClientContext& context,
406 base::FilePath* drive_file_path,
407 base::FilePath* cache_file_path,
409 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
411 DCHECK(drive_file_path);
412 DCHECK(cache_file_path);
414 if (error != FILE_ERROR_OK) {
415 // During precondition check, an error is found.
416 params->OnError(error);
420 if (!cache_file_path->empty()) {
421 // The cache file is found.
422 params->OnCacheFileFound(*cache_file_path);
423 params->OnComplete(*cache_file_path);
427 DCHECK(!params->entry().resource_id().empty());
429 // If cache file is not found, try to download the file from the server
430 // instead. Check if we have enough space, based on the expected file size.
431 // - if we don't have enough space, try to free up the disk space
432 // - if we still don't have enough space, return "no space" error
433 // - if we have enough space, start downloading the file from the server
434 int64 size = params->entry().file_info().size();
435 base::FilePath* temp_download_file_path = new base::FilePath;
436 base::PostTaskAndReplyWithResult(
437 blocking_task_runner_.get(),
439 base::Bind(&PrepareForDownloadFile,
440 base::Unretained(cache_),
442 temporary_file_directory_,
443 temp_download_file_path),
445 &DownloadOperation::EnsureFileDownloadedAfterPrepareForDownloadFile,
446 weak_ptr_factory_.GetWeakPtr(),
447 base::Passed(¶ms),
450 base::Owned(temp_download_file_path)));
453 void DownloadOperation::EnsureFileDownloadedAfterPrepareForDownloadFile(
454 scoped_ptr<DownloadParams> params,
455 const ClientContext& context,
456 const base::FilePath& drive_file_path,
457 base::FilePath* temp_download_file_path,
459 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
461 DCHECK(temp_download_file_path);
463 if (error != FILE_ERROR_OK) {
464 params->OnError(error);
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::GDataErrorCode gdata_error,
491 const base::FilePath& downloaded_file_path) {
492 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
494 const std::string& local_id = params->entry().local_id();
495 const std::string& md5 = params->entry().file_specific_info().md5();
496 base::FilePath* cache_file_path = new base::FilePath;
497 base::PostTaskAndReplyWithResult(
498 blocking_task_runner_.get(),
500 base::Bind(&UpdateLocalStateForDownloadFile,
501 base::Unretained(cache_),
505 downloaded_file_path,
507 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState,
508 weak_ptr_factory_.GetWeakPtr(),
510 base::Passed(¶ms),
511 base::Owned(cache_file_path)));
514 void DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState(
515 const base::FilePath& file_path,
516 scoped_ptr<DownloadParams> params,
517 base::FilePath* cache_file_path,
519 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
521 if (error != FILE_ERROR_OK) {
522 params->OnError(error);
526 // Storing to cache changes the "offline available" status, hence notify.
527 observer_->OnDirectoryChangedByOperation(file_path.DirName());
528 params->OnComplete(*cache_file_path);
531 void DownloadOperation::CancelJob(JobID job_id) {
532 scheduler_->CancelJob(job_id);
535 } // namespace file_system