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 char16 kJobDescription[] = L"Chrome Component Updater";
100
101 // How often the code looks for changes in the BITS job state.
102 const int kJobPollingIntervalSec = 10;
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)
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 HRESULT hr = S_OK;
168
169 if (local_name) {
170 ScopedCoMem<char16> name;
171 hr = file->GetLocalName(&name);
172 if (FAILED(hr))
173 return hr;
174 local_name->assign(name);
175 }
176
177 if (remote_name) {
178 ScopedCoMem<char16> name;
179 hr = file->GetRemoteName(&name);
180 if (FAILED(hr))
181 return hr;
182 remote_name->assign(name);
183 }
184
185 if (progress) {
186 BG_FILE_PROGRESS bg_file_progress = {};
187 hr = file->GetProgress(&bg_file_progress);
188 if (FAILED(hr))
189 return hr;
190 *progress = bg_file_progress;
191 }
192
193 return hr;
194 }
195
196 // Returns the number of bytes downloaded and bytes to download for all files
197 // in the job. If the values are not known or if an error has occurred,
198 // a value of -1 is reported.
GetJobByteCount(IBackgroundCopyJob * job,int64 * bytes_downloaded,int64 * bytes_total)199 HRESULT GetJobByteCount(IBackgroundCopyJob* job,
200 int64* bytes_downloaded,
201 int64* bytes_total) {
202 *bytes_downloaded = -1;
203 *bytes_total = -1;
204
205 if (!job)
206 return E_FAIL;
207
208 BG_JOB_PROGRESS job_progress = {0};
209 HRESULT hr = job->GetProgress(&job_progress);
210 if (FAILED(hr))
211 return hr;
212
213 if (job_progress.BytesTransferred <= kint64max)
214 *bytes_downloaded = job_progress.BytesTransferred;
215
216 if (job_progress.BytesTotal <= kint64max &&
217 job_progress.BytesTotal != BG_SIZE_UNKNOWN)
218 *bytes_total = job_progress.BytesTotal;
219
220 return S_OK;
221 }
222
GetJobDescription(IBackgroundCopyJob * job,const base::string16 * name)223 HRESULT GetJobDescription(IBackgroundCopyJob* job, const base::string16* name) {
224 ScopedCoMem<char16> description;
225 return job->GetDescription(&description);
226 }
227
228 // Returns the job error code in |error_code| if the job is in the transient
229 // or the final error state. Otherwise, the job error is not available and
230 // the function fails.
GetJobError(IBackgroundCopyJob * job,HRESULT * error_code_out)231 HRESULT GetJobError(IBackgroundCopyJob* job, HRESULT* error_code_out) {
232 *error_code_out = S_OK;
233 ScopedComPtr<IBackgroundCopyError> copy_error;
234 HRESULT hr = job->GetError(copy_error.Receive());
235 if (FAILED(hr))
236 return hr;
237
238 BG_ERROR_CONTEXT error_context = BG_ERROR_CONTEXT_NONE;
239 HRESULT error_code = S_OK;
240 hr = copy_error->GetError(&error_context, &error_code);
241 if (FAILED(hr))
242 return hr;
243
244 *error_code_out = FAILED(error_code) ? error_code : E_FAIL;
245 return S_OK;
246 }
247
248 // Finds the component updater jobs matching the given predicate.
249 // Returns S_OK if the function has found at least one job, returns S_FALSE if
250 // no job was found, and it returns an error otherwise.
251 template<class Predicate>
FindBitsJobIf(Predicate pred,IBackgroundCopyManager * bits_manager,std::vector<ScopedComPtr<IBackgroundCopyJob>> * jobs)252 HRESULT FindBitsJobIf(Predicate pred,
253 IBackgroundCopyManager* bits_manager,
254 std::vector<ScopedComPtr<IBackgroundCopyJob> >* jobs) {
255 ScopedComPtr<IEnumBackgroundCopyJobs> enum_jobs;
256 HRESULT hr = bits_manager->EnumJobs(0, enum_jobs.Receive());
257 if (FAILED(hr))
258 return hr;
259
260 ULONG job_count = 0;
261 hr = enum_jobs->GetCount(&job_count);
262 if (FAILED(hr))
263 return hr;
264
265 // Iterate over jobs, run the predicate, and select the job only if
266 // the job description matches the component updater jobs.
267 for (ULONG i = 0; i != job_count; ++i) {
268 ScopedComPtr<IBackgroundCopyJob> current_job;
269 if (enum_jobs->Next(1, current_job.Receive(), NULL) == S_OK &&
270 pred(current_job)) {
271 base::string16 job_description;
272 hr = GetJobDescription(current_job, &job_description);
273 if (job_description.compare(kJobDescription) == 0)
274 jobs->push_back(current_job);
275 }
276 }
277
278 return jobs->empty() ? S_FALSE : S_OK;
279 }
280
281 // Compares the job creation time and returns true if the job creation time
282 // is older than |num_days|.
283 struct JobCreationOlderThanDays
284 : public std::binary_function<IBackgroundCopyJob*, int, bool> {
285 bool operator()(IBackgroundCopyJob* job, int num_days) const;
286 };
287
operator ()(IBackgroundCopyJob * job,int num_days) const288 bool JobCreationOlderThanDays::operator()(IBackgroundCopyJob* job,
289 int num_days) const {
290 BG_JOB_TIMES times = {0};
291 HRESULT hr = job->GetTimes(×);
292 if (FAILED(hr))
293 return false;
294
295 const base::TimeDelta time_delta(base::TimeDelta::FromDays(num_days));
296 const base::Time creation_time(base::Time::FromFileTime(times.CreationTime));
297
298 return creation_time + time_delta < base::Time::Now();
299 }
300
301 // Compares the url of a file in a job and returns true if the remote name
302 // of any file in a job matches the argument.
303 struct JobFileUrlEqual
304 : public std::binary_function<IBackgroundCopyJob*, const base::string16&,
305 bool> {
306 bool operator()(IBackgroundCopyJob* job,
307 const base::string16& remote_name) const;
308 };
309
operator ()(IBackgroundCopyJob * job,const base::string16 & remote_name) const310 bool JobFileUrlEqual::operator()(IBackgroundCopyJob* job,
311 const base::string16& remote_name) const {
312 std::vector<ScopedComPtr<IBackgroundCopyFile> > files;
313 HRESULT hr = GetFilesInJob(job, &files);
314 if (FAILED(hr))
315 return false;
316
317 for (size_t i = 0; i != files.size(); ++i) {
318 ScopedCoMem<char16> name;
319 if (SUCCEEDED(files[i]->GetRemoteName(&name)) &&
320 remote_name.compare(name) == 0)
321 return true;
322 }
323
324 return false;
325 }
326
327 // Creates an instance of the BITS manager.
GetBitsManager(IBackgroundCopyManager ** bits_manager)328 HRESULT GetBitsManager(IBackgroundCopyManager** bits_manager) {
329 ScopedComPtr<IBackgroundCopyManager> object;
330 HRESULT hr = object.CreateInstance(__uuidof(BackgroundCopyManager));
331 if (FAILED(hr)) {
332 VLOG(1) << "Failed to instantiate BITS." << std::hex << hr;
333 // TODO: add UMA pings.
334 return hr;
335 }
336 *bits_manager = object.Detach();
337 return S_OK;
338 }
339
CleanupJobFiles(IBackgroundCopyJob * job)340 void CleanupJobFiles(IBackgroundCopyJob* job) {
341 std::vector<ScopedComPtr<IBackgroundCopyFile> > files;
342 if (FAILED(GetFilesInJob(job, &files)))
343 return;
344 for (size_t i = 0; i != files.size(); ++i) {
345 base::string16 local_name;
346 HRESULT hr(GetJobFileProperties(files[i], &local_name, NULL, NULL));
347 if (SUCCEEDED(hr))
348 DeleteFileAndEmptyParentDirectory(base::FilePath(local_name));
349 }
350 }
351
352 // Cleans up incompleted jobs that are too old.
CleanupStaleJobs(base::win::ScopedComPtr<IBackgroundCopyManager> bits_manager)353 HRESULT CleanupStaleJobs(
354 base::win::ScopedComPtr<IBackgroundCopyManager> bits_manager) {
355 if (!bits_manager)
356 return E_FAIL;
357
358 static base::Time last_sweep;
359
360 const base::TimeDelta time_delta(base::TimeDelta::FromDays(
361 kPurgeStaleJobsIntervalBetweenChecksDays));
362 const base::Time current_time(base::Time::Now());
363 if (last_sweep + time_delta > current_time)
364 return S_OK;
365
366 last_sweep = current_time;
367
368 std::vector<ScopedComPtr<IBackgroundCopyJob> > jobs;
369 HRESULT hr = FindBitsJobIf(
370 std::bind2nd(JobCreationOlderThanDays(), kPurgeStaleJobsAfterDays),
371 bits_manager,
372 &jobs);
373 if (FAILED(hr))
374 return hr;
375
376 for (size_t i = 0; i != jobs.size(); ++i) {
377 jobs[i]->Cancel();
378 CleanupJobFiles(jobs[i]);
379 }
380
381 return S_OK;
382 }
383
384 } // namespace
385
BackgroundDownloader(scoped_ptr<CrxDownloader> successor,net::URLRequestContextGetter * context_getter,scoped_refptr<base::SequencedTaskRunner> task_runner,const DownloadCallback & download_callback)386 BackgroundDownloader::BackgroundDownloader(
387 scoped_ptr<CrxDownloader> successor,
388 net::URLRequestContextGetter* context_getter,
389 scoped_refptr<base::SequencedTaskRunner> task_runner,
390 const DownloadCallback& download_callback)
391 : CrxDownloader(successor.Pass(), download_callback),
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 }
400
DoStartDownload(const GURL & url)401 void BackgroundDownloader::DoStartDownload(const GURL& url) {
402 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
403
404 BrowserThread::PostTask(
405 BrowserThread::FILE,
406 FROM_HERE,
407 base::Bind(&BackgroundDownloader::BeginDownload,
408 base::Unretained(this),
409 url));
410 }
411
412 // Called once when this class is asked to do a download. Creates or opens
413 // an existing bits job, hooks up the notifications, and starts the timer.
BeginDownload(const GURL & url)414 void BackgroundDownloader::BeginDownload(const GURL& url) {
415 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
416
417 DCHECK(!timer_);
418
419 is_completed_ = false;
420 download_start_time_ = base::Time::Now();
421 job_stuck_begin_time_ = download_start_time_;
422
423 HRESULT hr = QueueBitsJob(url);
424 if (FAILED(hr)) {
425 EndDownload(hr);
426 return;
427 }
428
429 // A repeating timer retains the user task. This timer can be stopped and
430 // reset multiple times.
431 timer_.reset(new base::RepeatingTimer<BackgroundDownloader>);
432 timer_->Start(FROM_HERE,
433 base::TimeDelta::FromSeconds(kJobPollingIntervalSec),
434 this,
435 &BackgroundDownloader::OnDownloading);
436 }
437
438 // Called any time the timer fires.
OnDownloading()439 void BackgroundDownloader::OnDownloading() {
440 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
441
442 DCHECK(job_);
443
444 DCHECK(!is_completed_);
445 if (is_completed_)
446 return;
447
448 BG_JOB_STATE job_state = BG_JOB_STATE_ERROR;
449 HRESULT hr = job_->GetState(&job_state);
450 if (FAILED(hr)) {
451 EndDownload(hr);
452 return;
453 }
454
455 switch (job_state) {
456 case BG_JOB_STATE_TRANSFERRED:
457 OnStateTransferred();
458 return;
459
460 case BG_JOB_STATE_ERROR:
461 OnStateError();
462 return;
463
464 case BG_JOB_STATE_CANCELLED:
465 OnStateCancelled();
466 return;
467
468 case BG_JOB_STATE_ACKNOWLEDGED:
469 OnStateAcknowledged();
470 return;
471
472 case BG_JOB_STATE_QUEUED:
473 // Fall through.
474 case BG_JOB_STATE_CONNECTING:
475 // Fall through.
476 case BG_JOB_STATE_SUSPENDED:
477 OnStateQueued();
478 break;
479
480 case BG_JOB_STATE_TRANSIENT_ERROR:
481 OnStateTransientError();
482 break;
483
484 case BG_JOB_STATE_TRANSFERRING:
485 OnStateTransferring();
486 break;
487
488 default:
489 break;
490 }
491 }
492
493 // Completes the BITS download, picks up the file path of the response, and
494 // notifies the CrxDownloader. The function should be called only once.
EndDownload(HRESULT error)495 void BackgroundDownloader::EndDownload(HRESULT error) {
496 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
497
498 DCHECK(!is_completed_);
499 is_completed_ = true;
500
501 timer_.reset();
502
503 const base::Time download_end_time(base::Time::Now());
504 const base::TimeDelta download_time =
505 download_end_time >= download_start_time_ ?
506 download_end_time - download_start_time_ : base::TimeDelta();
507
508 int64 bytes_downloaded = -1;
509 int64 bytes_total = -1;
510 GetJobByteCount(job_, &bytes_downloaded, &bytes_total);
511
512 base::FilePath response;
513 if (SUCCEEDED(error)) {
514 DCHECK(job_);
515 std::vector<ScopedComPtr<IBackgroundCopyFile> > files;
516 GetFilesInJob(job_, &files);
517 DCHECK(files.size() == 1);
518 base::string16 local_name;
519 BG_FILE_PROGRESS progress = {0};
520 HRESULT hr = GetJobFileProperties(files[0], &local_name, NULL, &progress);
521 if (SUCCEEDED(hr)) {
522 // Sanity check the post-conditions of a successful download, including
523 // the file and job invariants. The byte counts for a job and its file
524 // must match as a job only contains one file.
525 DCHECK(progress.Completed);
526 DCHECK(bytes_downloaded == static_cast<int64>(progress.BytesTransferred));
527 DCHECK(bytes_total == static_cast<int64>(progress.BytesTotal));
528 response = base::FilePath(local_name);
529 if (progress.BytesTransferred <= kint64max)
530 bytes_downloaded = progress.BytesTransferred;
531 if (progress.BytesTotal <= kint64max)
532 bytes_total = progress.BytesTotal;
533 } else {
534 error = hr;
535 }
536 }
537
538 if (FAILED(error) && job_) {
539 job_->Cancel();
540 CleanupJobFiles(job_);
541 }
542
543 job_ = NULL;
544
545 // Consider the url handled if it has been successfully downloaded or a
546 // 5xx has been received.
547 const bool is_handled = SUCCEEDED(error) ||
548 IsHttpServerError(GetHttpStatusFromBitsError(error));
549
550 const int error_to_report = SUCCEEDED(error) ? 0 : error;
551
552 DownloadMetrics download_metrics;
553 download_metrics.url = url();
554 download_metrics.downloader = DownloadMetrics::kBits;
555 download_metrics.error = error_to_report;
556 download_metrics.bytes_downloaded = bytes_downloaded;
557 download_metrics.bytes_total = bytes_total;
558 download_metrics.download_time_ms = download_time.InMilliseconds();
559
560 // Clean up stale jobs before invoking the callback.
561 CleanupStaleJobs(bits_manager_);
562
563 bits_manager_ = NULL;
564
565 Result result;
566 result.error = error_to_report;
567 result.response = response;
568 BrowserThread::PostTask(
569 BrowserThread::UI,
570 FROM_HERE,
571 base::Bind(&BackgroundDownloader::OnDownloadComplete,
572 base::Unretained(this),
573 is_handled,
574 result,
575 download_metrics));
576
577 // Once the task is posted to the the UI thread, this object may be deleted
578 // by its owner. It is not safe to access members of this object on the
579 // FILE thread from this point on. The timer is stopped and all BITS
580 // interface pointers have been released.
581 }
582
583 // Called when the BITS job has been transferred successfully. Completes the
584 // BITS job by removing it from the BITS queue and making the download
585 // available to the caller.
OnStateTransferred()586 void BackgroundDownloader::OnStateTransferred() {
587 HRESULT hr = job_->Complete();
588 if (SUCCEEDED(hr) || hr == BG_S_UNABLE_TO_DELETE_FILES)
589 hr = S_OK;
590 EndDownload(hr);
591 }
592
593 // Called when the job has encountered an error and no further progress can
594 // be made. Cancels this job and removes it from the BITS queue.
OnStateError()595 void BackgroundDownloader::OnStateError() {
596 HRESULT error_code = S_OK;
597 HRESULT hr = GetJobError(job_, &error_code);
598 if (FAILED(hr))
599 error_code = hr;
600 DCHECK(FAILED(error_code));
601 EndDownload(error_code);
602 }
603
604 // Called when the job has encountered a transient error, such as a
605 // network disconnect, a server error, or some other recoverable error.
OnStateTransientError()606 void BackgroundDownloader::OnStateTransientError() {
607 // If the job appears to be stuck, handle the transient error as if
608 // it were a final error. This causes the job to be cancelled and a specific
609 // error be returned, if the error was available.
610 if (IsStuck()) {
611 OnStateError();
612 return;
613 }
614
615 // Don't retry at all if the transient error was a 5xx.
616 HRESULT error_code = S_OK;
617 HRESULT hr = GetJobError(job_, &error_code);
618 if (SUCCEEDED(hr) &&
619 IsHttpServerError(GetHttpStatusFromBitsError(error_code))) {
620 OnStateError();
621 return;
622 }
623 }
624
OnStateQueued()625 void BackgroundDownloader::OnStateQueued() {
626 if (IsStuck())
627 EndDownload(E_ABORT); // Return a generic error for now.
628 }
629
OnStateTransferring()630 void BackgroundDownloader::OnStateTransferring() {
631 // Resets the baseline for detecting a stuck job since the job is transferring
632 // data and it is making progress.
633 job_stuck_begin_time_ = base::Time::Now();
634 }
635
636 // Called when the download was cancelled. Since the observer should have
637 // been disconnected by now, this notification must not be seen.
OnStateCancelled()638 void BackgroundDownloader::OnStateCancelled() {
639 EndDownload(E_UNEXPECTED);
640 }
641
642 // Called when the download was completed. Same as above.
OnStateAcknowledged()643 void BackgroundDownloader::OnStateAcknowledged() {
644 EndDownload(E_UNEXPECTED);
645 }
646
647 // Creates or opens a job for the given url and queues it up. Tries to
648 // install a job observer but continues on if an observer can't be set up.
QueueBitsJob(const GURL & url)649 HRESULT BackgroundDownloader::QueueBitsJob(const GURL& url) {
650 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
651
652 HRESULT hr = S_OK;
653 if (bits_manager_ == NULL) {
654 hr = GetBitsManager(bits_manager_.Receive());
655 if (FAILED(hr))
656 return hr;
657 }
658
659 hr = CreateOrOpenJob(url);
660 if (FAILED(hr))
661 return hr;
662
663 if (hr == S_OK) {
664 hr = InitializeNewJob(url);
665 if (FAILED(hr))
666 return hr;
667 }
668
669 return job_->Resume();
670 }
671
CreateOrOpenJob(const GURL & url)672 HRESULT BackgroundDownloader::CreateOrOpenJob(const GURL& url) {
673 std::vector<ScopedComPtr<IBackgroundCopyJob> > jobs;
674 HRESULT hr = FindBitsJobIf(
675 std::bind2nd(JobFileUrlEqual(), base::SysUTF8ToWide(url.spec())),
676 bits_manager_,
677 &jobs);
678 if (SUCCEEDED(hr) && !jobs.empty()) {
679 job_ = jobs.front();
680 return S_FALSE;
681 }
682
683 // Use kJobDescription as a temporary job display name until the proper
684 // display name is initialized later on.
685 GUID guid = {0};
686 ScopedComPtr<IBackgroundCopyJob> job;
687 hr = bits_manager_->CreateJob(kJobDescription,
688 BG_JOB_TYPE_DOWNLOAD,
689 &guid,
690 job.Receive());
691 if (FAILED(hr))
692 return hr;
693
694 job_ = job;
695 return S_OK;
696 }
697
InitializeNewJob(const GURL & url)698 HRESULT BackgroundDownloader::InitializeNewJob(const GURL& url) {
699 const base::string16 filename(base::SysUTF8ToWide(url.ExtractFileName()));
700
701 base::FilePath tempdir;
702 if (!base::CreateNewTempDirectory(
703 FILE_PATH_LITERAL("chrome_BITS_"),
704 &tempdir))
705 return E_FAIL;
706
707 HRESULT hr = job_->AddFile(
708 base::SysUTF8ToWide(url.spec()).c_str(),
709 tempdir.Append(filename).AsUTF16Unsafe().c_str());
710 if (FAILED(hr))
711 return hr;
712
713 hr = job_->SetDisplayName(filename.c_str());
714 if (FAILED(hr))
715 return hr;
716
717 hr = job_->SetDescription(kJobDescription);
718 if (FAILED(hr))
719 return hr;
720
721 hr = job_->SetPriority(BG_JOB_PRIORITY_NORMAL);
722 if (FAILED(hr))
723 return hr;
724
725 hr = job_->SetMinimumRetryDelay(60 * kMinimumRetryDelayMin);
726 if (FAILED(hr))
727 return hr;
728
729 const int kSecondsDay = 60 * 60 * 24;
730 hr = job_->SetNoProgressTimeout(kSecondsDay * kSetNoProgressTimeoutDays);
731 if (FAILED(hr))
732 return hr;
733
734 return S_OK;
735 }
736
IsStuck()737 bool BackgroundDownloader::IsStuck() {
738 const base::TimeDelta job_stuck_timeout(
739 base::TimeDelta::FromMinutes(kJobStuckTimeoutMin));
740 return job_stuck_begin_time_ + job_stuck_timeout < base::Time::Now();
741 }
742
743 } // namespace component_updater
744
745