• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
22 using base::win::ScopedCoMem;
23 using base::win::ScopedComPtr;
24 using 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 
93 namespace component_updater {
94 
95 namespace {
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.
99 const base::char16 kJobDescription[] = L"Chrome Component Updater";
100 
101 // How often the code looks for changes in the BITS job state.
102 const 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.
106 const 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.
111 const 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.
116 const 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.
123 const int kPurgeStaleJobsAfterDays = 7;
124 const int kPurgeStaleJobsIntervalBetweenChecksDays = 1;
125 
126 // Returns the status code from a given BITS error.
GetHttpStatusFromBitsError(HRESULT error)127 int 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.
GetFilesInJob(IBackgroundCopyJob * job,std::vector<ScopedComPtr<IBackgroundCopyFile>> * files)140 HRESULT 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.
GetJobFileProperties(IBackgroundCopyFile * file,base::string16 * local_name,base::string16 * remote_name,BG_FILE_PROGRESS * progress)163 HRESULT 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.
GetJobByteCount(IBackgroundCopyJob * job,int64 * downloaded_bytes,int64 * total_bytes)202 HRESULT 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 
GetJobDescription(IBackgroundCopyJob * job,const base::string16 * name)226 HRESULT 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.
GetJobError(IBackgroundCopyJob * job,HRESULT * error_code_out)234 HRESULT 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.
254 template <class Predicate>
FindBitsJobIf(Predicate pred,IBackgroundCopyManager * bits_manager,std::vector<ScopedComPtr<IBackgroundCopyJob>> * jobs)255 HRESULT 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|.
286 struct JobCreationOlderThanDays
287     : public std::binary_function<IBackgroundCopyJob*, int, bool> {
288   bool operator()(IBackgroundCopyJob* job, int num_days) const;
289 };
290 
operator ()(IBackgroundCopyJob * job,int num_days) const291 bool 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.
306 struct 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 
operator ()(IBackgroundCopyJob * job,const base::string16 & remote_name) const313 bool 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.
GetBitsManager(IBackgroundCopyManager ** bits_manager)331 HRESULT 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 
CleanupJobFiles(IBackgroundCopyJob * job)341 void 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.
CleanupStaleJobs(base::win::ScopedComPtr<IBackgroundCopyManager> bits_manager)354 HRESULT 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 
BackgroundDownloader(scoped_ptr<CrxDownloader> successor,net::URLRequestContextGetter * context_getter,scoped_refptr<base::SequencedTaskRunner> task_runner)387 BackgroundDownloader::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 
~BackgroundDownloader()398 BackgroundDownloader::~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 
DoStartDownload(const GURL & url)414 void 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.
BeginDownload(const GURL & url)426 void 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.
OnDownloading()451 void 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.
EndDownload(HRESULT error)507 void 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.
OnStateTransferred()572 void 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.
OnStateError()578 void 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.
OnStateTransientError()589 void 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 
OnStateQueued()608 void BackgroundDownloader::OnStateQueued() {
609   if (IsStuck())
610     EndDownload(E_ABORT);  // Return a generic error for now.
611 }
612 
OnStateTransferring()613 void 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.
OnStateCancelled()637 void BackgroundDownloader::OnStateCancelled() {
638   EndDownload(E_UNEXPECTED);
639 }
640 
641 // Called when the download was completed. Same as above.
OnStateAcknowledged()642 void 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.
QueueBitsJob(const GURL & url)648 HRESULT 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 
CreateOrOpenJob(const GURL & url)671 HRESULT 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 
InitializeNewJob(const GURL & url)695 HRESULT 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 
IsStuck()732 bool 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 
CompleteJob()738 HRESULT 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