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