Home | History | Annotate | Download | only in drive
      1 // Copyright (c) 2012 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/download_handler.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/file_util.h"
      9 #include "base/supports_user_data.h"
     10 #include "base/threading/sequenced_worker_pool.h"
     11 #include "chrome/browser/chromeos/drive/drive.pb.h"
     12 #include "chrome/browser/chromeos/drive/drive_integration_service.h"
     13 #include "chrome/browser/chromeos/drive/file_system_interface.h"
     14 #include "chrome/browser/chromeos/drive/file_system_util.h"
     15 #include "chrome/browser/chromeos/drive/file_write_helper.h"
     16 #include "content/public/browser/browser_thread.h"
     17 
     18 using content::BrowserThread;
     19 using content::DownloadManager;
     20 using content::DownloadItem;
     21 
     22 namespace drive {
     23 namespace {
     24 
     25 // Key for base::SupportsUserData::Data.
     26 const char kDrivePathKey[] = "DrivePath";
     27 
     28 // User Data stored in DownloadItem for drive path.
     29 class DriveUserData : public base::SupportsUserData::Data {
     30  public:
     31   explicit DriveUserData(const base::FilePath& path) : file_path_(path),
     32                                                        is_complete_(false) {}
     33   virtual ~DriveUserData() {}
     34 
     35   const base::FilePath& file_path() const { return file_path_; }
     36   bool is_complete() const { return is_complete_; }
     37   void set_complete() { is_complete_ = true; }
     38 
     39  private:
     40   const base::FilePath file_path_;
     41   bool is_complete_;
     42 };
     43 
     44 // Extracts DriveUserData* from |download|.
     45 const DriveUserData* GetDriveUserData(const DownloadItem* download) {
     46   return static_cast<const DriveUserData*>(
     47       download->GetUserData(&kDrivePathKey));
     48 }
     49 
     50 DriveUserData* GetDriveUserData(DownloadItem* download) {
     51   return static_cast<DriveUserData*>(download->GetUserData(&kDrivePathKey));
     52 }
     53 
     54 // Creates a temporary file |drive_tmp_download_path| in
     55 // |drive_tmp_download_dir|. Must be called on a thread that allows file
     56 // operations.
     57 base::FilePath GetDriveTempDownloadPath(
     58     const base::FilePath& drive_tmp_download_dir) {
     59   bool created = file_util::CreateDirectory(drive_tmp_download_dir);
     60   DCHECK(created) << "Can not create temp download directory at "
     61                   << drive_tmp_download_dir.value();
     62   base::FilePath drive_tmp_download_path;
     63   created = file_util::CreateTemporaryFileInDir(drive_tmp_download_dir,
     64                                                 &drive_tmp_download_path);
     65   DCHECK(created) << "Temporary download file creation failed";
     66   return drive_tmp_download_path;
     67 }
     68 
     69 // Moves downloaded file to Drive.
     70 void MoveDownloadedFile(const base::FilePath& downloaded_file,
     71                         FileError error,
     72                         const base::FilePath& dest_path) {
     73   if (error != FILE_ERROR_OK)
     74     return;
     75   base::Move(downloaded_file, dest_path);
     76 }
     77 
     78 // Used to implement CheckForFileExistence().
     79 void ContinueCheckingForFileExistence(
     80     const content::CheckForFileExistenceCallback& callback,
     81     FileError error,
     82     scoped_ptr<ResourceEntry> entry) {
     83   callback.Run(error == FILE_ERROR_OK);
     84 }
     85 
     86 // Returns true if |download| is a Drive download created from data persisted
     87 // on the download history DB.
     88 bool IsPersistedDriveDownload(const base::FilePath& drive_tmp_download_path,
     89                               DownloadItem* download) {
     90   // Persisted downloads are not in IN_PROGRESS state when created, while newly
     91   // created downloads are.
     92   return drive_tmp_download_path.IsParent(download->GetTargetFilePath()) &&
     93       download->GetState() != DownloadItem::IN_PROGRESS;
     94 }
     95 
     96 }  // namespace
     97 
     98 DownloadHandler::DownloadHandler(
     99     FileWriteHelper* file_write_helper,
    100     FileSystemInterface* file_system)
    101     : file_write_helper_(file_write_helper),
    102       file_system_(file_system),
    103       weak_ptr_factory_(this) {
    104 }
    105 
    106 DownloadHandler::~DownloadHandler() {
    107 }
    108 
    109 // static
    110 DownloadHandler* DownloadHandler::GetForProfile(Profile* profile) {
    111   DriveIntegrationService* integration_service =
    112       DriveIntegrationServiceFactory::FindForProfile(profile);
    113   return integration_service ? integration_service->download_handler() : NULL;
    114 }
    115 
    116 void DownloadHandler::Initialize(
    117     DownloadManager* download_manager,
    118     const base::FilePath& drive_tmp_download_path) {
    119   DCHECK(!drive_tmp_download_path.empty());
    120 
    121   drive_tmp_download_path_ = drive_tmp_download_path;
    122 
    123   if (download_manager) {
    124     notifier_.reset(new AllDownloadItemNotifier(download_manager, this));
    125     // Remove any persisted Drive DownloadItem. crbug.com/171384
    126     content::DownloadManager::DownloadVector downloads;
    127     download_manager->GetAllDownloads(&downloads);
    128     for (size_t i = 0; i < downloads.size(); ++i) {
    129       if (IsPersistedDriveDownload(drive_tmp_download_path_, downloads[i]))
    130         RemoveDownload(downloads[i]->GetId());
    131     }
    132   }
    133 }
    134 
    135 void DownloadHandler::SubstituteDriveDownloadPath(
    136     const base::FilePath& drive_path,
    137     content::DownloadItem* download,
    138     const SubstituteDriveDownloadPathCallback& callback) {
    139   DVLOG(1) << "SubstituteDriveDownloadPath " << drive_path.value();
    140 
    141   SetDownloadParams(drive_path, download);
    142 
    143   if (util::IsUnderDriveMountPoint(drive_path)) {
    144     // Can't access drive if the directory does not exist on Drive.
    145     // We set off a chain of callbacks as follows:
    146     // FileSystem::GetResourceEntryByPath
    147     //   OnEntryFound calls FileSystem::CreateDirectory (if necessary)
    148     //     OnCreateDirectory calls SubstituteDriveDownloadPathInternal
    149     const base::FilePath drive_dir_path =
    150         util::ExtractDrivePath(drive_path.DirName());
    151     // Check if the directory exists, and create it if the directory does not
    152     // exist.
    153     file_system_->GetResourceEntryByPath(
    154         drive_dir_path,
    155         base::Bind(&DownloadHandler::OnEntryFound,
    156                    weak_ptr_factory_.GetWeakPtr(),
    157                    drive_dir_path,
    158                    callback));
    159   } else {
    160     callback.Run(drive_path);
    161   }
    162 }
    163 
    164 void DownloadHandler::SetDownloadParams(const base::FilePath& drive_path,
    165                                         DownloadItem* download) {
    166   if (!download || (download->GetState() != DownloadItem::IN_PROGRESS))
    167     return;
    168 
    169   if (util::IsUnderDriveMountPoint(drive_path)) {
    170     download->SetUserData(&kDrivePathKey, new DriveUserData(drive_path));
    171     download->SetDisplayName(drive_path.BaseName());
    172   } else if (IsDriveDownload(download)) {
    173     // This may have been previously set if the default download folder is
    174     // /drive, and the user has now changed the download target to a local
    175     // folder.
    176     download->SetUserData(&kDrivePathKey, NULL);
    177     download->SetDisplayName(base::FilePath());
    178   }
    179 }
    180 
    181 base::FilePath DownloadHandler::GetTargetPath(
    182     const DownloadItem* download) {
    183   const DriveUserData* data = GetDriveUserData(download);
    184   // If data is NULL, we've somehow lost the drive path selected by the file
    185   // picker.
    186   DCHECK(data);
    187   return data ? data->file_path() : base::FilePath();
    188 }
    189 
    190 bool DownloadHandler::IsDriveDownload(const DownloadItem* download) {
    191   // We use the existence of the DriveUserData object in download as a
    192   // signal that this is a download to Drive.
    193   return GetDriveUserData(download) != NULL;
    194 }
    195 
    196 void DownloadHandler::CheckForFileExistence(
    197     const DownloadItem* download,
    198     const content::CheckForFileExistenceCallback& callback) {
    199   file_system_->GetResourceEntryByPath(
    200       util::ExtractDrivePath(GetTargetPath(download)),
    201       base::Bind(&ContinueCheckingForFileExistence,
    202                  callback));
    203 }
    204 
    205 void DownloadHandler::OnDownloadCreated(DownloadManager* manager,
    206                                         DownloadItem* download) {
    207   // Remove any persisted Drive DownloadItem. crbug.com/171384
    208   if (IsPersistedDriveDownload(drive_tmp_download_path_, download)) {
    209     // Remove download later, since doing it here results in a crash.
    210     BrowserThread::PostTask(BrowserThread::UI,
    211                             FROM_HERE,
    212                             base::Bind(&DownloadHandler::RemoveDownload,
    213                                        weak_ptr_factory_.GetWeakPtr(),
    214                                        download->GetId()));
    215   }
    216 }
    217 
    218 void DownloadHandler::RemoveDownload(int id) {
    219   DownloadManager* manager = notifier_->GetManager();
    220   if (!manager)
    221     return;
    222   DownloadItem* download = manager->GetDownload(id);
    223   if (!download)
    224     return;
    225   download->Remove();
    226 }
    227 
    228 void DownloadHandler::OnDownloadUpdated(
    229     DownloadManager* manager, DownloadItem* download) {
    230   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    231 
    232   // Only accept downloads that have the Drive meta data associated with them.
    233   DriveUserData* data = GetDriveUserData(download);
    234   if (!drive_tmp_download_path_.IsParent(download->GetTargetFilePath()) ||
    235       !data ||
    236       data->is_complete())
    237     return;
    238 
    239   switch (download->GetState()) {
    240     case DownloadItem::IN_PROGRESS:
    241       break;
    242 
    243     case DownloadItem::COMPLETE:
    244       UploadDownloadItem(download);
    245       data->set_complete();
    246       break;
    247 
    248     case DownloadItem::CANCELLED:
    249       download->SetUserData(&kDrivePathKey, NULL);
    250       break;
    251 
    252     case DownloadItem::INTERRUPTED:
    253       // Interrupted downloads can be resumed. Keep the Drive user data around
    254       // so that it can be used when the download resumes. The download is truly
    255       // done when it's complete, is cancelled or is removed.
    256       break;
    257 
    258     default:
    259       NOTREACHED();
    260   }
    261 }
    262 
    263 void DownloadHandler::OnEntryFound(
    264     const base::FilePath& drive_dir_path,
    265     const SubstituteDriveDownloadPathCallback& callback,
    266     FileError error,
    267     scoped_ptr<ResourceEntry> entry) {
    268   if (error == FILE_ERROR_NOT_FOUND) {
    269     // Destination Drive directory doesn't exist, so create it.
    270     const bool is_exclusive = false, is_recursive = true;
    271     file_system_->CreateDirectory(
    272         drive_dir_path, is_exclusive, is_recursive,
    273         base::Bind(&DownloadHandler::OnCreateDirectory,
    274                    weak_ptr_factory_.GetWeakPtr(),
    275                    callback));
    276   } else if (error == FILE_ERROR_OK) {
    277     // Directory is already ready.
    278     OnCreateDirectory(callback, FILE_ERROR_OK);
    279   } else {
    280     LOG(WARNING) << "Failed to get resource entry for path: "
    281                  << drive_dir_path.value() << ", error = "
    282                  << FileErrorToString(error);
    283     callback.Run(base::FilePath());
    284   }
    285 }
    286 
    287 void DownloadHandler::OnCreateDirectory(
    288     const SubstituteDriveDownloadPathCallback& callback,
    289     FileError error) {
    290   DVLOG(1) << "OnCreateDirectory " << FileErrorToString(error);
    291   if (error == FILE_ERROR_OK) {
    292     base::PostTaskAndReplyWithResult(
    293         BrowserThread::GetBlockingPool(),
    294         FROM_HERE,
    295         base::Bind(&GetDriveTempDownloadPath, drive_tmp_download_path_),
    296         callback);
    297   } else {
    298     LOG(WARNING) << "Failed to create directory, error = "
    299                  << FileErrorToString(error);
    300     callback.Run(base::FilePath());
    301   }
    302 }
    303 
    304 void DownloadHandler::UploadDownloadItem(DownloadItem* download) {
    305   DCHECK_EQ(DownloadItem::COMPLETE, download->GetState());
    306   file_write_helper_->PrepareWritableFileAndRun(
    307       util::ExtractDrivePath(GetTargetPath(download)),
    308       base::Bind(&MoveDownloadedFile, download->GetTargetFilePath()));
    309 }
    310 
    311 }  // namespace drive
    312