Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(312)

Side by Side Diff: chrome/browser/component_updater/background_downloader_win.cc

Issue 105853002: Implement a background downloader using BITS in Windows Chrome. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: removed useless helpers, fixed timer assert. Created 7 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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/time/time.h"
17 #include "base/win/scoped_co_mem.h"
18 #include "chrome/browser/component_updater/component_updater_utils.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "ui/base/win/atl_module.h"
21 #include "url/gurl.h"
22
23 using base::win::ScopedCoMem;
24 using base::win::ScopedComPtr;
25 using content::BrowserThread;
26
27 // The class BackgroundDownloader in this module is an adapter between
28 // the CrxDownloader interface and the BITS service interfaces.
29 // The interface exposed on the CrxDownloader code runs on the UI thread, while
30 // the BITS specific code runs in a single threaded apartment on the FILE
31 // thread.
32 // For every url to download, a BITS job is created, unless there is already
33 // an existing job for that url, in which case, the downloader connects to it.
34 // Once a job is associated with the url, the code looks for changes in the
35 // BITS job state.
36 // The changes arrive either as COM callbacks (due to the threading model, the
37 // events always arrive on the FILE thread) or by polling, triggered by a timer,
38 // as a last resort. The BITS job contains just one file to download. There
39 // could only be one download in progress at a time. If Chrome closes down
40 // before the download is complete, the BITS job remains active and finishes in
41 // the background, without any intervention. The job can be completed next time
42 // the code runs, if the file is still needed, otherwise it will be cleaned up
43 // on a periodic basis.
44 //
45 // To list the BITS jobs for a user, use the |bitsadmin| tool. The command line
46 // to do that is: "bitsadmin /list /verbose". Another useful command is
47 // "bitsadmin /info" and provide the job id returned by the previous /list
48 // command.
49 namespace component_updater {
50
51 namespace {
52
53 // All jobs created by this module have a specific description so they can
54 // be found at run-time or by using system administration tools.
55 const char16 kJobDescription[] = L"Chrome Component Updater";
56
57 // How often the code looks for changes in the BITS job state.
58 const int kJobPollingIntervalSec = 10;
59
60 // How often the jobs which were started but not completed for any reason
61 // are cleaned up. Reasons for jobs to be left behind include browser restarts,
62 // system restarts, etc. Also, the check to purge stale jobs only happens
63 // at most once a day.
64 const int kPurgeStaleJobsAfterDays = 7;
65 const int kPurgeStaleJobsIntervalBetweenChecksDays = 1;
66
67 // Returns the status code from a given BITS error.
68 int GetHttpStatusFromBitsError(HRESULT error) {
69 // BITS errors are defined in bitsmsg.h. Although not documented, it is
70 // clear that all errors corresponding to http status code have the high
71 // word equal to 0x8019 and the low word equal to the http status code.
72 const int kHttpStatusFirst = 100; // Continue.
73 const int kHttpStatusLast = 505; // Version not supported.
74 bool is_valid = HIWORD(error) == 0x8019 &&
75 LOWORD(error) >= kHttpStatusFirst &&
76 LOWORD(error) <= kHttpStatusLast;
77 return is_valid ? LOWORD(error) : 0;
78 }
79
80 // Returns the files in a BITS job.
81 HRESULT GetFilesInJob(IBackgroundCopyJob* job,
82 std::vector<ScopedComPtr<IBackgroundCopyFile> >* files) {
83 ScopedComPtr<IEnumBackgroundCopyFiles> enum_files;
84 HRESULT hr = job->EnumFiles(enum_files.Receive());
85 if (FAILED(hr))
86 return hr;
87
88 ULONG num_files = 0;
89 hr = enum_files->GetCount(&num_files);
90 if (FAILED(hr))
91 return hr;
92
93 for (ULONG i = 0; i != num_files; ++i) {
94 ScopedComPtr<IBackgroundCopyFile> file;
95 if (enum_files->Next(1, file.Receive(), NULL) == S_OK)
96 files->push_back(file);
97 }
98
99 return S_OK;
100 }
101
102 // Returns the file name, the url, and some per-file progress information.
103 // The function out parameters can be NULL if that data is not requested.
104 HRESULT GetJobFileProperties(IBackgroundCopyFile* file,
105 string16* local_name,
106 string16* remote_name,
107 BG_FILE_PROGRESS* progress) {
108 HRESULT hr = S_OK;
109
110 if (local_name) {
111 ScopedCoMem<char16> name;
112 hr = file->GetLocalName(&name);
113 if (FAILED(hr))
114 return hr;
115 local_name->assign(name);
116 }
117
118 if (remote_name) {
119 ScopedCoMem<char16> name;
120 hr = file->GetRemoteName(&name);
121 if (FAILED(hr))
122 return hr;
123 remote_name->assign(name);
124 }
125
126 if (progress) {
127 BG_FILE_PROGRESS bg_file_progress = {};
128 hr = file->GetProgress(&bg_file_progress);
129 if (FAILED(hr))
130 return hr;
131 *progress = bg_file_progress;
132 }
133
134 return hr;
135 }
136
137 // Finds the jobs matching the given predicate.
138 // Returns S_OK if the function has found at least one job, returns S_FALSE if
139 // no job was found, and it returns an error otherwise.
140 template<class Predicate>
141 HRESULT FindBitsJobIf(Predicate pred,
142 IBackgroundCopyManager* bits_manager,
143 std::vector<ScopedComPtr<IBackgroundCopyJob> >* jobs) {
144 ScopedComPtr<IEnumBackgroundCopyJobs> enum_jobs;
145 HRESULT hr = bits_manager->EnumJobs(0, enum_jobs.Receive());
146 if (FAILED(hr))
147 return hr;
148
149 ULONG job_count = 0;
150 hr = enum_jobs->GetCount(&job_count);
151 if (FAILED(hr))
152 return hr;
153
154 for (ULONG i = 0; i != job_count; ++i) {
155 ScopedComPtr<IBackgroundCopyJob> current_job;
156 if (enum_jobs->Next(1, current_job.Receive(), NULL) == S_OK &&
157 pred(current_job)) {
158 jobs->push_back(current_job);
159 }
160 }
161
162 return jobs->empty() ? S_FALSE : S_OK;
163 }
164
165 // Compares the description of a job matches |name|.
166 struct JobDescriptionEqual
167 : public std::binary_function<IBackgroundCopyJob*, const string16&, bool> {
168 bool operator()(IBackgroundCopyJob* job, const string16& name) const;
169 };
170
171 bool JobDescriptionEqual::operator()(IBackgroundCopyJob* job,
172 const string16& name) const {
173 ScopedCoMem<char16> description;
174 HRESULT hr = job->GetDescription(&description);
175 return SUCCEEDED(hr) && name.compare(description) == 0;
176 }
177
178 // Compares the job creation time and returns true if the job creation time
179 // is older than |num_days|.
180 struct JobCreationOlderThanDays
181 : public std::binary_function<IBackgroundCopyJob*, int, bool> {
182 bool operator()(IBackgroundCopyJob* job, int num_days) const;
183 };
184
185 bool JobCreationOlderThanDays::operator()(IBackgroundCopyJob* job,
186 int num_days) const {
187 if (!JobDescriptionEqual()(job, kJobDescription))
188 return false;
189
190 BG_JOB_TIMES times = {0};
191 HRESULT hr = job->GetTimes(&times);
192 if (FAILED(hr))
193 return false;
194
195 const base::TimeDelta time_delta(base::TimeDelta::FromDays(num_days));
196 const base::Time creation_time(base::Time::FromFileTime(times.CreationTime));
197
198 return creation_time + time_delta < base::Time::Now();
199 }
200
201 // Compares the url of a file in a job and returns true if the remote name
202 // of any file in a job matches |url|.
203 struct JobFileUrlEqual
204 : public std::binary_function<IBackgroundCopyJob*, const string16&, bool> {
205 bool operator()(IBackgroundCopyJob* job, const string16& url) const;
206 };
207
208 bool JobFileUrlEqual::operator()(IBackgroundCopyJob* job,
209 const string16& url) const {
210 if (!JobDescriptionEqual()(job, kJobDescription))
211 return false;
212
213 std::vector<ScopedComPtr<IBackgroundCopyFile> > files;
214 HRESULT hr = GetFilesInJob(job, &files);
215 if (FAILED(hr))
216 return false;
217
218 for (size_t i = 0; i != files.size(); ++i) {
219 ScopedCoMem<char16> name;
220 if (SUCCEEDED(files[i]->GetRemoteName(&name)) && url.compare(name) == 0)
221 return true;
222 }
223
224 return false;
225 }
226
227 // Sets the proxy authentication credentials for the job.
228 HRESULT SetProxyAuthCredentials(IBackgroundCopyJob* job) {
229 ScopedComPtr<IBackgroundCopyJob2> job2;
230 HRESULT hr = job2.QueryFrom(job);
231 if (FAILED(hr))
232 return hr;
233
234 BG_AUTH_CREDENTIALS auth_cred = {};
235 auth_cred.Target = BG_AUTH_TARGET_PROXY;
236 auth_cred.Scheme = BG_AUTH_SCHEME_NEGOTIATE;
237
238 return job2->SetCredentials(&auth_cred);
239 }
240
241 // Creates an instance of the BITS manager.
242 HRESULT GetBitsManager(IBackgroundCopyManager** bits_manager) {
243 ScopedComPtr<IBackgroundCopyManager> object;
244 HRESULT hr = object.CreateInstance(__uuidof(BackgroundCopyManager));
245 if (FAILED(hr)) {
246 VLOG(1) << "Failed to instantiate BITS." << std::hex << hr;
247 // TODO: add UMA pings.
248 return hr;
249 }
250 *bits_manager = object.Detach();
251 return S_OK;
252 }
253
254 // JobObserver receives notifications when a BITS job has been completed,
255 // modified, or has encountered an error. This class lives on the FILE thread.
256 class JobObserver
257 : public CComObjectRootEx<CComSingleThreadModel>,
258 public IBackgroundCopyCallback {
259 public:
260 typedef base::Callback<void (void)> JobChangedCallback;
261
262 JobObserver() {}
263
264 virtual ~JobObserver() {}
265
266 void set_callback(const JobChangedCallback& callback) {
267 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
268 callback_ = callback;
269 }
270
271 BEGIN_COM_MAP(JobObserver)
272 COM_INTERFACE_ENTRY(IBackgroundCopyCallback)
273 END_COM_MAP()
274
275 // IBackgroundCopyCallback methods.
276 STDMETHOD(JobTransferred)(IBackgroundCopyJob* job) OVERRIDE {
277 NotifyJobChanged();
278 return S_OK;
279 }
280
281 STDMETHOD(JobError)(IBackgroundCopyJob* job,
282 IBackgroundCopyError* error) OVERRIDE {
283 NotifyJobChanged();
284 return S_OK;
285 }
286
287 STDMETHOD(JobModification)(IBackgroundCopyJob* job,
288 DWORD reserved) OVERRIDE {
289 NotifyJobChanged();
290 return S_OK;
291 }
292
293 private:
294 void NotifyJobChanged() {
295 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
296 if (!callback_.is_null())
297 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, callback_);
298 }
299
300 JobChangedCallback callback_;
301
302 DISALLOW_COPY_AND_ASSIGN(JobObserver);
303 };
304
305 } // namespace
306
307 BackgroundDownloader::BackgroundDownloader(
308 scoped_ptr<CrxDownloader> successor,
309 net::URLRequestContextGetter* context_getter,
310 scoped_refptr<base::SequencedTaskRunner> task_runner,
311 const DownloadCallback& download_callback)
312 : CrxDownloader(successor.Pass(), download_callback),
313 context_getter_(context_getter),
314 task_runner_(task_runner),
315 is_completed_(false) {
316 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
317 }
318
319 BackgroundDownloader::~BackgroundDownloader() {
320 }
321
322 void BackgroundDownloader::DoStartDownload(const GURL& url) {
323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
324
325 BrowserThread::PostTask(
326 BrowserThread::FILE,
327 FROM_HERE,
328 base::Bind(&BackgroundDownloader::BeginDownload,
329 base::Unretained(this),
330 url));
331 }
332
333 // Called once when this class is asked to do a download. Creates or opens
334 // an existing bits job, hooks up the notifications, and starts the timer.
335 void BackgroundDownloader::BeginDownload(const GURL& url) {
336 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
337
338 DCHECK(!timer_);
339
340 HRESULT hr = QueueBitsJob(url);
341 if (FAILED(hr)) {
342 if (job_)
343 job_->Cancel();
344 EndDownload(hr);
345 return;
346 }
347
348 timer_.reset(new base::OneShotTimer<BackgroundDownloader>);
349 timer_->Start(FROM_HERE,
350 base::TimeDelta::FromSeconds(kJobPollingIntervalSec),
351 this,
352 &BackgroundDownloader::OnDownloading);
353 }
354
355 // Called any time there is a change in the state of the job or when
356 // the timer fires.
357 void BackgroundDownloader::OnDownloading() {
358 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
359
360 // Stop the timer in case this function was called by BITS event.
361 timer_->Stop();
362
363 BG_JOB_STATE job_state = BG_JOB_STATE_ERROR;
364 HRESULT hr = job_->GetState(&job_state);
365 if (FAILED(hr)) {
366 EndDownload(hr);
367 return;
368 }
369
370 switch (job_state) {
371 case BG_JOB_STATE_TRANSFERRED:
372 OnStateTransferred();
373 return;
374
375 case BG_JOB_STATE_ERROR:
376 OnStateError();
377 return;
378
379 case BG_JOB_STATE_CANCELLED:
380 OnStateCancelled();
381 return;
382
383 case BG_JOB_STATE_ACKNOWLEDGED:
384 OnStateAcknowledged();
385 return;
386
387 // TODO: handle the non-final states, so that the download does not get
388 // stuck if BITS is not able to make progress on a given url.
389 case BG_JOB_STATE_TRANSIENT_ERROR:
390 case BG_JOB_STATE_QUEUED:
391 case BG_JOB_STATE_CONNECTING:
392 case BG_JOB_STATE_TRANSFERRING:
393 case BG_JOB_STATE_SUSPENDED:
394 default:
395 break;
396 }
397
398 timer_->Reset();
399 }
400
401 // Completes the BITS download, picks up the file path of the response, and
402 // notifies the CrxDownloader. Usually called one time but it could be called
403 // multiple times due to the dual polling/event drive mechanism to receive
404 // job state changes.
405 void BackgroundDownloader::EndDownload(HRESULT error) {
406 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
407 DCHECK(!timer_->IsRunning());
408
409 timer_.reset();
410
411 if (is_completed_)
412 return;
413
414 base::FilePath response;
415 HRESULT hr = error;
416 if (SUCCEEDED(hr)) {
417 std::vector<ScopedComPtr<IBackgroundCopyFile> > files;
418 GetFilesInJob(job_, &files);
419 DCHECK(files.size() == 1);
420 string16 local_name;
421 BG_FILE_PROGRESS progress = {0};
422 hr = GetJobFileProperties(files[0], &local_name, NULL, &progress);
423 if (SUCCEEDED(hr)) {
424 DCHECK(progress.Completed);
425 response = base::FilePath(local_name);
426 }
427 }
428
429 // Consider the url handled if it has been successfully downloaded or a
430 // 5xx has been received.
431 const bool is_handled = SUCCEEDED(hr) ||
432 IsHttpServerError(GetHttpStatusFromBitsError(error));
433
434 Result result;
435 result.error = error;
436 result.is_background_download = true;
437 result.response = response;
438 BrowserThread::PostTask(
439 BrowserThread::UI,
440 FROM_HERE,
441 base::Bind(&BackgroundDownloader::OnDownloadComplete,
442 base::Unretained(this),
443 is_handled,
444 result));
445
446 is_completed_ = true;
447
448 CleanupStaleJobs(bits_manager_);
449 }
450
451 // Called when the BITS job has been transferred successfully. Completes the
452 // BITS job by removing it from the BITS queue and making the download
453 // available to the caller.
454 void BackgroundDownloader::OnStateTransferred() {
455 RemoveJobObserver();
456
457 HRESULT hr = job_->Complete();
458 if (SUCCEEDED(hr) || hr == BG_S_UNABLE_TO_DELETE_FILES)
459 hr = S_OK;
460 else
461 hr = job_->Cancel();
462
463 EndDownload(hr);
464 }
465
466 // Called when the job has encountered an error and no further progress can
467 // be made. Cancels this job and remove it from the BITS queue.
468 void BackgroundDownloader::OnStateError() {
469 RemoveJobObserver();
470
471 ScopedComPtr<IBackgroundCopyError> copy_error;
472 HRESULT hr = job_->GetError(copy_error.Receive());
473 if (SUCCEEDED(hr)) {
474 BG_ERROR_CONTEXT error_context = BG_ERROR_CONTEXT_NONE;
475 HRESULT error_code = E_FAIL;
476 hr = copy_error->GetError(&error_context, &error_code);
477 if (SUCCEEDED(hr)) {
478 EndDownload(error_code);
479 return;
480 }
481 }
482
483 hr = job_->Cancel();
484 EndDownload(hr);
485 }
486
487 // Called when the download was cancelled. Since the observer should have
488 // been disconnected by now, this notification must not be seen.
489 void BackgroundDownloader::OnStateCancelled() {
490 EndDownload(E_UNEXPECTED);
491 }
492
493 // Called when the download was completed. Same as above.
494 void BackgroundDownloader::OnStateAcknowledged() {
495 EndDownload(E_UNEXPECTED);
496 }
497
498 // Creates or opens a job for the given url and queues it up. Tries to
499 // install a job observer but continues on if an observer can't be set up.
500 HRESULT BackgroundDownloader::QueueBitsJob(const GURL& url) {
501 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
502
503 HRESULT hr = S_OK;
504 if (bits_manager_ == NULL) {
505 hr = GetBitsManager(bits_manager_.Receive());
506 if (FAILED(hr))
507 return hr;
508 }
509
510 hr = CreateOrOpenJob(url);
511 if (FAILED(hr))
512 return hr;
513
514 if (hr == S_OK) {
515 hr = InitializeNewJob(url);
516 if (FAILED(hr))
517 return hr;
518 }
519
520 InstallJobObserver();
521
522 return job_->Resume();
523 }
524
525 HRESULT BackgroundDownloader::CreateOrOpenJob(const GURL& url) {
526 std::vector<ScopedComPtr<IBackgroundCopyJob> > jobs;
527 HRESULT hr = FindBitsJobIf(
528 std::bind2nd(JobFileUrlEqual(), base::SysUTF8ToWide(url.spec())),
529 bits_manager_,
530 &jobs);
531 if (SUCCEEDED(hr) && !jobs.empty()) {
532 job_ = jobs.front();
533 return S_FALSE;
534 }
535
536 GUID guid = {0};
537 ScopedComPtr<IBackgroundCopyJob> job;
538 hr = bits_manager_->CreateJob(L"",
539 BG_JOB_TYPE_DOWNLOAD,
540 &guid,
541 job.Receive());
542 if (FAILED(hr))
543 return hr;
544
545 job_ = job;
546 return S_OK;
547 }
548
549 HRESULT BackgroundDownloader::InitializeNewJob(const GURL& url) {
550 const string16 filename(base::SysUTF8ToWide(url.ExtractFileName()));
551
552 base::FilePath tempdir;
553 if (!base::CreateNewTempDirectory(
554 FILE_PATH_LITERAL("chrome_BITS_"),
555 &tempdir))
556 return E_FAIL;
557
558 HRESULT hr = job_->AddFile(
559 base::SysUTF8ToWide(url.spec()).c_str(),
560 tempdir.Append(filename).AsUTF16Unsafe().c_str());
561 if (FAILED(hr))
562 return hr;
563
564 hr = job_->SetDisplayName(filename.c_str());
565 if (FAILED(hr))
566 return hr;
567
568 hr = job_->SetDescription(kJobDescription);
569 if (FAILED(hr))
570 return hr;
571
572 hr = job_->SetPriority(BG_JOB_PRIORITY_NORMAL);
573 if (FAILED(hr))
574 return hr;
575
576 return S_OK;
577 }
578
579 HRESULT BackgroundDownloader::InstallJobObserver() {
580 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
581
582 // Make sure ATL is initialized in this module.
583 ui::win::CreateATLModuleIfNeeded();
584
585 CComObject<JobObserver>* job_observer = NULL;
586 HRESULT hr(CComObject<JobObserver>::CreateInstance(&job_observer));
587 if (FAILED(hr))
588 return hr;
589
590 job_observer->set_callback(
591 base::Bind(&BackgroundDownloader::OnDownloading,
592 base::Unretained(this)));
593
594 job_observer->AddRef();
595 job_observer_.Attach(job_observer);
596
597 hr = job_->SetNotifyInterface(job_observer_);
598 if (FAILED(hr))
599 return hr;
600
601 hr = job_->SetNotifyFlags(BG_NOTIFY_FILE_TRANSFERRED |
602 BG_NOTIFY_JOB_TRANSFERRED |
603 BG_NOTIFY_JOB_ERROR);
604 if (FAILED(hr))
605 return hr;
606
607 return S_OK;
608 }
609
610 HRESULT BackgroundDownloader::RemoveJobObserver() {
611 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
612
613 if (!job_ || !job_observer_)
614 return S_OK;
615
616 HRESULT hr = job_->SetNotifyFlags(0);
617 if (FAILED(hr))
618 return hr;
619
620 hr = job_->SetNotifyInterface(NULL);
621 if (FAILED(hr))
622 return hr;
623
624 static_cast<JobObserver*>(job_observer_.get())->set_callback(
625 JobObserver::JobChangedCallback());
626 job_observer_ = NULL;
627
628 return S_OK;
629 }
630
631 // Cleans up incompleted jobs that are too old.
632 HRESULT BackgroundDownloader::CleanupStaleJobs(
633 base::win::ScopedComPtr<IBackgroundCopyManager> bits_manager) {
634 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
635
636 if (!bits_manager)
637 return E_FAIL;
638
639 static base::Time last_sweep;
640
641 const base::TimeDelta time_delta(base::TimeDelta::FromDays(
642 kPurgeStaleJobsIntervalBetweenChecksDays));
643 const base::Time current_time(base::Time::Now());
644 if (last_sweep + time_delta > current_time)
645 return S_OK;
646
647 last_sweep = current_time;
648
649 std::vector<ScopedComPtr<IBackgroundCopyJob> > jobs;
650 HRESULT hr = FindBitsJobIf(
651 std::bind2nd(JobCreationOlderThanDays(), kPurgeStaleJobsAfterDays),
652 bits_manager,
653 &jobs);
654 if (FAILED(hr))
655 return hr;
656
657 for (size_t i = 0; i != jobs.size(); ++i) {
658 jobs[i]->Cancel();
659 }
660
661 return S_OK;
662 }
663
664 } // namespace component_updater
665
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698