blob: 9cc3738d251627d50f2fb8054e131d351e1f1c85 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/drive/download_handler.h"
#include "base/bind.h"
#include "base/file_util.h"
#include "base/supports_user_data.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/chromeos/drive/drive.pb.h"
#include "chrome/browser/chromeos/drive/drive_integration_service.h"
#include "chrome/browser/chromeos/drive/file_system_interface.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/drive/write_on_cache_file.h"
#include "content/public/browser/browser_thread.h"
using content::BrowserThread;
using content::DownloadManager;
using content::DownloadItem;
namespace drive {
namespace {
// Key for base::SupportsUserData::Data.
const char kDrivePathKey[] = "DrivePath";
// User Data stored in DownloadItem for drive path.
class DriveUserData : public base::SupportsUserData::Data {
public:
explicit DriveUserData(const base::FilePath& path) : file_path_(path),
is_complete_(false) {}
virtual ~DriveUserData() {}
const base::FilePath& file_path() const { return file_path_; }
bool is_complete() const { return is_complete_; }
void set_complete() { is_complete_ = true; }
private:
const base::FilePath file_path_;
bool is_complete_;
};
// Extracts DriveUserData* from |download|.
const DriveUserData* GetDriveUserData(const DownloadItem* download) {
return static_cast<const DriveUserData*>(
download->GetUserData(&kDrivePathKey));
}
DriveUserData* GetDriveUserData(DownloadItem* download) {
return static_cast<DriveUserData*>(download->GetUserData(&kDrivePathKey));
}
// Creates a temporary file |drive_tmp_download_path| in
// |drive_tmp_download_dir|. Must be called on a thread that allows file
// operations.
base::FilePath GetDriveTempDownloadPath(
const base::FilePath& drive_tmp_download_dir) {
bool created = file_util::CreateDirectory(drive_tmp_download_dir);
DCHECK(created) << "Can not create temp download directory at "
<< drive_tmp_download_dir.value();
base::FilePath drive_tmp_download_path;
created = file_util::CreateTemporaryFileInDir(drive_tmp_download_dir,
&drive_tmp_download_path);
DCHECK(created) << "Temporary download file creation failed";
return drive_tmp_download_path;
}
// Moves downloaded file to Drive.
void MoveDownloadedFile(const base::FilePath& downloaded_file,
FileError error,
const base::FilePath& dest_path) {
if (error != FILE_ERROR_OK)
return;
base::Move(downloaded_file, dest_path);
}
// Used to implement CheckForFileExistence().
void ContinueCheckingForFileExistence(
const content::CheckForFileExistenceCallback& callback,
FileError error,
scoped_ptr<ResourceEntry> entry) {
callback.Run(error == FILE_ERROR_OK);
}
// Returns true if |download| is a Drive download created from data persisted
// on the download history DB.
bool IsPersistedDriveDownload(const base::FilePath& drive_tmp_download_path,
DownloadItem* download) {
// Persisted downloads are not in IN_PROGRESS state when created, while newly
// created downloads are.
return drive_tmp_download_path.IsParent(download->GetTargetFilePath()) &&
download->GetState() != DownloadItem::IN_PROGRESS;
}
} // namespace
DownloadHandler::DownloadHandler(FileSystemInterface* file_system)
: file_system_(file_system),
weak_ptr_factory_(this) {
}
DownloadHandler::~DownloadHandler() {
}
// static
DownloadHandler* DownloadHandler::GetForProfile(Profile* profile) {
DriveIntegrationService* integration_service =
DriveIntegrationServiceFactory::FindForProfile(profile);
// TODO(hidehiko): FindForProfile will return the instance even if Drive is
// disabled. Needs to check the state. crbug.com/284972.
return integration_service ? integration_service->download_handler() : NULL;
}
void DownloadHandler::Initialize(
DownloadManager* download_manager,
const base::FilePath& drive_tmp_download_path) {
DCHECK(!drive_tmp_download_path.empty());
drive_tmp_download_path_ = drive_tmp_download_path;
if (download_manager) {
notifier_.reset(new AllDownloadItemNotifier(download_manager, this));
// Remove any persisted Drive DownloadItem. crbug.com/171384
content::DownloadManager::DownloadVector downloads;
download_manager->GetAllDownloads(&downloads);
for (size_t i = 0; i < downloads.size(); ++i) {
if (IsPersistedDriveDownload(drive_tmp_download_path_, downloads[i]))
RemoveDownload(downloads[i]->GetId());
}
}
}
void DownloadHandler::SubstituteDriveDownloadPath(
const base::FilePath& drive_path,
content::DownloadItem* download,
const SubstituteDriveDownloadPathCallback& callback) {
DVLOG(1) << "SubstituteDriveDownloadPath " << drive_path.value();
SetDownloadParams(drive_path, download);
if (util::IsUnderDriveMountPoint(drive_path)) {
// Can't access drive if the directory does not exist on Drive.
// We set off a chain of callbacks as follows:
// FileSystem::GetResourceEntryByPath
// OnEntryFound calls FileSystem::CreateDirectory (if necessary)
// OnCreateDirectory calls SubstituteDriveDownloadPathInternal
const base::FilePath drive_dir_path =
util::ExtractDrivePath(drive_path.DirName());
// Check if the directory exists, and create it if the directory does not
// exist.
file_system_->GetResourceEntryByPath(
drive_dir_path,
base::Bind(&DownloadHandler::OnEntryFound,
weak_ptr_factory_.GetWeakPtr(),
drive_dir_path,
callback));
} else {
callback.Run(drive_path);
}
}
void DownloadHandler::SetDownloadParams(const base::FilePath& drive_path,
DownloadItem* download) {
if (!download || (download->GetState() != DownloadItem::IN_PROGRESS))
return;
if (util::IsUnderDriveMountPoint(drive_path)) {
download->SetUserData(&kDrivePathKey, new DriveUserData(drive_path));
download->SetDisplayName(drive_path.BaseName());
} else if (IsDriveDownload(download)) {
// This may have been previously set if the default download folder is
// /drive, and the user has now changed the download target to a local
// folder.
download->SetUserData(&kDrivePathKey, NULL);
download->SetDisplayName(base::FilePath());
}
}
base::FilePath DownloadHandler::GetTargetPath(
const DownloadItem* download) {
const DriveUserData* data = GetDriveUserData(download);
// If data is NULL, we've somehow lost the drive path selected by the file
// picker.
DCHECK(data);
return data ? data->file_path() : base::FilePath();
}
bool DownloadHandler::IsDriveDownload(const DownloadItem* download) {
// We use the existence of the DriveUserData object in download as a
// signal that this is a download to Drive.
return GetDriveUserData(download) != NULL;
}
void DownloadHandler::CheckForFileExistence(
const DownloadItem* download,
const content::CheckForFileExistenceCallback& callback) {
file_system_->GetResourceEntryByPath(
util::ExtractDrivePath(GetTargetPath(download)),
base::Bind(&ContinueCheckingForFileExistence,
callback));
}
void DownloadHandler::OnDownloadCreated(DownloadManager* manager,
DownloadItem* download) {
// Remove any persisted Drive DownloadItem. crbug.com/171384
if (IsPersistedDriveDownload(drive_tmp_download_path_, download)) {
// Remove download later, since doing it here results in a crash.
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&DownloadHandler::RemoveDownload,
weak_ptr_factory_.GetWeakPtr(),
download->GetId()));
}
}
void DownloadHandler::RemoveDownload(int id) {
DownloadManager* manager = notifier_->GetManager();
if (!manager)
return;
DownloadItem* download = manager->GetDownload(id);
if (!download)
return;
download->Remove();
}
void DownloadHandler::OnDownloadUpdated(
DownloadManager* manager, DownloadItem* download) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Only accept downloads that have the Drive meta data associated with them.
DriveUserData* data = GetDriveUserData(download);
if (!drive_tmp_download_path_.IsParent(download->GetTargetFilePath()) ||
!data ||
data->is_complete())
return;
switch (download->GetState()) {
case DownloadItem::IN_PROGRESS:
break;
case DownloadItem::COMPLETE:
UploadDownloadItem(download);
data->set_complete();
break;
case DownloadItem::CANCELLED:
download->SetUserData(&kDrivePathKey, NULL);
break;
case DownloadItem::INTERRUPTED:
// Interrupted downloads can be resumed. Keep the Drive user data around
// so that it can be used when the download resumes. The download is truly
// done when it's complete, is cancelled or is removed.
break;
default:
NOTREACHED();
}
}
void DownloadHandler::OnEntryFound(
const base::FilePath& drive_dir_path,
const SubstituteDriveDownloadPathCallback& callback,
FileError error,
scoped_ptr<ResourceEntry> entry) {
if (error == FILE_ERROR_NOT_FOUND) {
// Destination Drive directory doesn't exist, so create it.
const bool is_exclusive = false, is_recursive = true;
file_system_->CreateDirectory(
drive_dir_path, is_exclusive, is_recursive,
base::Bind(&DownloadHandler::OnCreateDirectory,
weak_ptr_factory_.GetWeakPtr(),
callback));
} else if (error == FILE_ERROR_OK) {
// Directory is already ready.
OnCreateDirectory(callback, FILE_ERROR_OK);
} else {
LOG(WARNING) << "Failed to get resource entry for path: "
<< drive_dir_path.value() << ", error = "
<< FileErrorToString(error);
callback.Run(base::FilePath());
}
}
void DownloadHandler::OnCreateDirectory(
const SubstituteDriveDownloadPathCallback& callback,
FileError error) {
DVLOG(1) << "OnCreateDirectory " << FileErrorToString(error);
if (error == FILE_ERROR_OK) {
base::PostTaskAndReplyWithResult(
BrowserThread::GetBlockingPool(),
FROM_HERE,
base::Bind(&GetDriveTempDownloadPath, drive_tmp_download_path_),
callback);
} else {
LOG(WARNING) << "Failed to create directory, error = "
<< FileErrorToString(error);
callback.Run(base::FilePath());
}
}
void DownloadHandler::UploadDownloadItem(DownloadItem* download) {
DCHECK_EQ(DownloadItem::COMPLETE, download->GetState());
WriteOnCacheFile(
file_system_,
util::ExtractDrivePath(GetTargetPath(download)),
download->GetMimeType(),
base::Bind(&MoveDownloadedFile, download->GetTargetFilePath()));
}
} // namespace drive