Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1018)

Side by Side Diff: chrome/browser/chromeos/drive/file_system/download_operation.cc

Issue 15681009: Extract GetResolveFile into DownloadOperation. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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.
4
5 #include "chrome/browser/chromeos/drive/file_system/download_operation.h"
6
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 "chrome/browser/google_apis/gdata_errorcode.h"
20 #include "content/public/browser/browser_thread.h"
21
22 using content::BrowserThread;
23
24 namespace drive {
25 namespace file_system {
26 namespace {
27
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& file_path,
41 base::FilePath* cache_file_path,
42 ResourceEntry* entry) {
43 DCHECK(metadata);
44 DCHECK(cache);
45 DCHECK(cache_file_path);
46 DCHECK(entry);
47
48 FileError error = metadata->GetResourceEntryByPath(file_path, entry);
49 if (error != FILE_ERROR_OK)
50 return error;
51
52 if (entry->file_info().is_directory())
53 return FILE_ERROR_NOT_A_FILE;
54
55 // The file's entry should have its file specific info.
56 DCHECK(entry->has_file_specific_info());
57
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 if (!file_util::CreateTemporaryFileInDir(
65 cache->GetCacheDirectoryPath(
66 internal::FileCache::CACHE_TYPE_TMP_DOCUMENTS),
67 &gdoc_file_path) ||
68 !util::CreateGDocFile(gdoc_file_path,
69 GURL(entry->file_specific_info().alternate_url()),
70 entry->resource_id()))
71 return FILE_ERROR_FAILED;
72
73 *cache_file_path = gdoc_file_path;
74 return FILE_ERROR_OK;
75 }
76
77 // Look up if there exists the cache file.
78 FileError cache_error = cache->GetFile(
79 entry->resource_id(),
80 entry->file_specific_info().file_md5(),
81 cache_file_path);
82 DCHECK((cache_error == FILE_ERROR_OK && !cache_file_path->empty()) ||
83 (cache_error == FILE_ERROR_NOT_FOUND && cache_file_path->empty()));
84
85 return error;
86 }
87
88 // Creates a file with unique name in |dir| and stores the path to |temp_file|.
89 // Additionally, sets the permission of the file to allow read access from
90 // others and group member users (i.e, "-rw-r--r--").
91 // We need this wrapper because Drive cache files may be read from other
92 // processes (e.g., cros_disks for mounting zip files).
93 bool CreateTemporaryReadableFileInDir(const base::FilePath& dir,
94 base::FilePath* temp_file) {
95 if (!file_util::CreateTemporaryFileInDir(dir, temp_file))
96 return false;
97 return file_util::SetPosixFilePermissions(
98 *temp_file,
99 file_util::FILE_PERMISSION_READ_BY_USER |
100 file_util::FILE_PERMISSION_WRITE_BY_USER |
101 file_util::FILE_PERMISSION_READ_BY_GROUP |
102 file_util::FILE_PERMISSION_READ_BY_OTHERS);
103 }
104
105 // Allocates the enough space in the cache. If succeeded, returns FILE_ERROR_OK
106 // with |entry| storing the ResourceEntry of the resource, |drive_file_path|
107 // with storing the path of the entry, and |temp_download_file| storing the
108 // path to the file in the cache.
109 FileError AllocateCacheSpace(
hashimoto 2013/05/24 09:48:38 nit: What this function does is more than just all
hidehiko 2013/05/26 15:39:36 Done.
110 internal::ResourceMetadata* metadata,
111 internal::FileCache* cache,
112 scoped_ptr<google_apis::ResourceEntry> gdata_entry,
113 ResourceEntry* entry,
114 base::FilePath* drive_file_path,
115 base::FilePath* temp_download_file) {
116 DCHECK(metadata);
117 DCHECK(cache);
118 DCHECK(gdata_entry);
119 DCHECK(entry);
120 DCHECK(drive_file_path);
121 DCHECK(temp_download_file);
122
123 FileError error = metadata->RefreshEntry(
124 ConvertToResourceEntry(*gdata_entry), drive_file_path, entry);
125 if (error != FILE_ERROR_OK)
126 return error;
127
128 // Ensure enough space in the cache.
129 if (!cache->FreeDiskSpaceIfNeededFor(entry->file_info().size()))
130 return FILE_ERROR_NO_SPACE;
131
132 // Create the temporary file which will store the donwloaded content.
133 return CreateTemporaryReadableFileInDir(
134 cache->GetCacheDirectoryPath(
135 internal::FileCache::CACHE_TYPE_TMP_DOWNLOADS),
136 temp_download_file) ?
137 FILE_ERROR_OK : FILE_ERROR_FAILED;
138 }
139
140 // Stores the downloaded file at |downloaded_file_path| into |cache|.
141 // If succeeded, returns FILE_ERROR_OK with |cache_file_path| storing the
142 // path to the cache file.
143 // If failed, returns an error code with deleting |downloaded_file_path|.
144 FileError UpdateLocalStateForDownloadFile(
145 internal::FileCache* cache,
146 const std::string& resource_id,
147 const std::string& md5,
148 google_apis::GDataErrorCode gdata_error,
149 const base::FilePath& downloaded_file_path,
150 base::FilePath* cache_file_path) {
151 DCHECK(cache);
152
153 // If user cancels download of a pinned-but-not-fetched file, mark file as
154 // unpinned so that we do not sync the file again.
155 if (gdata_error == google_apis::GDATA_CANCELLED) {
156 FileCacheEntry cache_entry;
157 if (cache->GetCacheEntry(resource_id, md5, &cache_entry) &&
158 cache_entry.is_pinned()) {
159 // TODO(hshi): http://crbug.com/127138 notify when file properties change.
160 // This allows file manager to clear the "Available offline" checkbox.
161 cache->Unpin(resource_id, md5);
162 }
163 }
164
165 FileError error = util::GDataToFileError(gdata_error);
166 if (error != FILE_ERROR_OK) {
167 file_util::Delete(downloaded_file_path, false /* recursive */);
168 return error;
169 }
170
171 // Here the download is completed successfully, so store it into the cache.
172 error = cache->Store(resource_id, md5, downloaded_file_path,
173 internal::FileCache::FILE_OPERATION_MOVE);
174 if (error != FILE_ERROR_OK) {
175 file_util::Delete(downloaded_file_path, false /* recursive */);
176 return error;
177 }
178
179 return cache->GetFile(resource_id, md5, cache_file_path);
180 }
181
182 } // namespace
183
184 class DownloadOperation::DownloadCallback {
185 public:
186 DownloadCallback(
187 const GetFileContentInitializedCallback initialized_callback,
188 const google_apis::GetContentCallback get_content_callback,
189 const GetFileCallback completion_callback)
190 : initialized_callback_(initialized_callback),
191 get_content_callback_(get_content_callback),
192 completion_callback_(completion_callback) {
193 DCHECK(!completion_callback_.is_null());
194 }
195
196 void OnCacheFileFound(const ResourceEntry& entry,
197 const base::FilePath& cache_file_path) const {
198 if (initialized_callback_.is_null())
199 return;
200
201 initialized_callback_.Run(
202 FILE_ERROR_OK, make_scoped_ptr(new ResourceEntry(entry)),
203 cache_file_path, base::Closure());
204 }
205
206 void OnStartDownloading(const ResourceEntry& entry,
207 const base::Closure& cancel_download_closure) const {
208 if (initialized_callback_.is_null()) {
209 return;
210 }
211
212 initialized_callback_.Run(
213 FILE_ERROR_OK, make_scoped_ptr(new ResourceEntry(entry)),
214 base::FilePath(), cancel_download_closure);
215 }
216
217 void OnError(FileError error) const {
218 completion_callback_.Run(
219 error, base::FilePath(), scoped_ptr<ResourceEntry>());
220 }
221
222 void OnComplete(const base::FilePath& cache_file_path,
223 scoped_ptr<ResourceEntry> entry) const {
224 completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry.Pass());
225 }
226
227 const google_apis::GetContentCallback& get_content_callback() const {
228 return get_content_callback_;
229 }
230
231 private:
232 const GetFileContentInitializedCallback initialized_callback_;
233 const google_apis::GetContentCallback get_content_callback_;
234 const GetFileCallback completion_callback_;
235
236 // This class is copiable.
237 };
238
239 struct DownloadOperation::DownloadParams {
240 DownloadParams(const DriveClientContext& context,
241 const GURL& download_url)
242 : context(context),
243 download_url(download_url),
244 entry(new ResourceEntry) {
245 }
246
247 DriveClientContext context;
248 GURL download_url;
249 scoped_ptr<ResourceEntry> entry;
250 base::FilePath drive_file_path;
251 base::FilePath temp_download_file_path;
252 };
253
254 DownloadOperation::DownloadOperation(
255 base::SequencedTaskRunner* blocking_task_runner,
256 OperationObserver* observer,
257 JobScheduler* scheduler,
258 internal::ResourceMetadata* metadata,
259 internal::FileCache* cache)
260 : blocking_task_runner_(blocking_task_runner),
261 observer_(observer),
262 scheduler_(scheduler),
263 metadata_(metadata),
264 cache_(cache),
265 weak_ptr_factory_(this) {
266 }
267
268 DownloadOperation::~DownloadOperation() {
269 }
270
271 void DownloadOperation::EnsureFileDownloaded(
272 const base::FilePath& file_path,
273 DriveClientContext context,
274 const GetFileContentInitializedCallback& initialized_callback,
275 const google_apis::GetContentCallback& get_content_callback,
276 const GetFileCallback& completion_callback) {
277 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
278 DCHECK(!completion_callback.is_null());
279
280 DownloadCallback callback(
281 initialized_callback, get_content_callback, completion_callback);
282
283 ResourceEntry* entry = new ResourceEntry;
284 base::FilePath* cache_file_path = new base::FilePath;
285 base::PostTaskAndReplyWithResult(
286 blocking_task_runner_,
287 FROM_HERE,
288 base::Bind(&CheckPreConditionForEnsureFileDownloaded,
289 base::Unretained(metadata_),
290 base::Unretained(cache_),
291 file_path,
292 cache_file_path,
293 entry),
294 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
295 weak_ptr_factory_.GetWeakPtr(),
296 file_path,
297 context,
298 callback,
299 base::Passed(make_scoped_ptr(entry)),
300 base::Owned(cache_file_path)));
301 }
302
303 void DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition(
304 const base::FilePath& file_path,
305 DriveClientContext context,
306 const DownloadCallback& callback,
307 scoped_ptr<ResourceEntry> entry,
308 base::FilePath* cache_file_path,
309 FileError error) {
310 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
311 DCHECK(entry);
312 DCHECK(cache_file_path);
313
314 if (error != FILE_ERROR_OK) {
315 // During precondition check, an error is found.
316 callback.OnError(error);
317 return;
318 }
319
320 if (!cache_file_path->empty()) {
321 // The cache file is found.
322 callback.OnCacheFileFound(*entry, *cache_file_path);
323 callback.OnComplete(*cache_file_path, entry.Pass());
324 return;
325 }
326
327 // If cache file is not found, try to download the file from the server
328 // instead. This logic is rather complicated but here's how this works:
329 //
330 // Retrieve fresh file metadata from server. We will extract file size and
331 // download url from there. Note that the download url is transient.
332 //
333 // Check if we have enough space, based on the expected file size.
334 // - if we don't have enough space, try to free up the disk space
335 // - if we still don't have enough space, return "no space" error
336 // - if we have enough space, start downloading the file from the server
337 scheduler_->GetResourceEntry(
338 entry->resource_id(),
339 context,
340 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterGetResourceEntry,
341 weak_ptr_factory_.GetWeakPtr(),
342 context,
343 callback));
344 }
345
346 void DownloadOperation::EnsureFileDownloadedAfterGetResourceEntry(
347 DriveClientContext context,
348 const DownloadCallback& callback,
349 google_apis::GDataErrorCode gdata_error,
350 scoped_ptr<google_apis::ResourceEntry> resource_entry) {
351 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
352
353 FileError error = util::GDataToFileError(gdata_error);
354 if (error != FILE_ERROR_OK) {
355 callback.OnError(error);
356 return;
357 }
358 DCHECK(resource_entry);
359
360 // The download URL is:
361 // 1) src attribute of content element, on GData WAPI.
362 // 2) the value of the key 'downloadUrl', on Drive API v2.
363 // In both cases, we can use ResourceEntry::download_url().
364 const GURL& download_url = resource_entry->download_url();
365
366 // The download URL can be empty for non-downloadable files (such as files
367 // shared from others with "prevent downloading by viewers" flag set.)
368 if (download_url.is_empty()) {
369 callback.OnError(FILE_ERROR_ACCESS_DENIED);
370 return;
371 }
372
373 // Before starting to download actually, allocate the cache space.
374 DownloadParams* params = new DownloadParams(context, download_url);
375 base::PostTaskAndReplyWithResult(
376 blocking_task_runner_,
377 FROM_HERE,
378 base::Bind(&AllocateCacheSpace,
379 base::Unretained(metadata_),
380 base::Unretained(cache_),
381 base::Passed(&resource_entry),
382 params->entry.get(),
383 &params->drive_file_path,
384 &params->temp_download_file_path),
385 base::Bind(
386 &DownloadOperation::EnsureFileDownloadedAfterAllocateCacheSpace,
387 weak_ptr_factory_.GetWeakPtr(),
388 base::Owned(params),
389 callback));
390 }
391
392 void DownloadOperation::EnsureFileDownloadedAfterAllocateCacheSpace(
393 DownloadParams* params,
394 const DownloadCallback& callback,
395 FileError error) {
396 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
397 DCHECK(params);
398
399 if (error != FILE_ERROR_OK) {
400 callback.OnError(error);
401 return;
402 }
403
404 ResourceEntry* entry_ptr = params->entry.get();
405 JobID id = scheduler_->DownloadFile(
406 params->drive_file_path,
407 params->temp_download_file_path,
408 params->download_url,
409 params->context,
410 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile,
411 weak_ptr_factory_.GetWeakPtr(),
412 params->drive_file_path,
413 base::Passed(&params->entry),
414 callback),
415 callback.get_content_callback());
416
417 // Notify via |initialized_callback| if necessary.
418 callback.OnStartDownloading(
419 *entry_ptr,
420 base::Bind(&DownloadOperation::CancelJob,
421 weak_ptr_factory_.GetWeakPtr(), id));
422 }
423
424 void DownloadOperation::EnsureFileDownloadedAfterDownloadFile(
425 const base::FilePath& drive_file_path,
426 scoped_ptr<ResourceEntry> entry,
427 const DownloadCallback& callback,
428 google_apis::GDataErrorCode gdata_error,
429 const base::FilePath& downloaded_file_path) {
430 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
431
432 ResourceEntry* entry_ptr = entry.get();
433 base::FilePath* cache_file_path = new base::FilePath;
434 base::PostTaskAndReplyWithResult(
435 blocking_task_runner_,
436 FROM_HERE,
437 base::Bind(&UpdateLocalStateForDownloadFile,
438 base::Unretained(cache_),
439 entry_ptr->resource_id(),
440 entry_ptr->file_specific_info().file_md5(),
441 gdata_error,
442 downloaded_file_path,
443 cache_file_path),
444 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState,
445 weak_ptr_factory_.GetWeakPtr(),
446 drive_file_path,
447 callback,
448 base::Passed(&entry),
449 base::Owned(cache_file_path)));
450 }
451
452 void DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState(
453 const base::FilePath& file_path,
454 const DownloadCallback& callback,
455 scoped_ptr<ResourceEntry> entry,
456 base::FilePath* cache_file_path,
457 FileError error) {
458 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
459
460 if (error != FILE_ERROR_OK) {
461 callback.OnError(error);
462 return;
463 }
464
465 // Storing to cache changes the "offline available" status, hence notify.
466 observer_->OnDirectoryChangedByOperation(file_path.DirName());
467 callback.OnComplete(*cache_file_path, entry.Pass());
468 }
469
470 void DownloadOperation::CancelJob(JobID job_id) {
471 scheduler_->CancelJob(job_id);
472 }
473
474 } // namespace file_system
475 } // namespace drive
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698