blob: 51c26ba299a13853a56a5e82cf48621bb057a9b5 [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 <stddef.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/supports_user_data.h"
#include "base/task/post_task.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/chromeos/drive/drive_integration_service.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/drive/write_on_cache_file.h"
#include "components/drive/chromeos/file_system_interface.h"
#include "components/drive/drive.pb.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_item_utils.h"
using content::BrowserThread;
using download::DownloadItem;
using content::DownloadManager;
namespace drive {
namespace {
// Key for base::SupportsUserData::Data.
const char kDrivePathKey[] = "DrivePath";
// Mime types that we better not trust. If the file was downloade with these
// mime types, while uploading to Drive we ignore it at guess by our own logic.
const char* const kGenericMimeTypes[] = {"text/html", "text/plain",
"application/octet-stream"};
// Longer is better. But at the same time, this value should be short enough as
// drive::internal::kMinFreeSpaceInBytes is not used up by file download in this
// interval.
const int kFreeDiskSpaceDelayInSeconds = 3;
// 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) {}
~DriveUserData() override = default;
const base::FilePath& file_path() const { return file_path_; }
const base::FilePath& cache_file_path() const { return cache_file_path_; }
void set_cache_file_path(const base::FilePath& path) {
cache_file_path_ = path;
}
bool is_complete() const { return is_complete_; }
void set_complete() { is_complete_ = true; }
private:
const base::FilePath file_path_;
base::FilePath cache_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 = base::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 = base::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,
base::FilePath* cache_file_path,
FileError error,
const base::FilePath& dest_path) {
if (error != FILE_ERROR_OK ||
!base::Move(downloaded_file, dest_path))
return;
*cache_file_path = dest_path;
}
// Used to implement CheckForFileExistence().
void ContinueCheckingForFileExistence(
content::CheckForFileExistenceCallback callback,
FileError error,
std::unique_ptr<ResourceEntry> entry) {
std::move(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) {
if (!drive_tmp_download_path.IsParent(download->GetTargetFilePath()))
return false;
return download->GetDownloadCreationType() ==
download::DownloadItem::TYPE_HISTORY_IMPORT;
}
// Returns an empty string |mime_type| was too generic that can be a result of
// 'default' fallback choice on the HTTP server. In such a case, we ignore the
// type so that our logic can guess by its own while uploading to Drive.
std::string FilterOutGenericMimeType(const std::string& mime_type) {
for (size_t i = 0; i < base::size(kGenericMimeTypes); ++i) {
if (base::LowerCaseEqualsASCII(mime_type, kGenericMimeTypes[i]))
return std::string();
}
return mime_type;
}
void IgnoreFreeDiskSpaceIfNeededForCallback(bool /*result*/) {}
} // namespace
DownloadHandler::DownloadHandler(FileSystemInterface* file_system)
: file_system_(file_system),
has_pending_free_disk_space_(false),
free_disk_space_delay_(
base::TimeDelta::FromSeconds(kFreeDiskSpaceDelayInSeconds)),
weak_ptr_factory_(this) {}
DownloadHandler::~DownloadHandler() = default;
// static
DownloadHandler* DownloadHandler::GetForProfile(Profile* profile) {
DriveIntegrationService* service =
DriveIntegrationServiceFactory::FindForProfile(profile);
if (!service || !service->IsMounted())
return nullptr;
return service->download_handler();
}
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_ = std::make_unique<download::AllDownloadItemNotifier>(
download_manager, this);
// Remove any persisted Drive DownloadItem. crbug.com/171384
DownloadManager::DownloadVector downloads;
download_manager->GetAllDownloads(&downloads);
for (size_t i = 0; i < downloads.size(); ++i) {
if (IsPersistedDriveDownload(drive_tmp_download_path_, downloads[i]))
downloads[i]->Remove();
}
}
}
void DownloadHandler::ObserveIncognitoDownloadManager(
DownloadManager* download_manager) {
notifier_incognito_ = std::make_unique<download::AllDownloadItemNotifier>(
download_manager, this);
}
void DownloadHandler::SubstituteDriveDownloadPath(
const base::FilePath& drive_path,
download::DownloadItem* download,
const SubstituteDriveDownloadPathCallback& callback) {
DVLOG(1) << "SubstituteDriveDownloadPath " << drive_path.value();
SetDownloadParams(drive_path, download);
if (util::IsUnderDriveMountPoint(drive_path)) {
// Prepare the destination directory.
const bool is_exclusive = false, is_recursive = true;
file_system_->CreateDirectory(
util::ExtractDrivePath(drive_path.DirName()),
is_exclusive, is_recursive,
base::Bind(&DownloadHandler::OnCreateDirectory,
weak_ptr_factory_.GetWeakPtr(),
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,
std::make_unique<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, nullptr);
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();
}
base::FilePath DownloadHandler::GetCacheFilePath(const DownloadItem* download) {
const DriveUserData* data = GetDriveUserData(download);
return data ? data->cache_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) != nullptr;
}
void DownloadHandler::CheckForFileExistence(
const DownloadItem* download,
content::CheckForFileExistenceCallback callback) {
file_system_->GetResourceEntry(
util::ExtractDrivePath(GetTargetPath(download)),
base::BindOnce(&ContinueCheckingForFileExistence, std::move(callback)));
}
void DownloadHandler::SetFreeDiskSpaceDelayForTesting(
const base::TimeDelta& delay) {
free_disk_space_delay_ = delay;
}
int64_t DownloadHandler::CalculateRequestSpace(
const DownloadManager::DownloadVector& downloads) {
int64_t request_space = 0;
for (const auto* download : downloads) {
if (download->IsDone())
continue;
const int64_t total_bytes = download->GetTotalBytes();
// Skip unknown size download. Since drive cache tries to keep
// drive::internal::kMinFreeSpaceInBytes, we can continue download with
// using the space temporally.
if (total_bytes == 0)
continue;
request_space += total_bytes - download->GetReceivedBytes();
}
return request_space;
}
void DownloadHandler::FreeDiskSpaceIfNeeded() {
if (has_pending_free_disk_space_)
return;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DownloadHandler::FreeDiskSpaceIfNeededImmediately,
weak_ptr_factory_.GetWeakPtr()),
free_disk_space_delay_);
has_pending_free_disk_space_ = true;
}
void DownloadHandler::FreeDiskSpaceIfNeededImmediately() {
DownloadManager::DownloadVector downloads;
// Get all downloads of current profile and its off-the-record profile.
// TODO(yawano): support multi profiles.
if (notifier_ && notifier_->GetManager()) {
notifier_->GetManager()->GetAllDownloads(&downloads);
}
if (notifier_incognito_ && notifier_incognito_->GetManager()) {
notifier_incognito_->GetManager()->GetAllDownloads(&downloads);
}
// Free disk space even if request size is 0 byte in order to make drive cache
// keep drive::internal::kMinFreeSpaceInBytes.
file_system_->FreeDiskSpaceIfNeededFor(
CalculateRequestSpace(downloads),
base::Bind(&IgnoreFreeDiskSpaceIfNeededForCallback));
has_pending_free_disk_space_ = false;
}
void DownloadHandler::OnDownloadCreated(DownloadManager* manager,
DownloadItem* download) {
FreeDiskSpaceIfNeededImmediately();
// 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.
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&DownloadHandler::RemoveDownload,
weak_ptr_factory_.GetWeakPtr(),
static_cast<void*>(manager), download->GetId()));
}
}
void DownloadHandler::RemoveDownload(void* manager_id, int id) {
DownloadManager* manager = GetDownloadManager(manager_id);
if (!manager)
return;
DownloadItem* download = manager->GetDownload(id);
if (!download)
return;
download->Remove();
}
void DownloadHandler::OnDownloadUpdated(
DownloadManager* manager, DownloadItem* download) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
FreeDiskSpaceIfNeeded();
// 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(manager, download);
data->set_complete();
break;
case DownloadItem::CANCELLED:
download->SetUserData(&kDrivePathKey, nullptr);
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::OnCreateDirectory(
const SubstituteDriveDownloadPathCallback& callback,
FileError error) {
DVLOG(1) << "OnCreateDirectory " << FileErrorToString(error);
if (error == FILE_ERROR_OK) {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
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(DownloadManager* manager,
DownloadItem* download) {
DCHECK_EQ(DownloadItem::COMPLETE, download->GetState());
base::FilePath* cache_file_path = new base::FilePath;
WriteOnCacheFileAndReply(
file_system_, util::ExtractDrivePath(GetTargetPath(download)),
FilterOutGenericMimeType(download->GetMimeType()),
base::Bind(&MoveDownloadedFile, download->GetTargetFilePath(),
cache_file_path),
base::Bind(&DownloadHandler::SetCacheFilePath,
weak_ptr_factory_.GetWeakPtr(), static_cast<void*>(manager),
download->GetId(), base::Owned(cache_file_path)));
}
void DownloadHandler::SetCacheFilePath(void* manager_id,
int id,
const base::FilePath* cache_file_path,
FileError error) {
if (error != FILE_ERROR_OK)
return;
DownloadManager* manager = GetDownloadManager(manager_id);
if (!manager)
return;
DownloadItem* download = manager->GetDownload(id);
if (!download)
return;
DriveUserData* data = GetDriveUserData(download);
if (!data)
return;
data->set_cache_file_path(*cache_file_path);
}
DownloadManager* DownloadHandler::GetDownloadManager(void* manager_id) {
if (manager_id == notifier_->GetManager())
return notifier_->GetManager();
if (notifier_incognito_ && manager_id == notifier_incognito_->GetManager())
return notifier_incognito_->GetManager();
return nullptr;
}
} // namespace drive