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