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

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: Rebase Created 7 years, 6 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 // Prepares for downloading the file. Given the |gdata_entry|, refreshes the
106 // |metadata| and then allocates the enough space in the cache.
107 // If succeeded, returns FILE_ERROR_OK with |entry| storing the ResourceEntry
108 // of the resource, |drive_file_path| with storing the path of the entry,
109 // and |temp_download_file| storing the path to the file in the cache.
110 FileError PrepareForDownloadFile(
111 internal::ResourceMetadata* metadata,
112 internal::FileCache* cache,
113 scoped_ptr<google_apis::ResourceEntry> gdata_entry,
114 ResourceEntry* entry,
115 base::FilePath* drive_file_path,
116 base::FilePath* temp_download_file) {
117 DCHECK(metadata);
118 DCHECK(cache);
119 DCHECK(gdata_entry);
120 DCHECK(entry);
121 DCHECK(drive_file_path);
122 DCHECK(temp_download_file);
123
124 FileError error = metadata->RefreshEntry(
125 ConvertToResourceEntry(*gdata_entry), drive_file_path, entry);
126 if (error != FILE_ERROR_OK)
127 return error;
128
129 // Ensure enough space in the cache.
130 if (!cache->FreeDiskSpaceIfNeededFor(entry->file_info().size()))
131 return FILE_ERROR_NO_SPACE;
132
133 // Create the temporary file which will store the donwloaded content.
134 return CreateTemporaryReadableFileInDir(
135 cache->GetCacheDirectoryPath(
136 internal::FileCache::CACHE_TYPE_TMP_DOWNLOADS),
137 temp_download_file) ?
138 FILE_ERROR_OK : FILE_ERROR_FAILED;
139 }
140
141 // Stores the downloaded file at |downloaded_file_path| into |cache|.
142 // If succeeded, returns FILE_ERROR_OK with |cache_file_path| storing the
143 // path to the cache file.
144 // If failed, returns an error code with deleting |downloaded_file_path|.
145 FileError UpdateLocalStateForDownloadFile(
146 internal::FileCache* cache,
147 const std::string& resource_id,
148 const std::string& md5,
149 google_apis::GDataErrorCode gdata_error,
150 const base::FilePath& downloaded_file_path,
151 base::FilePath* cache_file_path) {
152 DCHECK(cache);
153
154 // If user cancels download of a pinned-but-not-fetched file, mark file as
155 // unpinned so that we do not sync the file again.
156 if (gdata_error == google_apis::GDATA_CANCELLED) {
157 FileCacheEntry cache_entry;
158 if (cache->GetCacheEntry(resource_id, md5, &cache_entry) &&
159 cache_entry.is_pinned()) {
160 // TODO(hshi): http://crbug.com/127138 notify when file properties change.
161 // This allows file manager to clear the "Available offline" checkbox.
162 cache->Unpin(resource_id, md5);
163 }
164 }
165
166 FileError error = util::GDataToFileError(gdata_error);
167 if (error != FILE_ERROR_OK) {
168 file_util::Delete(downloaded_file_path, false /* recursive */);
169 return error;
170 }
171
172 // Here the download is completed successfully, so store it into the cache.
173 error = cache->Store(resource_id, md5, downloaded_file_path,
174 internal::FileCache::FILE_OPERATION_MOVE);
175 if (error != FILE_ERROR_OK) {
176 file_util::Delete(downloaded_file_path, false /* recursive */);
177 return error;
178 }
179
180 return cache->GetFile(resource_id, md5, cache_file_path);
181 }
182
183 } // namespace
184
185 class DownloadOperation::DownloadCallback {
186 public:
187 DownloadCallback(
188 const GetFileContentInitializedCallback initialized_callback,
189 const google_apis::GetContentCallback get_content_callback,
190 const GetFileCallback completion_callback)
191 : initialized_callback_(initialized_callback),
192 get_content_callback_(get_content_callback),
193 completion_callback_(completion_callback) {
194 DCHECK(!completion_callback_.is_null());
195 }
196
197 void OnCacheFileFound(const ResourceEntry& entry,
198 const base::FilePath& cache_file_path) const {
199 if (initialized_callback_.is_null())
200 return;
201
202 initialized_callback_.Run(
203 FILE_ERROR_OK, make_scoped_ptr(new ResourceEntry(entry)),
204 cache_file_path, base::Closure());
205 }
206
207 void OnStartDownloading(const ResourceEntry& entry,
208 const base::Closure& cancel_download_closure) const {
209 if (initialized_callback_.is_null()) {
210 return;
211 }
212
213 initialized_callback_.Run(
214 FILE_ERROR_OK, make_scoped_ptr(new ResourceEntry(entry)),
215 base::FilePath(), cancel_download_closure);
216 }
217
218 void OnError(FileError error) const {
219 completion_callback_.Run(
220 error, base::FilePath(), scoped_ptr<ResourceEntry>());
221 }
222
223 void OnComplete(const base::FilePath& cache_file_path,
224 scoped_ptr<ResourceEntry> entry) const {
225 completion_callback_.Run(FILE_ERROR_OK, cache_file_path, entry.Pass());
226 }
227
228 const google_apis::GetContentCallback& get_content_callback() const {
229 return get_content_callback_;
230 }
231
232 private:
233 const GetFileContentInitializedCallback initialized_callback_;
234 const google_apis::GetContentCallback get_content_callback_;
235 const GetFileCallback completion_callback_;
236
237 // This class is copiable.
238 };
239
240 struct DownloadOperation::DownloadParams {
241 DownloadParams(const DriveClientContext& context,
242 const GURL& download_url)
243 : context(context),
244 download_url(download_url),
245 entry(new ResourceEntry) {
246 }
247
248 DriveClientContext context;
249 GURL download_url;
250 scoped_ptr<ResourceEntry> entry;
251 base::FilePath drive_file_path;
252 base::FilePath temp_download_file_path;
253 };
254
255 DownloadOperation::DownloadOperation(
256 base::SequencedTaskRunner* blocking_task_runner,
257 OperationObserver* observer,
258 JobScheduler* scheduler,
259 internal::ResourceMetadata* metadata,
260 internal::FileCache* cache)
261 : blocking_task_runner_(blocking_task_runner),
262 observer_(observer),
263 scheduler_(scheduler),
264 metadata_(metadata),
265 cache_(cache),
266 weak_ptr_factory_(this) {
267 }
268
269 DownloadOperation::~DownloadOperation() {
270 }
271
272 void DownloadOperation::EnsureFileDownloaded(
273 const base::FilePath& file_path,
274 DriveClientContext context,
275 const GetFileContentInitializedCallback& initialized_callback,
276 const google_apis::GetContentCallback& get_content_callback,
277 const GetFileCallback& completion_callback) {
278 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
279 DCHECK(!completion_callback.is_null());
280
281 DownloadCallback callback(
282 initialized_callback, get_content_callback, completion_callback);
283
284 ResourceEntry* entry = new ResourceEntry;
285 base::FilePath* cache_file_path = new base::FilePath;
286 base::PostTaskAndReplyWithResult(
287 blocking_task_runner_,
288 FROM_HERE,
289 base::Bind(&CheckPreConditionForEnsureFileDownloaded,
290 base::Unretained(metadata_),
291 base::Unretained(cache_),
292 file_path,
293 cache_file_path,
294 entry),
295 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition,
296 weak_ptr_factory_.GetWeakPtr(),
297 file_path,
298 context,
299 callback,
300 base::Passed(make_scoped_ptr(entry)),
301 base::Owned(cache_file_path)));
302 }
303
304 void DownloadOperation::EnsureFileDownloadedAfterCheckPreCondition(
305 const base::FilePath& file_path,
306 DriveClientContext context,
307 const DownloadCallback& callback,
308 scoped_ptr<ResourceEntry> entry,
309 base::FilePath* cache_file_path,
310 FileError error) {
311 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
312 DCHECK(entry);
313 DCHECK(cache_file_path);
314
315 if (error != FILE_ERROR_OK) {
316 // During precondition check, an error is found.
317 callback.OnError(error);
318 return;
319 }
320
321 if (!cache_file_path->empty()) {
322 // The cache file is found.
323 callback.OnCacheFileFound(*entry, *cache_file_path);
324 callback.OnComplete(*cache_file_path, entry.Pass());
325 return;
326 }
327
328 // If cache file is not found, try to download the file from the server
329 // instead. This logic is rather complicated but here's how this works:
330 //
331 // Retrieve fresh file metadata from server. We will extract file size and
332 // download url from there. Note that the download url is transient.
333 //
334 // Check if we have enough space, based on the expected file size.
335 // - if we don't have enough space, try to free up the disk space
336 // - if we still don't have enough space, return "no space" error
337 // - if we have enough space, start downloading the file from the server
338 scheduler_->GetResourceEntry(
339 entry->resource_id(),
340 context,
341 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterGetResourceEntry,
342 weak_ptr_factory_.GetWeakPtr(),
343 context,
344 callback));
345 }
346
347 void DownloadOperation::EnsureFileDownloadedAfterGetResourceEntry(
348 DriveClientContext context,
349 const DownloadCallback& callback,
350 google_apis::GDataErrorCode gdata_error,
351 scoped_ptr<google_apis::ResourceEntry> resource_entry) {
352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
353
354 FileError error = util::GDataToFileError(gdata_error);
355 if (error != FILE_ERROR_OK) {
356 callback.OnError(error);
357 return;
358 }
359 DCHECK(resource_entry);
360
361 // The download URL is:
362 // 1) src attribute of content element, on GData WAPI.
363 // 2) the value of the key 'downloadUrl', on Drive API v2.
364 // In both cases, we can use ResourceEntry::download_url().
365 const GURL& download_url = resource_entry->download_url();
366
367 // The download URL can be empty for non-downloadable files (such as files
368 // shared from others with "prevent downloading by viewers" flag set.)
369 if (download_url.is_empty()) {
370 callback.OnError(FILE_ERROR_ACCESS_DENIED);
371 return;
372 }
373
374 // Before starting to download actually, refresh the metadata and allocate
375 // the cache space.
376 DownloadParams* params = new DownloadParams(context, download_url);
377 base::PostTaskAndReplyWithResult(
378 blocking_task_runner_,
379 FROM_HERE,
380 base::Bind(&PrepareForDownloadFile,
381 base::Unretained(metadata_),
382 base::Unretained(cache_),
383 base::Passed(&resource_entry),
384 params->entry.get(),
385 &params->drive_file_path,
386 &params->temp_download_file_path),
387 base::Bind(
388 &DownloadOperation::EnsureFileDownloadedAfterPrepareForDownloadFile,
389 weak_ptr_factory_.GetWeakPtr(),
390 base::Owned(params),
391 callback));
392 }
393
394 void DownloadOperation::EnsureFileDownloadedAfterPrepareForDownloadFile(
395 DownloadParams* params,
396 const DownloadCallback& callback,
397 FileError error) {
398 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
399 DCHECK(params);
400
401 if (error != FILE_ERROR_OK) {
402 callback.OnError(error);
403 return;
404 }
405
406 ResourceEntry* entry_ptr = params->entry.get();
407 JobID id = scheduler_->DownloadFile(
408 params->drive_file_path,
409 params->temp_download_file_path,
410 params->download_url,
411 params->context,
412 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterDownloadFile,
413 weak_ptr_factory_.GetWeakPtr(),
414 params->drive_file_path,
415 base::Passed(&params->entry),
416 callback),
417 callback.get_content_callback());
418
419 // Notify via |initialized_callback| if necessary.
420 callback.OnStartDownloading(
421 *entry_ptr,
422 base::Bind(&DownloadOperation::CancelJob,
423 weak_ptr_factory_.GetWeakPtr(), id));
424 }
425
426 void DownloadOperation::EnsureFileDownloadedAfterDownloadFile(
427 const base::FilePath& drive_file_path,
428 scoped_ptr<ResourceEntry> entry,
429 const DownloadCallback& callback,
430 google_apis::GDataErrorCode gdata_error,
431 const base::FilePath& downloaded_file_path) {
432 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
433
434 ResourceEntry* entry_ptr = entry.get();
435 base::FilePath* cache_file_path = new base::FilePath;
436 base::PostTaskAndReplyWithResult(
437 blocking_task_runner_,
438 FROM_HERE,
439 base::Bind(&UpdateLocalStateForDownloadFile,
440 base::Unretained(cache_),
441 entry_ptr->resource_id(),
442 entry_ptr->file_specific_info().file_md5(),
443 gdata_error,
444 downloaded_file_path,
445 cache_file_path),
446 base::Bind(&DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState,
447 weak_ptr_factory_.GetWeakPtr(),
448 drive_file_path,
449 callback,
450 base::Passed(&entry),
451 base::Owned(cache_file_path)));
452 }
453
454 void DownloadOperation::EnsureFileDownloadedAfterUpdateLocalState(
455 const base::FilePath& file_path,
456 const DownloadCallback& callback,
457 scoped_ptr<ResourceEntry> entry,
458 base::FilePath* cache_file_path,
459 FileError error) {
460 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
461
462 if (error != FILE_ERROR_OK) {
463 callback.OnError(error);
464 return;
465 }
466
467 // Storing to cache changes the "offline available" status, hence notify.
468 observer_->OnDirectoryChangedByOperation(file_path.DirName());
469 callback.OnComplete(*cache_file_path, entry.Pass());
470 }
471
472 void DownloadOperation::CancelJob(JobID job_id) {
473 scheduler_->CancelJob(job_id);
474 }
475
476 } // namespace file_system
477 } // namespace drive
OLDNEW
« no previous file with comments | « chrome/browser/chromeos/drive/file_system/download_operation.h ('k') | chrome/browser/chromeos/drive/file_system/operations.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698