• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
27 using base::win::ScopedCoMem;
28 using 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 
96 namespace component_updater {
97 
98 namespace {
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.
102 const base::char16 kJobDescription[] = L"Chrome Component Updater";
103 
104 // How often the code looks for changes in the BITS job state.
105 const 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.
109 const 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.
114 const 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.
119 const 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.
126 const int kPurgeStaleJobsAfterDays = 7;
127 const int kPurgeStaleJobsIntervalBetweenChecksDays = 1;
128 
129 // Returns the status code from a given BITS error.
GetHttpStatusFromBitsError(HRESULT error)130 int 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.
GetFilesInJob(IBackgroundCopyJob * job,std::vector<ScopedComPtr<IBackgroundCopyFile>> * files)143 HRESULT 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.
GetJobFileProperties(IBackgroundCopyFile * file,base::string16 * local_name,base::string16 * remote_name,BG_FILE_PROGRESS * progress)166 HRESULT 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.
GetJobByteCount(IBackgroundCopyJob * job,int64_t * downloaded_bytes,int64_t * total_bytes)205 HRESULT 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 
GetJobDescription(IBackgroundCopyJob * job,const base::string16 * name)231 HRESULT 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.
GetJobError(IBackgroundCopyJob * job,HRESULT * error_code_out)239 HRESULT 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.
259 template <class Predicate>
FindBitsJobIf(Predicate pred,IBackgroundCopyManager * bits_manager,std::vector<ScopedComPtr<IBackgroundCopyJob>> * jobs)260 HRESULT 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|.
291 struct JobCreationOlderThanDays
292     : public std::binary_function<IBackgroundCopyJob*, int, bool> {
293   bool operator()(IBackgroundCopyJob* job, int num_days) const;
294 };
295 
operator ()(IBackgroundCopyJob * job,int num_days) const296 bool 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.
311 struct 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 
operator ()(IBackgroundCopyJob * job,const base::string16 & remote_name) const318 bool 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.
GetBitsManager(IBackgroundCopyManager ** bits_manager)336 HRESULT 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 
CleanupJobFiles(IBackgroundCopyJob * job)346 void 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.
CleanupStaleJobs(base::win::ScopedComPtr<IBackgroundCopyManager> bits_manager)359 HRESULT 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 
BackgroundDownloader(scoped_ptr<CrxDownloader> successor,net::URLRequestContextGetter * context_getter,scoped_refptr<base::SingleThreadTaskRunner> task_runner)392 BackgroundDownloader::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 
~BackgroundDownloader()403 BackgroundDownloader::~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 
DoStartDownload(const GURL & url)419 void 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.
BeginDownload(const GURL & url)430 void 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.
OnDownloading()455 void 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.
EndDownload(HRESULT error)511 void 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.
OnStateTransferred()576 void 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.
OnStateError()582 void 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.
OnStateTransientError()593 void 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 
OnStateQueued()612 void BackgroundDownloader::OnStateQueued() {
613   if (IsStuck())
614     EndDownload(E_ABORT);  // Return a generic error for now.
615 }
616 
OnStateTransferring()617 void 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.
OnStateCancelled()641 void BackgroundDownloader::OnStateCancelled() {
642   EndDownload(E_UNEXPECTED);
643 }
644 
645 // Called when the download was completed. Same as above.
OnStateAcknowledged()646 void 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.
QueueBitsJob(const GURL & url)652 HRESULT 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 
CreateOrOpenJob(const GURL & url)675 HRESULT 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 
InitializeNewJob(const GURL & url)699 HRESULT 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 
IsStuck()736 bool 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 
CompleteJob()742 HRESULT 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