Chromium Code Reviews| 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 |