1// Copyright 2013 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/component_updater/background_downloader_win.h" 6 7#include <atlbase.h> 8#include <atlcom.h> 9 10#include <functional> 11#include <iomanip> 12#include <vector> 13 14#include "base/file_util.h" 15#include "base/strings/sys_string_conversions.h" 16#include "base/win/scoped_co_mem.h" 17#include "chrome/browser/component_updater/component_updater_utils.h" 18#include "content/public/browser/browser_thread.h" 19#include "ui/base/win/atl_module.h" 20#include "url/gurl.h" 21 22using base::win::ScopedCoMem; 23using base::win::ScopedComPtr; 24using content::BrowserThread; 25 26// The class BackgroundDownloader in this module is an adapter between 27// the CrxDownloader interface and the BITS service interfaces. 28// The interface exposed on the CrxDownloader code runs on the UI thread, while 29// the BITS specific code runs in a single threaded apartment on the FILE 30// thread. 31// For every url to download, a BITS job is created, unless there is already 32// an existing job for that url, in which case, the downloader connects to it. 33// Once a job is associated with the url, the code looks for changes in the 34// BITS job state. The checks are triggered by a timer. 35// The BITS job contains just one file to download. There could only be one 36// download in progress at a time. If Chrome closes down before the download is 37// complete, the BITS job remains active and finishes in the background, without 38// any intervention. The job can be completed next time the code runs, if the 39// file is still needed, otherwise it will be cleaned up on a periodic basis. 40// 41// To list the BITS jobs for a user, use the |bitsadmin| tool. The command line 42// to do that is: "bitsadmin /list /verbose". Another useful command is 43// "bitsadmin /info" and provide the job id returned by the previous /list 44// command. 45// 46// Ignoring the suspend/resume issues since this code is not using them, the 47// job state machine implemented by BITS is something like this: 48// 49// Suspended--->Queued--->Connecting---->Transferring--->Transferred 50// | ^ | | | 51// | | V V | (complete) 52// +----------|---------+-----------------+-----+ V 53// | | | | Acknowledged 54// | V V | 55// | Transient Error------->Error | 56// | | | |(cancel) 57// | +-------+---------+--->-+ 58// | V | 59// | (resume) | | 60// +------<----------+ +---->Cancelled 61// 62// The job is created in the "suspended" state. Once |Resume| is called, 63// BITS queues up the job, then tries to connect, begins transferring the 64// job bytes, and moves the job to the "transferred state, after the job files 65// have been transferred. When calling |Complete| for a job, the job files are 66// made available to the caller, and the job is moved to the "acknowledged" 67// state. 68// At any point, the job can be cancelled, in which case, the job is moved 69// to the "cancelled state" and the job object is removed from the BITS queue. 70// Along the way, the job can encounter recoverable and non-recoverable errors. 71// BITS moves the job to "transient error" or "error", depending on which kind 72// of error has occured. 73// If the job has reached the "transient error" state, BITS retries the 74// job after a certain programmable delay. If the job can't be completed in a 75// certain time interval, BITS stops retrying and errors the job out. This time 76// interval is also programmable. 77// If the job is in either of the error states, the job parameters can be 78// adjusted to handle the error, after which the job can be resumed, and the 79// whole cycle starts again. 80// Jobs that are not touched in 90 days (or a value set by group policy) are 81// automatically disposed off by BITS. This concludes the brief description of 82// a job lifetime, according to BITS. 83// 84// In addition to how BITS is managing the life time of the job, there are a 85// couple of special cases defined by the BackgroundDownloader. 86// First, if the job encounters any of the 5xx HTTP responses, the job is 87// not retried, in order to avoid DDOS-ing the servers. 88// Second, there is a simple mechanism to detect stuck jobs, and allow the rest 89// of the code to move on to trying other urls or trying other components. 90// Last, after completing a job, irrespective of the outcome, the jobs older 91// than a week are proactively cleaned up. 92 93namespace component_updater { 94 95namespace { 96 97// All jobs created by this module have a specific description so they can 98// be found at run-time or by using system administration tools. 99const base::char16 kJobDescription[] = L"Chrome Component Updater"; 100 101// How often the code looks for changes in the BITS job state. 102const int kJobPollingIntervalSec = 4; 103 104// How long BITS waits before retrying a job after the job encountered 105// a transient error. If this value is not set, the BITS default is 10 minutes. 106const int kMinimumRetryDelayMin = 1; 107 108// How long to wait for stuck jobs. Stuck jobs could be queued for too long, 109// have trouble connecting, could be suspended for any reason, or they have 110// encountered some transient error. 111const int kJobStuckTimeoutMin = 15; 112 113// How long BITS waits before giving up on a job that could not be completed 114// since the job has encountered its first transient error. If this value is 115// not set, the BITS default is 14 days. 116const int kSetNoProgressTimeoutDays = 1; 117 118// How often the jobs which were started but not completed for any reason 119// are cleaned up. Reasons for jobs to be left behind include browser restarts, 120// system restarts, etc. Also, the check to purge stale jobs only happens 121// at most once a day. If the job clean up code is not running, the BITS 122// default policy is to cancel jobs after 90 days of inactivity. 123const int kPurgeStaleJobsAfterDays = 7; 124const int kPurgeStaleJobsIntervalBetweenChecksDays = 1; 125 126// Returns the status code from a given BITS error. 127int GetHttpStatusFromBitsError(HRESULT error) { 128 // BITS errors are defined in bitsmsg.h. Although not documented, it is 129 // clear that all errors corresponding to http status code have the high 130 // word equal to 0x8019 and the low word equal to the http status code. 131 const int kHttpStatusFirst = 100; // Continue. 132 const int kHttpStatusLast = 505; // Version not supported. 133 bool is_valid = HIWORD(error) == 0x8019 && 134 LOWORD(error) >= kHttpStatusFirst && 135 LOWORD(error) <= kHttpStatusLast; 136 return is_valid ? LOWORD(error) : 0; 137} 138 139// Returns the files in a BITS job. 140HRESULT GetFilesInJob(IBackgroundCopyJob* job, 141 std::vector<ScopedComPtr<IBackgroundCopyFile> >* files) { 142 ScopedComPtr<IEnumBackgroundCopyFiles> enum_files; 143 HRESULT hr = job->EnumFiles(enum_files.Receive()); 144 if (FAILED(hr)) 145 return hr; 146 147 ULONG num_files = 0; 148 hr = enum_files->GetCount(&num_files); 149 if (FAILED(hr)) 150 return hr; 151 152 for (ULONG i = 0; i != num_files; ++i) { 153 ScopedComPtr<IBackgroundCopyFile> file; 154 if (enum_files->Next(1, file.Receive(), NULL) == S_OK && file) 155 files->push_back(file); 156 } 157 158 return S_OK; 159} 160 161// Returns the file name, the url, and some per-file progress information. 162// The function out parameters can be NULL if that data is not requested. 163HRESULT GetJobFileProperties(IBackgroundCopyFile* file, 164 base::string16* local_name, 165 base::string16* remote_name, 166 BG_FILE_PROGRESS* progress) { 167 if (!file) 168 return E_FAIL; 169 170 HRESULT hr = S_OK; 171 172 if (local_name) { 173 ScopedCoMem<base::char16> name; 174 hr = file->GetLocalName(&name); 175 if (FAILED(hr)) 176 return hr; 177 local_name->assign(name); 178 } 179 180 if (remote_name) { 181 ScopedCoMem<base::char16> name; 182 hr = file->GetRemoteName(&name); 183 if (FAILED(hr)) 184 return hr; 185 remote_name->assign(name); 186 } 187 188 if (progress) { 189 BG_FILE_PROGRESS bg_file_progress = {}; 190 hr = file->GetProgress(&bg_file_progress); 191 if (FAILED(hr)) 192 return hr; 193 *progress = bg_file_progress; 194 } 195 196 return hr; 197} 198 199// Returns the number of bytes downloaded and bytes to download for all files 200// in the job. If the values are not known or if an error has occurred, 201// a value of -1 is reported. 202HRESULT GetJobByteCount(IBackgroundCopyJob* job, 203 int64* downloaded_bytes, 204 int64* total_bytes) { 205 *downloaded_bytes = -1; 206 *total_bytes = -1; 207 208 if (!job) 209 return E_FAIL; 210 211 BG_JOB_PROGRESS job_progress = {0}; 212 HRESULT hr = job->GetProgress(&job_progress); 213 if (FAILED(hr)) 214 return hr; 215 216 if (job_progress.BytesTransferred <= kint64max) 217 *downloaded_bytes = job_progress.BytesTransferred; 218 219 if (job_progress.BytesTotal <= kint64max && 220 job_progress.BytesTotal != BG_SIZE_UNKNOWN) 221 *total_bytes = job_progress.BytesTotal; 222 223 return S_OK; 224} 225 226HRESULT GetJobDescription(IBackgroundCopyJob* job, const base::string16* name) { 227 ScopedCoMem<base::char16> description; 228 return job->GetDescription(&description); 229} 230 231// Returns the job error code in |error_code| if the job is in the transient 232// or the final error state. Otherwise, the job error is not available and 233// the function fails. 234HRESULT GetJobError(IBackgroundCopyJob* job, HRESULT* error_code_out) { 235 *error_code_out = S_OK; 236 ScopedComPtr<IBackgroundCopyError> copy_error; 237 HRESULT hr = job->GetError(copy_error.Receive()); 238 if (FAILED(hr)) 239 return hr; 240 241 BG_ERROR_CONTEXT error_context = BG_ERROR_CONTEXT_NONE; 242 HRESULT error_code = S_OK; 243 hr = copy_error->GetError(&error_context, &error_code); 244 if (FAILED(hr)) 245 return hr; 246 247 *error_code_out = FAILED(error_code) ? error_code : E_FAIL; 248 return S_OK; 249} 250 251// Finds the component updater jobs matching the given predicate. 252// Returns S_OK if the function has found at least one job, returns S_FALSE if 253// no job was found, and it returns an error otherwise. 254template <class Predicate> 255HRESULT FindBitsJobIf(Predicate pred, 256 IBackgroundCopyManager* bits_manager, 257 std::vector<ScopedComPtr<IBackgroundCopyJob> >* jobs) { 258 ScopedComPtr<IEnumBackgroundCopyJobs> enum_jobs; 259 HRESULT hr = bits_manager->EnumJobs(0, enum_jobs.Receive()); 260 if (FAILED(hr)) 261 return hr; 262 263 ULONG job_count = 0; 264 hr = enum_jobs->GetCount(&job_count); 265 if (FAILED(hr)) 266 return hr; 267 268 // Iterate over jobs, run the predicate, and select the job only if 269 // the job description matches the component updater jobs. 270 for (ULONG i = 0; i != job_count; ++i) { 271 ScopedComPtr<IBackgroundCopyJob> current_job; 272 if (enum_jobs->Next(1, current_job.Receive(), NULL) == S_OK && 273 pred(current_job)) { 274 base::string16 job_description; 275 hr = GetJobDescription(current_job, &job_description); 276 if (job_description.compare(kJobDescription) == 0) 277 jobs->push_back(current_job); 278 } 279 } 280 281 return jobs->empty() ? S_FALSE : S_OK; 282} 283 284// Compares the job creation time and returns true if the job creation time 285// is older than |num_days|. 286struct JobCreationOlderThanDays 287 : public std::binary_function<IBackgroundCopyJob*, int, bool> { 288 bool operator()(IBackgroundCopyJob* job, int num_days) const; 289}; 290 291bool JobCreationOlderThanDays::operator()(IBackgroundCopyJob* job, 292 int num_days) const { 293 BG_JOB_TIMES times = {0}; 294 HRESULT hr = job->GetTimes(×); 295 if (FAILED(hr)) 296 return false; 297 298 const base::TimeDelta time_delta(base::TimeDelta::FromDays(num_days)); 299 const base::Time creation_time(base::Time::FromFileTime(times.CreationTime)); 300 301 return creation_time + time_delta < base::Time::Now(); 302} 303 304// Compares the url of a file in a job and returns true if the remote name 305// of any file in a job matches the argument. 306struct JobFileUrlEqual : public std::binary_function<IBackgroundCopyJob*, 307 const base::string16&, 308 bool> { 309 bool operator()(IBackgroundCopyJob* job, 310 const base::string16& remote_name) const; 311}; 312 313bool JobFileUrlEqual::operator()(IBackgroundCopyJob* job, 314 const base::string16& remote_name) const { 315 std::vector<ScopedComPtr<IBackgroundCopyFile> > files; 316 HRESULT hr = GetFilesInJob(job, &files); 317 if (FAILED(hr)) 318 return false; 319 320 for (size_t i = 0; i != files.size(); ++i) { 321 ScopedCoMem<base::char16> name; 322 if (SUCCEEDED(files[i]->GetRemoteName(&name)) && 323 remote_name.compare(name) == 0) 324 return true; 325 } 326 327 return false; 328} 329 330// Creates an instance of the BITS manager. 331HRESULT GetBitsManager(IBackgroundCopyManager** bits_manager) { 332 ScopedComPtr<IBackgroundCopyManager> object; 333 HRESULT hr = object.CreateInstance(__uuidof(BackgroundCopyManager)); 334 if (FAILED(hr)) { 335 return hr; 336 } 337 *bits_manager = object.Detach(); 338 return S_OK; 339} 340 341void CleanupJobFiles(IBackgroundCopyJob* job) { 342 std::vector<ScopedComPtr<IBackgroundCopyFile> > files; 343 if (FAILED(GetFilesInJob(job, &files))) 344 return; 345 for (size_t i = 0; i != files.size(); ++i) { 346 base::string16 local_name; 347 HRESULT hr(GetJobFileProperties(files[i], &local_name, NULL, NULL)); 348 if (SUCCEEDED(hr)) 349 DeleteFileAndEmptyParentDirectory(base::FilePath(local_name)); 350 } 351} 352 353// Cleans up incompleted jobs that are too old. 354HRESULT CleanupStaleJobs( 355 base::win::ScopedComPtr<IBackgroundCopyManager> bits_manager) { 356 if (!bits_manager) 357 return E_FAIL; 358 359 static base::Time last_sweep; 360 361 const base::TimeDelta time_delta( 362 base::TimeDelta::FromDays(kPurgeStaleJobsIntervalBetweenChecksDays)); 363 const base::Time current_time(base::Time::Now()); 364 if (last_sweep + time_delta > current_time) 365 return S_OK; 366 367 last_sweep = current_time; 368 369 std::vector<ScopedComPtr<IBackgroundCopyJob> > jobs; 370 HRESULT hr = FindBitsJobIf( 371 std::bind2nd(JobCreationOlderThanDays(), kPurgeStaleJobsAfterDays), 372 bits_manager, 373 &jobs); 374 if (FAILED(hr)) 375 return hr; 376 377 for (size_t i = 0; i != jobs.size(); ++i) { 378 jobs[i]->Cancel(); 379 CleanupJobFiles(jobs[i]); 380 } 381 382 return S_OK; 383} 384 385} // namespace 386 387BackgroundDownloader::BackgroundDownloader( 388 scoped_ptr<CrxDownloader> successor, 389 net::URLRequestContextGetter* context_getter, 390 scoped_refptr<base::SequencedTaskRunner> task_runner) 391 : CrxDownloader(successor.Pass()), 392 context_getter_(context_getter), 393 task_runner_(task_runner), 394 is_completed_(false) { 395 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 396} 397 398BackgroundDownloader::~BackgroundDownloader() { 399 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 400 401 // The following objects have thread affinity and can't be destroyed on the 402 // UI thread. The resources managed by these objects are acquired at the 403 // beginning of a download and released at the end of the download. Most of 404 // the time, when this destructor is called, these resources have been already 405 // disposed by. Releasing the ownership here is a NOP. However, if the browser 406 // is shutting down while a download is in progress, the timer is active and 407 // the interface pointers are valid. Releasing the ownership means leaking 408 // these objects and their associated resources. 409 timer_.release(); 410 bits_manager_.Detach(); 411 job_.Detach(); 412} 413 414void BackgroundDownloader::DoStartDownload(const GURL& url) { 415 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 416 417 BrowserThread::PostTask( 418 BrowserThread::FILE, 419 FROM_HERE, 420 base::Bind( 421 &BackgroundDownloader::BeginDownload, base::Unretained(this), url)); 422} 423 424// Called once when this class is asked to do a download. Creates or opens 425// an existing bits job, hooks up the notifications, and starts the timer. 426void BackgroundDownloader::BeginDownload(const GURL& url) { 427 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 428 429 DCHECK(!timer_); 430 431 is_completed_ = false; 432 download_start_time_ = base::Time::Now(); 433 job_stuck_begin_time_ = download_start_time_; 434 435 HRESULT hr = QueueBitsJob(url); 436 if (FAILED(hr)) { 437 EndDownload(hr); 438 return; 439 } 440 441 // A repeating timer retains the user task. This timer can be stopped and 442 // reset multiple times. 443 timer_.reset(new base::RepeatingTimer<BackgroundDownloader>); 444 timer_->Start(FROM_HERE, 445 base::TimeDelta::FromSeconds(kJobPollingIntervalSec), 446 this, 447 &BackgroundDownloader::OnDownloading); 448} 449 450// Called any time the timer fires. 451void BackgroundDownloader::OnDownloading() { 452 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 453 454 DCHECK(job_); 455 456 DCHECK(!is_completed_); 457 if (is_completed_) 458 return; 459 460 BG_JOB_STATE job_state = BG_JOB_STATE_ERROR; 461 HRESULT hr = job_->GetState(&job_state); 462 if (FAILED(hr)) { 463 EndDownload(hr); 464 return; 465 } 466 467 switch (job_state) { 468 case BG_JOB_STATE_TRANSFERRED: 469 OnStateTransferred(); 470 return; 471 472 case BG_JOB_STATE_ERROR: 473 OnStateError(); 474 return; 475 476 case BG_JOB_STATE_CANCELLED: 477 OnStateCancelled(); 478 return; 479 480 case BG_JOB_STATE_ACKNOWLEDGED: 481 OnStateAcknowledged(); 482 return; 483 484 case BG_JOB_STATE_QUEUED: 485 // Fall through. 486 case BG_JOB_STATE_CONNECTING: 487 // Fall through. 488 case BG_JOB_STATE_SUSPENDED: 489 OnStateQueued(); 490 break; 491 492 case BG_JOB_STATE_TRANSIENT_ERROR: 493 OnStateTransientError(); 494 break; 495 496 case BG_JOB_STATE_TRANSFERRING: 497 OnStateTransferring(); 498 break; 499 500 default: 501 break; 502 } 503} 504 505// Completes the BITS download, picks up the file path of the response, and 506// notifies the CrxDownloader. The function should be called only once. 507void BackgroundDownloader::EndDownload(HRESULT error) { 508 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 509 510 DCHECK(!is_completed_); 511 is_completed_ = true; 512 513 timer_.reset(); 514 515 const base::Time download_end_time(base::Time::Now()); 516 const base::TimeDelta download_time = 517 download_end_time >= download_start_time_ 518 ? download_end_time - download_start_time_ 519 : base::TimeDelta(); 520 521 int64 downloaded_bytes = -1; 522 int64 total_bytes = -1; 523 GetJobByteCount(job_, &downloaded_bytes, &total_bytes); 524 525 if (FAILED(error) && job_) { 526 job_->Cancel(); 527 CleanupJobFiles(job_); 528 } 529 530 job_ = NULL; 531 532 CleanupStaleJobs(bits_manager_); 533 bits_manager_ = NULL; 534 535 // Consider the url handled if it has been successfully downloaded or a 536 // 5xx has been received. 537 const bool is_handled = 538 SUCCEEDED(error) || IsHttpServerError(GetHttpStatusFromBitsError(error)); 539 540 const int error_to_report = SUCCEEDED(error) ? 0 : error; 541 542 DownloadMetrics download_metrics; 543 download_metrics.url = url(); 544 download_metrics.downloader = DownloadMetrics::kBits; 545 download_metrics.error = error_to_report; 546 download_metrics.downloaded_bytes = downloaded_bytes; 547 download_metrics.total_bytes = total_bytes; 548 download_metrics.download_time_ms = download_time.InMilliseconds(); 549 550 Result result; 551 result.error = error_to_report; 552 result.response = response_; 553 result.downloaded_bytes = downloaded_bytes; 554 result.total_bytes = total_bytes; 555 BrowserThread::PostTask(BrowserThread::UI, 556 FROM_HERE, 557 base::Bind(&BackgroundDownloader::OnDownloadComplete, 558 base::Unretained(this), 559 is_handled, 560 result, 561 download_metrics)); 562 563 // Once the task is posted to the the UI thread, this object may be deleted 564 // by its owner. It is not safe to access members of this object on the 565 // FILE thread from this point on. The timer is stopped and all BITS 566 // interface pointers have been released. 567} 568 569// Called when the BITS job has been transferred successfully. Completes the 570// BITS job by removing it from the BITS queue and making the download 571// available to the caller. 572void BackgroundDownloader::OnStateTransferred() { 573 EndDownload(CompleteJob()); 574} 575 576// Called when the job has encountered an error and no further progress can 577// be made. Cancels this job and removes it from the BITS queue. 578void BackgroundDownloader::OnStateError() { 579 HRESULT error_code = S_OK; 580 HRESULT hr = GetJobError(job_, &error_code); 581 if (FAILED(hr)) 582 error_code = hr; 583 DCHECK(FAILED(error_code)); 584 EndDownload(error_code); 585} 586 587// Called when the job has encountered a transient error, such as a 588// network disconnect, a server error, or some other recoverable error. 589void BackgroundDownloader::OnStateTransientError() { 590 // If the job appears to be stuck, handle the transient error as if 591 // it were a final error. This causes the job to be cancelled and a specific 592 // error be returned, if the error was available. 593 if (IsStuck()) { 594 OnStateError(); 595 return; 596 } 597 598 // Don't retry at all if the transient error was a 5xx. 599 HRESULT error_code = S_OK; 600 HRESULT hr = GetJobError(job_, &error_code); 601 if (SUCCEEDED(hr) && 602 IsHttpServerError(GetHttpStatusFromBitsError(error_code))) { 603 OnStateError(); 604 return; 605 } 606} 607 608void BackgroundDownloader::OnStateQueued() { 609 if (IsStuck()) 610 EndDownload(E_ABORT); // Return a generic error for now. 611} 612 613void BackgroundDownloader::OnStateTransferring() { 614 // Resets the baseline for detecting a stuck job since the job is transferring 615 // data and it is making progress. 616 job_stuck_begin_time_ = base::Time::Now(); 617 618 int64 downloaded_bytes = -1; 619 int64 total_bytes = -1; 620 HRESULT hr = GetJobByteCount(job_, &downloaded_bytes, &total_bytes); 621 if (FAILED(hr)) 622 return; 623 624 Result result; 625 result.downloaded_bytes = downloaded_bytes; 626 result.total_bytes = total_bytes; 627 628 BrowserThread::PostTask(BrowserThread::UI, 629 FROM_HERE, 630 base::Bind(&BackgroundDownloader::OnDownloadProgress, 631 base::Unretained(this), 632 result)); 633} 634 635// Called when the download was cancelled. Since the observer should have 636// been disconnected by now, this notification must not be seen. 637void BackgroundDownloader::OnStateCancelled() { 638 EndDownload(E_UNEXPECTED); 639} 640 641// Called when the download was completed. Same as above. 642void BackgroundDownloader::OnStateAcknowledged() { 643 EndDownload(E_UNEXPECTED); 644} 645 646// Creates or opens a job for the given url and queues it up. Tries to 647// install a job observer but continues on if an observer can't be set up. 648HRESULT BackgroundDownloader::QueueBitsJob(const GURL& url) { 649 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 650 651 HRESULT hr = S_OK; 652 if (bits_manager_ == NULL) { 653 hr = GetBitsManager(bits_manager_.Receive()); 654 if (FAILED(hr)) 655 return hr; 656 } 657 658 hr = CreateOrOpenJob(url); 659 if (FAILED(hr)) 660 return hr; 661 662 if (hr == S_OK) { 663 hr = InitializeNewJob(url); 664 if (FAILED(hr)) 665 return hr; 666 } 667 668 return job_->Resume(); 669} 670 671HRESULT BackgroundDownloader::CreateOrOpenJob(const GURL& url) { 672 std::vector<ScopedComPtr<IBackgroundCopyJob> > jobs; 673 HRESULT hr = FindBitsJobIf( 674 std::bind2nd(JobFileUrlEqual(), base::SysUTF8ToWide(url.spec())), 675 bits_manager_, 676 &jobs); 677 if (SUCCEEDED(hr) && !jobs.empty()) { 678 job_ = jobs.front(); 679 return S_FALSE; 680 } 681 682 // Use kJobDescription as a temporary job display name until the proper 683 // display name is initialized later on. 684 GUID guid = {0}; 685 ScopedComPtr<IBackgroundCopyJob> job; 686 hr = bits_manager_->CreateJob( 687 kJobDescription, BG_JOB_TYPE_DOWNLOAD, &guid, job.Receive()); 688 if (FAILED(hr)) 689 return hr; 690 691 job_ = job; 692 return S_OK; 693} 694 695HRESULT BackgroundDownloader::InitializeNewJob(const GURL& url) { 696 const base::string16 filename(base::SysUTF8ToWide(url.ExtractFileName())); 697 698 base::FilePath tempdir; 699 if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_BITS_"), 700 &tempdir)) 701 return E_FAIL; 702 703 HRESULT hr = job_->AddFile(base::SysUTF8ToWide(url.spec()).c_str(), 704 tempdir.Append(filename).AsUTF16Unsafe().c_str()); 705 if (FAILED(hr)) 706 return hr; 707 708 hr = job_->SetDisplayName(filename.c_str()); 709 if (FAILED(hr)) 710 return hr; 711 712 hr = job_->SetDescription(kJobDescription); 713 if (FAILED(hr)) 714 return hr; 715 716 hr = job_->SetPriority(BG_JOB_PRIORITY_NORMAL); 717 if (FAILED(hr)) 718 return hr; 719 720 hr = job_->SetMinimumRetryDelay(60 * kMinimumRetryDelayMin); 721 if (FAILED(hr)) 722 return hr; 723 724 const int kSecondsDay = 60 * 60 * 24; 725 hr = job_->SetNoProgressTimeout(kSecondsDay * kSetNoProgressTimeoutDays); 726 if (FAILED(hr)) 727 return hr; 728 729 return S_OK; 730} 731 732bool BackgroundDownloader::IsStuck() { 733 const base::TimeDelta job_stuck_timeout( 734 base::TimeDelta::FromMinutes(kJobStuckTimeoutMin)); 735 return job_stuck_begin_time_ + job_stuck_timeout < base::Time::Now(); 736} 737 738HRESULT BackgroundDownloader::CompleteJob() { 739 HRESULT hr = job_->Complete(); 740 if (FAILED(hr) && hr != BG_S_UNABLE_TO_DELETE_FILES) 741 return hr; 742 743 std::vector<ScopedComPtr<IBackgroundCopyFile> > files; 744 hr = GetFilesInJob(job_, &files); 745 if (FAILED(hr)) 746 return hr; 747 748 if (files.empty()) 749 return E_UNEXPECTED; 750 751 base::string16 local_name; 752 BG_FILE_PROGRESS progress = {0}; 753 hr = GetJobFileProperties(files.front(), &local_name, NULL, &progress); 754 if (FAILED(hr)) 755 return hr; 756 757 // Sanity check the post-conditions of a successful download, including 758 // the file and job invariants. The byte counts for a job and its file 759 // must match as a job only contains one file. 760 DCHECK(progress.Completed); 761 DCHECK_EQ(progress.BytesTotal, progress.BytesTransferred); 762 763 response_ = base::FilePath(local_name); 764 765 return S_OK; 766} 767 768} // namespace component_updater 769