OLD | NEW |
---|---|
(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 ¶ms->drive_file_path, | |
384 ¶ms->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(¶ms->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 | |
OLD | NEW |