| Index: chrome/browser/component_updater/background_downloader_win.cc
|
| diff --git a/chrome/browser/component_updater/background_downloader_win.cc b/chrome/browser/component_updater/background_downloader_win.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..da3773df39d3938eecec2697bd887b39276ab779
|
| --- /dev/null
|
| +++ b/chrome/browser/component_updater/background_downloader_win.cc
|
| @@ -0,0 +1,534 @@
|
| +// Copyright 2013 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/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/time/time.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;
|
| +
|
| +// The class BackgroundDownloader in this module is an adapter between
|
| +// the CrxDownloader interface and the BITS service interfaces.
|
| +// The interface exposed on the CrxDownloader code runs on the UI thread, while
|
| +// the BITS specific code runs in a single threaded apartment on the FILE
|
| +// thread.
|
| +// For every url to download, a BITS job is created, unless there is already
|
| +// an existing job for that url, in which case, the downloader connects to it.
|
| +// Once a job is associated with the url, the code looks for changes in the
|
| +// BITS job state. The checks are triggered by a timer.
|
| +// The BITS job contains just one file to download. There could only be one
|
| +// download in progress at a time. If Chrome closes down before the download is
|
| +// complete, the BITS job remains active and finishes in the background, without
|
| +// any intervention. The job can be completed next time the code runs, if the
|
| +// file is still needed, otherwise it will be cleaned up on a periodic basis.
|
| +//
|
| +// To list the BITS jobs for a user, use the |bitsadmin| tool. The command line
|
| +// to do that is: "bitsadmin /list /verbose". Another useful command is
|
| +// "bitsadmin /info" and provide the job id returned by the previous /list
|
| +// command.
|
| +namespace component_updater {
|
| +
|
| +namespace {
|
| +
|
| +// All jobs created by this module have a specific description so they can
|
| +// be found at run-time or by using system administration tools.
|
| +const char16 kJobDescription[] = L"Chrome Component Updater";
|
| +
|
| +// How often the code looks for changes in the BITS job state.
|
| +const int kJobPollingIntervalSec = 10;
|
| +
|
| +// How often the jobs which were started but not completed for any reason
|
| +// are cleaned up. Reasons for jobs to be left behind include browser restarts,
|
| +// system restarts, etc. Also, the check to purge stale jobs only happens
|
| +// at most once a day.
|
| +const int kPurgeStaleJobsAfterDays = 7;
|
| +const int kPurgeStaleJobsIntervalBetweenChecksDays = 1;
|
| +
|
| +// Returns the status code from a given BITS error.
|
| +int GetHttpStatusFromBitsError(HRESULT error) {
|
| + // BITS errors are defined in bitsmsg.h. Although not documented, it is
|
| + // clear that all errors corresponding to http status code have the high
|
| + // word equal to 0x8019 and the low word equal to the http status code.
|
| + const int kHttpStatusFirst = 100; // Continue.
|
| + const int kHttpStatusLast = 505; // Version not supported.
|
| + bool is_valid = HIWORD(error) == 0x8019 &&
|
| + LOWORD(error) >= kHttpStatusFirst &&
|
| + LOWORD(error) <= kHttpStatusLast;
|
| + return is_valid ? LOWORD(error) : 0;
|
| +}
|
| +
|
| +// Returns the files in a BITS job.
|
| +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;
|
| +}
|
| +
|
| +// Returns the file name, the url, and some per-file progress information.
|
| +// The function out parameters can be NULL if that data is not requested.
|
| +HRESULT GetJobFileProperties(IBackgroundCopyFile* file,
|
| + string16* local_name,
|
| + string16* remote_name,
|
| + BG_FILE_PROGRESS* progress) {
|
| + HRESULT hr = S_OK;
|
| +
|
| + if (local_name) {
|
| + ScopedCoMem<char16> name;
|
| + hr = file->GetLocalName(&name);
|
| + if (FAILED(hr))
|
| + return hr;
|
| + local_name->assign(name);
|
| + }
|
| +
|
| + if (remote_name) {
|
| + ScopedCoMem<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 GetJobDescription(IBackgroundCopyJob* job, const string16* name) {
|
| + ScopedCoMem<char16> description;
|
| + return job->GetDescription(&description);
|
| +}
|
| +
|
| +// Finds the component updater jobs matching the given predicate.
|
| +// Returns S_OK if the function has found at least one job, returns S_FALSE if
|
| +// no job was found, and it returns an error otherwise.
|
| +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;
|
| +
|
| + // Iterate over jobs, run the predicate, and select the job only if
|
| + // the job description matches the component updater jobs.
|
| + 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)) {
|
| + 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;
|
| +}
|
| +
|
| +// Compares the job creation time and returns true if the job creation time
|
| +// is older than |num_days|.
|
| +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();
|
| +}
|
| +
|
| +// Compares the url of a file in a job and returns true if the remote name
|
| +// of any file in a job matches the argument.
|
| +struct JobFileUrlEqual
|
| + : public std::binary_function<IBackgroundCopyJob*, const string16&, bool> {
|
| + bool operator()(IBackgroundCopyJob* job, const string16& remote_name) const;
|
| +};
|
| +
|
| +bool JobFileUrlEqual::operator()(IBackgroundCopyJob* job,
|
| + const 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<char16> name;
|
| + if (SUCCEEDED(files[i]->GetRemoteName(&name)) &&
|
| + remote_name.compare(name) == 0)
|
| + return true;
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +// Creates an instance of the BITS manager.
|
| +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;
|
| + // TODO: add UMA pings.
|
| + return hr;
|
| + }
|
| + *bits_manager = object.Detach();
|
| + return S_OK;
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +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() {
|
| +}
|
| +
|
| +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));
|
| +}
|
| +
|
| +// Called once when this class is asked to do a download. Creates or opens
|
| +// an existing bits job, hooks up the notifications, and starts the timer.
|
| +void BackgroundDownloader::BeginDownload(const GURL& url) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
| +
|
| + DCHECK(!timer_);
|
| +
|
| + HRESULT hr = QueueBitsJob(url);
|
| + if (FAILED(hr)) {
|
| + if (job_) {
|
| + job_->Cancel();
|
| + job_ = NULL;
|
| + }
|
| + EndDownload(hr);
|
| + return;
|
| + }
|
| +
|
| + // A repeating timer retains the user task. This timer can be stopped and
|
| + // reset multiple times.
|
| + timer_.reset(new base::RepeatingTimer<BackgroundDownloader>);
|
| + timer_->Start(FROM_HERE,
|
| + base::TimeDelta::FromSeconds(kJobPollingIntervalSec),
|
| + this,
|
| + &BackgroundDownloader::OnDownloading);
|
| +}
|
| +
|
| +// Called any time the timer fires.
|
| +void BackgroundDownloader::OnDownloading() {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
| +
|
| + timer_->Stop();
|
| +
|
| + 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;
|
| +
|
| + // TODO: handle the non-final states, so that the download does not get
|
| + // stuck if BITS is not able to make progress on a given url.
|
| + case BG_JOB_STATE_TRANSIENT_ERROR:
|
| + case BG_JOB_STATE_QUEUED:
|
| + case BG_JOB_STATE_CONNECTING:
|
| + case BG_JOB_STATE_TRANSFERRING:
|
| + case BG_JOB_STATE_SUSPENDED:
|
| + default:
|
| + break;
|
| + }
|
| +
|
| + timer_->Reset();
|
| +}
|
| +
|
| +// Completes the BITS download, picks up the file path of the response, and
|
| +// notifies the CrxDownloader. The function should be called only once.
|
| +void BackgroundDownloader::EndDownload(HRESULT error) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
| +
|
| + DCHECK(!is_completed_);
|
| + if (is_completed_)
|
| + return;
|
| +
|
| + is_completed_ = true;
|
| +
|
| + DCHECK(!timer_->IsRunning());
|
| + timer_.reset();
|
| +
|
| + base::FilePath response;
|
| + HRESULT hr = error;
|
| + if (SUCCEEDED(hr)) {
|
| + std::vector<ScopedComPtr<IBackgroundCopyFile> > files;
|
| + GetFilesInJob(job_, &files);
|
| + DCHECK(files.size() == 1);
|
| + string16 local_name;
|
| + BG_FILE_PROGRESS progress = {0};
|
| + hr = GetJobFileProperties(files[0], &local_name, NULL, &progress);
|
| + if (SUCCEEDED(hr)) {
|
| + DCHECK(progress.Completed);
|
| + response = base::FilePath(local_name);
|
| + }
|
| + }
|
| +
|
| + // Consider the url handled if it has been successfully downloaded or a
|
| + // 5xx has been received.
|
| + const bool is_handled = SUCCEEDED(hr) ||
|
| + IsHttpServerError(GetHttpStatusFromBitsError(error));
|
| +
|
| + Result result;
|
| + result.error = error;
|
| + result.is_background_download = true;
|
| + result.response = response;
|
| + BrowserThread::PostTask(
|
| + BrowserThread::UI,
|
| + FROM_HERE,
|
| + base::Bind(&BackgroundDownloader::OnDownloadComplete,
|
| + base::Unretained(this),
|
| + is_handled,
|
| + result));
|
| +
|
| + CleanupStaleJobs(bits_manager_);
|
| +}
|
| +
|
| +// Called when the BITS job has been transferred successfully. Completes the
|
| +// BITS job by removing it from the BITS queue and making the download
|
| +// available to the caller.
|
| +void BackgroundDownloader::OnStateTransferred() {
|
| + HRESULT hr = job_->Complete();
|
| + if (SUCCEEDED(hr) || hr == BG_S_UNABLE_TO_DELETE_FILES)
|
| + hr = S_OK;
|
| + else
|
| + job_->Cancel();
|
| +
|
| + EndDownload(hr);
|
| +}
|
| +
|
| +// Called when the job has encountered an error and no further progress can
|
| +// be made. Cancels this job and removes it from the BITS queue.
|
| +void BackgroundDownloader::OnStateError() {
|
| + ScopedComPtr<IBackgroundCopyError> copy_error;
|
| + HRESULT hr = job_->GetError(copy_error.Receive());
|
| + if (SUCCEEDED(hr)) {
|
| + BG_ERROR_CONTEXT error_context = BG_ERROR_CONTEXT_NONE;
|
| + HRESULT error_code = E_FAIL;
|
| + hr = copy_error->GetError(&error_context, &error_code);
|
| + if (SUCCEEDED(hr)) {
|
| + EndDownload(error_code);
|
| + return;
|
| + }
|
| + }
|
| +
|
| + job_->Cancel();
|
| + EndDownload(hr);
|
| +}
|
| +
|
| +// Called when the download was cancelled. Since the observer should have
|
| +// been disconnected by now, this notification must not be seen.
|
| +void BackgroundDownloader::OnStateCancelled() {
|
| + EndDownload(E_UNEXPECTED);
|
| +}
|
| +
|
| +// Called when the download was completed. Same as above.
|
| +void BackgroundDownloader::OnStateAcknowledged() {
|
| + EndDownload(E_UNEXPECTED);
|
| +}
|
| +
|
| +// Creates or opens a job for the given url and queues it up. Tries to
|
| +// install a job observer but continues on if an observer can't be set up.
|
| +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;
|
| + }
|
| +
|
| + // Use kJobDescription as a temporary job display name until the proper
|
| + // display name is initialized later on.
|
| + 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 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;
|
| +
|
| + return S_OK;
|
| +}
|
| +
|
| +// Cleans up incompleted jobs that are too old.
|
| +HRESULT BackgroundDownloader::CleanupStaleJobs(
|
| + base::win::ScopedComPtr<IBackgroundCopyManager> bits_manager) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
|
| +
|
| + 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();
|
| + }
|
| +
|
| + return S_OK;
|
| +}
|
| +
|
| +} // namespace component_updater
|
| +
|
|
|