This source file includes following definitions.
- GetHttpStatusFromBitsError
- GetFilesInJob
- GetJobFileProperties
- GetJobByteCount
- GetJobDescription
- GetJobError
- FindBitsJobIf
- GetBitsManager
- CleanupJobFiles
- CleanupStaleJobs
- is_completed_
- DoStartDownload
- BeginDownload
- OnDownloading
- EndDownload
- OnStateTransferred
- OnStateError
- OnStateTransientError
- OnStateQueued
- OnStateTransferring
- OnStateCancelled
- OnStateAcknowledged
- QueueBitsJob
- CreateOrOpenJob
- InitializeNewJob
- IsStuck
#include "chrome/browser/component_updater/background_downloader_win.h"
#include <atlbase.h>
#include <atlcom.h>
#include <functional>
#include <iomanip>
#include <vector>
#include "base/file_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/win/scoped_co_mem.h"
#include "chrome/browser/component_updater/component_updater_utils.h"
#include "content/public/browser/browser_thread.h"
#include "ui/base/win/atl_module.h"
#include "url/gurl.h"
using base::win::ScopedCoMem;
using base::win::ScopedComPtr;
using content::BrowserThread;
namespace component_updater {
namespace {
const base::char16 kJobDescription[] = L"Chrome Component Updater";
const int kJobPollingIntervalSec = 10;
const int kMinimumRetryDelayMin = 1;
const int kJobStuckTimeoutMin = 15;
const int kSetNoProgressTimeoutDays = 1;
const int kPurgeStaleJobsAfterDays = 7;
const int kPurgeStaleJobsIntervalBetweenChecksDays = 1;
int GetHttpStatusFromBitsError(HRESULT error) {
const int kHttpStatusFirst = 100;
const int kHttpStatusLast = 505;
bool is_valid = HIWORD(error) == 0x8019 &&
LOWORD(error) >= kHttpStatusFirst &&
LOWORD(error) <= kHttpStatusLast;
return is_valid ? LOWORD(error) : 0;
}
HRESULT GetFilesInJob(IBackgroundCopyJob* job,
std::vector<ScopedComPtr<IBackgroundCopyFile> >* files) {
ScopedComPtr<IEnumBackgroundCopyFiles> enum_files;
HRESULT hr = job->EnumFiles(enum_files.Receive());
if (FAILED(hr))
return hr;
ULONG num_files = 0;
hr = enum_files->GetCount(&num_files);
if (FAILED(hr))
return hr;
for (ULONG i = 0; i != num_files; ++i) {
ScopedComPtr<IBackgroundCopyFile> file;
if (enum_files->Next(1, file.Receive(), NULL) == S_OK)
files->push_back(file);
}
return S_OK;
}
HRESULT GetJobFileProperties(IBackgroundCopyFile* file,
base::string16* local_name,
base::string16* remote_name,
BG_FILE_PROGRESS* progress) {
HRESULT hr = S_OK;
if (local_name) {
ScopedCoMem<base::char16> name;
hr = file->GetLocalName(&name);
if (FAILED(hr))
return hr;
local_name->assign(name);
}
if (remote_name) {
ScopedCoMem<base::char16> name;
hr = file->GetRemoteName(&name);
if (FAILED(hr))
return hr;
remote_name->assign(name);
}
if (progress) {
BG_FILE_PROGRESS bg_file_progress = {};
hr = file->GetProgress(&bg_file_progress);
if (FAILED(hr))
return hr;
*progress = bg_file_progress;
}
return hr;
}
HRESULT GetJobByteCount(IBackgroundCopyJob* job,
int64* bytes_downloaded,
int64* bytes_total) {
*bytes_downloaded = -1;
*bytes_total = -1;
if (!job)
return E_FAIL;
BG_JOB_PROGRESS job_progress = {0};
HRESULT hr = job->GetProgress(&job_progress);
if (FAILED(hr))
return hr;
if (job_progress.BytesTransferred <= kint64max)
*bytes_downloaded = job_progress.BytesTransferred;
if (job_progress.BytesTotal <= kint64max &&
job_progress.BytesTotal != BG_SIZE_UNKNOWN)
*bytes_total = job_progress.BytesTotal;
return S_OK;
}
HRESULT GetJobDescription(IBackgroundCopyJob* job, const base::string16* name) {
ScopedCoMem<base::char16> description;
return job->GetDescription(&description);
}
HRESULT GetJobError(IBackgroundCopyJob* job, HRESULT* error_code_out) {
*error_code_out = S_OK;
ScopedComPtr<IBackgroundCopyError> copy_error;
HRESULT hr = job->GetError(copy_error.Receive());
if (FAILED(hr))
return hr;
BG_ERROR_CONTEXT error_context = BG_ERROR_CONTEXT_NONE;
HRESULT error_code = S_OK;
hr = copy_error->GetError(&error_context, &error_code);
if (FAILED(hr))
return hr;
*error_code_out = FAILED(error_code) ? error_code : E_FAIL;
return S_OK;
}
template<class Predicate>
HRESULT FindBitsJobIf(Predicate pred,
IBackgroundCopyManager* bits_manager,
std::vector<ScopedComPtr<IBackgroundCopyJob> >* jobs) {
ScopedComPtr<IEnumBackgroundCopyJobs> enum_jobs;
HRESULT hr = bits_manager->EnumJobs(0, enum_jobs.Receive());
if (FAILED(hr))
return hr;
ULONG job_count = 0;
hr = enum_jobs->GetCount(&job_count);
if (FAILED(hr))
return hr;
for (ULONG i = 0; i != job_count; ++i) {
ScopedComPtr<IBackgroundCopyJob> current_job;
if (enum_jobs->Next(1, current_job.Receive(), NULL) == S_OK &&
pred(current_job)) {
base::string16 job_description;
hr = GetJobDescription(current_job, &job_description);
if (job_description.compare(kJobDescription) == 0)
jobs->push_back(current_job);
}
}
return jobs->empty() ? S_FALSE : S_OK;
}
struct JobCreationOlderThanDays
: public std::binary_function<IBackgroundCopyJob*, int, bool> {
bool operator()(IBackgroundCopyJob* job, int num_days) const;
};
bool JobCreationOlderThanDays::operator()(IBackgroundCopyJob* job,
int num_days) const {
BG_JOB_TIMES times = {0};
HRESULT hr = job->GetTimes(×);
if (FAILED(hr))
return false;
const base::TimeDelta time_delta(base::TimeDelta::FromDays(num_days));
const base::Time creation_time(base::Time::FromFileTime(times.CreationTime));
return creation_time + time_delta < base::Time::Now();
}
struct JobFileUrlEqual
: public std::binary_function<IBackgroundCopyJob*, const base::string16&,
bool> {
bool operator()(IBackgroundCopyJob* job,
const base::string16& remote_name) const;
};
bool JobFileUrlEqual::operator()(IBackgroundCopyJob* job,
const base::string16& remote_name) const {
std::vector<ScopedComPtr<IBackgroundCopyFile> > files;
HRESULT hr = GetFilesInJob(job, &files);
if (FAILED(hr))
return false;
for (size_t i = 0; i != files.size(); ++i) {
ScopedCoMem<base::char16> name;
if (SUCCEEDED(files[i]->GetRemoteName(&name)) &&
remote_name.compare(name) == 0)
return true;
}
return false;
}
HRESULT GetBitsManager(IBackgroundCopyManager** bits_manager) {
ScopedComPtr<IBackgroundCopyManager> object;
HRESULT hr = object.CreateInstance(__uuidof(BackgroundCopyManager));
if (FAILED(hr)) {
VLOG(1) << "Failed to instantiate BITS." << std::hex << hr;
return hr;
}
*bits_manager = object.Detach();
return S_OK;
}
void CleanupJobFiles(IBackgroundCopyJob* job) {
std::vector<ScopedComPtr<IBackgroundCopyFile> > files;
if (FAILED(GetFilesInJob(job, &files)))
return;
for (size_t i = 0; i != files.size(); ++i) {
base::string16 local_name;
HRESULT hr(GetJobFileProperties(files[i], &local_name, NULL, NULL));
if (SUCCEEDED(hr))
DeleteFileAndEmptyParentDirectory(base::FilePath(local_name));
}
}
HRESULT CleanupStaleJobs(
base::win::ScopedComPtr<IBackgroundCopyManager> bits_manager) {
if (!bits_manager)
return E_FAIL;
static base::Time last_sweep;
const base::TimeDelta time_delta(base::TimeDelta::FromDays(
kPurgeStaleJobsIntervalBetweenChecksDays));
const base::Time current_time(base::Time::Now());
if (last_sweep + time_delta > current_time)
return S_OK;
last_sweep = current_time;
std::vector<ScopedComPtr<IBackgroundCopyJob> > jobs;
HRESULT hr = FindBitsJobIf(
std::bind2nd(JobCreationOlderThanDays(), kPurgeStaleJobsAfterDays),
bits_manager,
&jobs);
if (FAILED(hr))
return hr;
for (size_t i = 0; i != jobs.size(); ++i) {
jobs[i]->Cancel();
CleanupJobFiles(jobs[i]);
}
return S_OK;
}
}
BackgroundDownloader::BackgroundDownloader(
scoped_ptr<CrxDownloader> successor,
net::URLRequestContextGetter* context_getter,
scoped_refptr<base::SequencedTaskRunner> task_runner,
const DownloadCallback& download_callback)
: CrxDownloader(successor.Pass(), download_callback),
context_getter_(context_getter),
task_runner_(task_runner),
is_completed_(false) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
BackgroundDownloader::~BackgroundDownloader() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
timer_.release();
bits_manager_.Detach();
job_.Detach();
}
void BackgroundDownloader::DoStartDownload(const GURL& url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
BrowserThread::PostTask(
BrowserThread::FILE,
FROM_HERE,
base::Bind(&BackgroundDownloader::BeginDownload,
base::Unretained(this),
url));
}
void BackgroundDownloader::BeginDownload(const GURL& url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(!timer_);
is_completed_ = false;
download_start_time_ = base::Time::Now();
job_stuck_begin_time_ = download_start_time_;
HRESULT hr = QueueBitsJob(url);
if (FAILED(hr)) {
EndDownload(hr);
return;
}
timer_.reset(new base::RepeatingTimer<BackgroundDownloader>);
timer_->Start(FROM_HERE,
base::TimeDelta::FromSeconds(kJobPollingIntervalSec),
this,
&BackgroundDownloader::OnDownloading);
}
void BackgroundDownloader::OnDownloading() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(job_);
DCHECK(!is_completed_);
if (is_completed_)
return;
BG_JOB_STATE job_state = BG_JOB_STATE_ERROR;
HRESULT hr = job_->GetState(&job_state);
if (FAILED(hr)) {
EndDownload(hr);
return;
}
switch (job_state) {
case BG_JOB_STATE_TRANSFERRED:
OnStateTransferred();
return;
case BG_JOB_STATE_ERROR:
OnStateError();
return;
case BG_JOB_STATE_CANCELLED:
OnStateCancelled();
return;
case BG_JOB_STATE_ACKNOWLEDGED:
OnStateAcknowledged();
return;
case BG_JOB_STATE_QUEUED:
case BG_JOB_STATE_CONNECTING:
case BG_JOB_STATE_SUSPENDED:
OnStateQueued();
break;
case BG_JOB_STATE_TRANSIENT_ERROR:
OnStateTransientError();
break;
case BG_JOB_STATE_TRANSFERRING:
OnStateTransferring();
break;
default:
break;
}
}
void BackgroundDownloader::EndDownload(HRESULT error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
DCHECK(!is_completed_);
is_completed_ = true;
timer_.reset();
const base::Time download_end_time(base::Time::Now());
const base::TimeDelta download_time =
download_end_time >= download_start_time_ ?
download_end_time - download_start_time_ : base::TimeDelta();
int64 bytes_downloaded = -1;
int64 bytes_total = -1;
GetJobByteCount(job_, &bytes_downloaded, &bytes_total);
base::FilePath response;
if (SUCCEEDED(error)) {
DCHECK(job_);
std::vector<ScopedComPtr<IBackgroundCopyFile> > files;
GetFilesInJob(job_, &files);
DCHECK(files.size() == 1);
base::string16 local_name;
BG_FILE_PROGRESS progress = {0};
HRESULT hr = GetJobFileProperties(files[0], &local_name, NULL, &progress);
if (SUCCEEDED(hr)) {
DCHECK(progress.Completed);
DCHECK(bytes_downloaded == static_cast<int64>(progress.BytesTransferred));
DCHECK(bytes_total == static_cast<int64>(progress.BytesTotal));
response = base::FilePath(local_name);
} else {
error = hr;
}
}
if (FAILED(error) && job_) {
job_->Cancel();
CleanupJobFiles(job_);
}
job_ = NULL;
const bool is_handled = SUCCEEDED(error) ||
IsHttpServerError(GetHttpStatusFromBitsError(error));
const int error_to_report = SUCCEEDED(error) ? 0 : error;
DownloadMetrics download_metrics;
download_metrics.url = url();
download_metrics.downloader = DownloadMetrics::kBits;
download_metrics.error = error_to_report;
download_metrics.bytes_downloaded = bytes_downloaded;
download_metrics.bytes_total = bytes_total;
download_metrics.download_time_ms = download_time.InMilliseconds();
CleanupStaleJobs(bits_manager_);
bits_manager_ = NULL;
Result result;
result.error = error_to_report;
result.response = response;
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&BackgroundDownloader::OnDownloadComplete,
base::Unretained(this),
is_handled,
result,
download_metrics));
}
void BackgroundDownloader::OnStateTransferred() {
HRESULT hr = job_->Complete();
if (SUCCEEDED(hr) || hr == BG_S_UNABLE_TO_DELETE_FILES)
hr = S_OK;
EndDownload(hr);
}
void BackgroundDownloader::OnStateError() {
HRESULT error_code = S_OK;
HRESULT hr = GetJobError(job_, &error_code);
if (FAILED(hr))
error_code = hr;
DCHECK(FAILED(error_code));
EndDownload(error_code);
}
void BackgroundDownloader::OnStateTransientError() {
if (IsStuck()) {
OnStateError();
return;
}
HRESULT error_code = S_OK;
HRESULT hr = GetJobError(job_, &error_code);
if (SUCCEEDED(hr) &&
IsHttpServerError(GetHttpStatusFromBitsError(error_code))) {
OnStateError();
return;
}
}
void BackgroundDownloader::OnStateQueued() {
if (IsStuck())
EndDownload(E_ABORT);
}
void BackgroundDownloader::OnStateTransferring() {
job_stuck_begin_time_ = base::Time::Now();
}
void BackgroundDownloader::OnStateCancelled() {
EndDownload(E_UNEXPECTED);
}
void BackgroundDownloader::OnStateAcknowledged() {
EndDownload(E_UNEXPECTED);
}
HRESULT BackgroundDownloader::QueueBitsJob(const GURL& url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
HRESULT hr = S_OK;
if (bits_manager_ == NULL) {
hr = GetBitsManager(bits_manager_.Receive());
if (FAILED(hr))
return hr;
}
hr = CreateOrOpenJob(url);
if (FAILED(hr))
return hr;
if (hr == S_OK) {
hr = InitializeNewJob(url);
if (FAILED(hr))
return hr;
}
return job_->Resume();
}
HRESULT BackgroundDownloader::CreateOrOpenJob(const GURL& url) {
std::vector<ScopedComPtr<IBackgroundCopyJob> > jobs;
HRESULT hr = FindBitsJobIf(
std::bind2nd(JobFileUrlEqual(), base::SysUTF8ToWide(url.spec())),
bits_manager_,
&jobs);
if (SUCCEEDED(hr) && !jobs.empty()) {
job_ = jobs.front();
return S_FALSE;
}
GUID guid = {0};
ScopedComPtr<IBackgroundCopyJob> job;
hr = bits_manager_->CreateJob(kJobDescription,
BG_JOB_TYPE_DOWNLOAD,
&guid,
job.Receive());
if (FAILED(hr))
return hr;
job_ = job;
return S_OK;
}
HRESULT BackgroundDownloader::InitializeNewJob(const GURL& url) {
const base::string16 filename(base::SysUTF8ToWide(url.ExtractFileName()));
base::FilePath tempdir;
if (!base::CreateNewTempDirectory(
FILE_PATH_LITERAL("chrome_BITS_"),
&tempdir))
return E_FAIL;
HRESULT hr = job_->AddFile(
base::SysUTF8ToWide(url.spec()).c_str(),
tempdir.Append(filename).AsUTF16Unsafe().c_str());
if (FAILED(hr))
return hr;
hr = job_->SetDisplayName(filename.c_str());
if (FAILED(hr))
return hr;
hr = job_->SetDescription(kJobDescription);
if (FAILED(hr))
return hr;
hr = job_->SetPriority(BG_JOB_PRIORITY_NORMAL);
if (FAILED(hr))
return hr;
hr = job_->SetMinimumRetryDelay(60 * kMinimumRetryDelayMin);
if (FAILED(hr))
return hr;
const int kSecondsDay = 60 * 60 * 24;
hr = job_->SetNoProgressTimeout(kSecondsDay * kSetNoProgressTimeoutDays);
if (FAILED(hr))
return hr;
return S_OK;
}
bool BackgroundDownloader::IsStuck() {
const base::TimeDelta job_stuck_timeout(
base::TimeDelta::FromMinutes(kJobStuckTimeoutMin));
return job_stuck_begin_time_ + job_stuck_timeout < base::Time::Now();
}
}