Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / chromeos / drive / file_system / download_operation.cc
blob5257e824bc3d73127a74d40b6217b6a97676766d
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;
24 namespace drive {
25 namespace file_system {
26 namespace {
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
33 // cache file.
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,
42 ResourceEntry* entry,
43 base::FilePath* cache_file_path) {
44 DCHECK(metadata);
45 DCHECK(cache);
46 DCHECK(cache_file_path);
48 FileError error = metadata->GetResourceEntryById(local_id, entry);
49 if (error != FILE_ERROR_OK)
50 return error;
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
61 // document.
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,
67 &gdoc_file_path) ||
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);
77 return FILE_ERROR_OK;
80 // Leave |cache_file_path| empty when no cache entry is found.
81 FileCacheEntry cache_entry;
82 if (!cache->GetCacheEntry(local_id, &cache_entry))
83 return FILE_ERROR_OK;
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())
89 return FILE_ERROR_OK;
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)
94 return error;
96 // If the cache file is dirty, the modified file info needs to be stored in
97 // |entry|.
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,
124 cache_file_path);
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)
139 return error;
140 return CheckPreConditionForEnsureFileDownloaded(
141 metadata, cache, temporary_file_directory, local_id, entry,
142 cache_file_path);
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))
153 return false;
154 return base::SetPosixFilePermissions(
155 *temp_file,
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
163 // in the cache.
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) {
170 DCHECK(cache);
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) {
194 DCHECK(cache);
196 FileError error = GDataToFileError(gdata_error);
197 if (error != FILE_ERROR_OK) {
198 base::DeleteFile(downloaded_file_path, false /* recursive */);
199 return error;
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 */);
207 return error;
210 return cache->GetFile(local_id, cache_file_path);
213 } // namespace
215 class DownloadOperation::DownloadParams {
216 public:
217 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());
227 DCHECK(entry_);
230 void OnCacheFileFound(const base::FilePath& cache_file_path) const {
231 if (initialized_callback_.is_null())
232 return;
234 DCHECK(entry_);
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()) {
242 return;
245 DCHECK(entry_);
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_; }
266 private:
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),
284 observer_(observer),
285 scheduler_(scheduler),
286 metadata_(metadata),
287 cache_(cache),
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(),
312 FROM_HERE,
313 base::Bind(&CheckPreConditionForEnsureFileDownloadedByLocalId,
314 base::Unretained(metadata_),
315 base::Unretained(cache_),
316 local_id,
317 temporary_file_directory_,
318 drive_file_path,
319 cache_file_path,
320 entry),
321 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
322 weak_ptr_factory_.GetWeakPtr(),
323 base::Passed(&params),
324 context,
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(),
346 FROM_HERE,
347 base::Bind(&CheckPreConditionForEnsureFileDownloadedByPath,
348 base::Unretained(metadata_),
349 base::Unretained(cache_),
350 file_path,
351 temporary_file_directory_,
352 cache_file_path,
353 entry),
354 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
355 weak_ptr_factory_.GetWeakPtr(),
356 base::Passed(&params),
357 context,
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,
367 FileError error) {
368 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
369 DCHECK(params);
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);
376 return;
379 if (!cache_file_path->empty()) {
380 // The cache file is found.
381 params->OnCacheFileFound(*cache_file_path);
382 params->OnComplete(*cache_file_path);
383 return;
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(),
395 FROM_HERE,
396 base::Bind(&PrepareForDownloadFile,
397 base::Unretained(cache_),
398 size,
399 temporary_file_directory_,
400 temp_download_file_path),
401 base::Bind(
402 &DownloadOperation::EnsureFileDownloadedAfterPrepareForDownloadFile,
403 weak_ptr_factory_.GetWeakPtr(),
404 base::Passed(&params),
405 context,
406 *drive_file_path,
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,
415 FileError error) {
416 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
417 DCHECK(params);
418 DCHECK(temp_download_file_path);
420 if (error != FILE_ERROR_OK) {
421 params->OnError(error);
422 return;
425 DownloadParams* params_ptr = params.get();
426 JobID id = scheduler_->DownloadFile(
427 drive_file_path,
428 params_ptr->entry().file_info().size(),
429 *temp_download_file_path,
430 params_ptr->entry().resource_id(),
431 context,
432 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile,
433 weak_ptr_factory_.GetWeakPtr(),
434 drive_file_path,
435 base::Passed(&params)),
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(),
456 FROM_HERE,
457 base::Bind(&UpdateLocalStateForDownloadFile,
458 base::Unretained(cache_),
459 local_id,
460 md5,
461 gdata_error,
462 downloaded_file_path,
463 cache_file_path),
464 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState,
465 weak_ptr_factory_.GetWeakPtr(),
466 drive_file_path,
467 base::Passed(&params),
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,
475 FileError error) {
476 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
478 if (error != FILE_ERROR_OK) {
479 params->OnError(error);
480 return;
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
493 } // namespace drive