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(&times);
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