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

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: Fixed typos. 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. The checks are triggered by a timer.
36 // The BITS job contains just one file to download. There could only be one
37 // download in progress at a time. If Chrome closes down before the download is
38 // complete, the BITS job remains active and finishes in the background, without
39 // any intervention. The job can be completed next time the code runs, if the
40 // file is still needed, otherwise it will be cleaned up on a periodic basis.
41 //
42 // To list the BITS jobs for a user, use the |bitsadmin| tool. The command line
43 // to do that is: "bitsadmin /list /verbose". Another useful command is
44 // "bitsadmin /info" and provide the job id returned by the previous /list
45 // command.
46 namespace component_updater {
47
48 namespace {
49
50 // All jobs created by this module have a specific description so they can
51 // be found at run-time or by using system administration tools.
52 const char16 kJobDescription[] = L"Chrome Component Updater";
53
54 // How often the code looks for changes in the BITS job state.
55 const int kJobPollingIntervalSec = 10;
56
57 // How often the jobs which were started but not completed for any reason
58 // are cleaned up. Reasons for jobs to be left behind include browser restarts,
59 // system restarts, etc. Also, the check to purge stale jobs only happens
60 // at most once a day.
61 const int kPurgeStaleJobsAfterDays = 7;
62 const int kPurgeStaleJobsIntervalBetweenChecksDays = 1;
63
64 // Returns the status code from a given BITS error.
65 int GetHttpStatusFromBitsError(HRESULT error) {
66 // BITS errors are defined in bitsmsg.h. Although not documented, it is
67 // clear that all errors corresponding to http status code have the high
68 // word equal to 0x8019 and the low word equal to the http status code.
69 const int kHttpStatusFirst = 100; // Continue.
70 const int kHttpStatusLast = 505; // Version not supported.
71 bool is_valid = HIWORD(error) == 0x8019 &&
72 LOWORD(error) >= kHttpStatusFirst &&
73 LOWORD(error) <= kHttpStatusLast;
74 return is_valid ? LOWORD(error) : 0;
75 }
76
77 // Returns the files in a BITS job.
78 HRESULT GetFilesInJob(IBackgroundCopyJob* job,
79 std::vector<ScopedComPtr<IBackgroundCopyFile> >* files) {
80 ScopedComPtr<IEnumBackgroundCopyFiles> enum_files;
81 HRESULT hr = job->EnumFiles(enum_files.Receive());
82 if (FAILED(hr))
83 return hr;
84
85 ULONG num_files = 0;
86 hr = enum_files->GetCount(&num_files);
87 if (FAILED(hr))
88 return hr;
89
90 for (ULONG i = 0; i != num_files; ++i) {
91 ScopedComPtr<IBackgroundCopyFile> file;
92 if (enum_files->Next(1, file.Receive(), NULL) == S_OK)
93 files->push_back(file);
94 }
95
96 return S_OK;
97 }
98
99 // Returns the file name, the url, and some per-file progress information.
100 // The function out parameters can be NULL if that data is not requested.
101 HRESULT GetJobFileProperties(IBackgroundCopyFile* file,
102 string16* local_name,
103 string16* remote_name,
104 BG_FILE_PROGRESS* progress) {
105 HRESULT hr = S_OK;
106
107 if (local_name) {
108 ScopedCoMem<char16> name;
109 hr = file->GetLocalName(&name);
110 if (FAILED(hr))
111 return hr;
112 local_name->assign(name);
113 }
114
115 if (remote_name) {
116 ScopedCoMem<char16> name;
117 hr = file->GetRemoteName(&name);
118 if (FAILED(hr))
119 return hr;
120 remote_name->assign(name);
121 }
122
123 if (progress) {
124 BG_FILE_PROGRESS bg_file_progress = {};
125 hr = file->GetProgress(&bg_file_progress);
126 if (FAILED(hr))
127 return hr;
128 *progress = bg_file_progress;
129 }
130
131 return hr;
132 }
133
134 HRESULT GetJobDescription(IBackgroundCopyJob* job, const string16* name) {
135 ScopedCoMem<char16> description;
136 return job->GetDescription(&description);
137 }
138
139 // Finds the component updater jobs matching the given predicate.
140 // Returns S_OK if the function has found at least one job, returns S_FALSE if
141 // no job was found, and it returns an error otherwise.
142 template<class Predicate>
143 HRESULT FindBitsJobIf(Predicate pred,
144 IBackgroundCopyManager* bits_manager,
145 std::vector<ScopedComPtr<IBackgroundCopyJob> >* jobs) {
146 ScopedComPtr<IEnumBackgroundCopyJobs> enum_jobs;
147 HRESULT hr = bits_manager->EnumJobs(0, enum_jobs.Receive());
148 if (FAILED(hr))
149 return hr;
150
151 ULONG job_count = 0;
152 hr = enum_jobs->GetCount(&job_count);
153 if (FAILED(hr))
154 return hr;
155
156 // Iterate over jobs, run the predicate, and select the job only if
157 // the job description matches the component updater jobs.
158 for (ULONG i = 0; i != job_count; ++i) {
159 ScopedComPtr<IBackgroundCopyJob> current_job;
160 if (enum_jobs->Next(1, current_job.Receive(), NULL) == S_OK &&
161 pred(current_job)) {
162 string16 job_description;
163 hr = GetJobDescription(current_job, &job_description);
164 if (job_description.compare(kJobDescription) == 0)
165 jobs->push_back(current_job);
166 }
167 }
168
169 return jobs->empty() ? S_FALSE : S_OK;
170 }
171
172 // Compares the job creation time and returns true if the job creation time
173 // is older than |num_days|.
174 struct JobCreationOlderThanDays
175 : public std::binary_function<IBackgroundCopyJob*, int, bool> {
176 bool operator()(IBackgroundCopyJob* job, int num_days) const;
177 };
178
179 bool JobCreationOlderThanDays::operator()(IBackgroundCopyJob* job,
180 int num_days) const {
181 BG_JOB_TIMES times = {0};
182 HRESULT hr = job->GetTimes(&times);
183 if (FAILED(hr))
184 return false;
185
186 const base::TimeDelta time_delta(base::TimeDelta::FromDays(num_days));
187 const base::Time creation_time(base::Time::FromFileTime(times.CreationTime));
188
189 return creation_time + time_delta < base::Time::Now();
190 }
191
192 // Compares the url of a file in a job and returns true if the remote name
193 // of any file in a job matches the argument.
194 struct JobFileUrlEqual
195 : public std::binary_function<IBackgroundCopyJob*, const string16&, bool> {
196 bool operator()(IBackgroundCopyJob* job, const string16& remote_name) const;
197 };
198
199 bool JobFileUrlEqual::operator()(IBackgroundCopyJob* job,
200 const string16& remote_name) const {
201 std::vector<ScopedComPtr<IBackgroundCopyFile> > files;
202 HRESULT hr = GetFilesInJob(job, &files);
203 if (FAILED(hr))
204 return false;
205
206 for (size_t i = 0; i != files.size(); ++i) {
207 ScopedCoMem<char16> name;
208 if (SUCCEEDED(files[i]->GetRemoteName(&name)) &&
209 remote_name.compare(name) == 0)
210 return true;
211 }
212
213 return false;
214 }
215
216 // Creates an instance of the BITS manager.
217 HRESULT GetBitsManager(IBackgroundCopyManager** bits_manager) {
218 ScopedComPtr<IBackgroundCopyManager> object;
219 HRESULT hr = object.CreateInstance(__uuidof(BackgroundCopyManager));
220 if (FAILED(hr)) {
221 VLOG(1) << "Failed to instantiate BITS." << std::hex << hr;
222 // TODO: add UMA pings.
223 return hr;
224 }
225 *bits_manager = object.Detach();
226 return S_OK;
227 }
228
229 } // namespace
230
231 BackgroundDownloader::BackgroundDownloader(
232 scoped_ptr<CrxDownloader> successor,
233 net::URLRequestContextGetter* context_getter,
234 scoped_refptr<base::SequencedTaskRunner> task_runner,
235 const DownloadCallback& download_callback)
236 : CrxDownloader(successor.Pass(), download_callback),
237 context_getter_(context_getter),
238 task_runner_(task_runner),
239 is_completed_(false) {
240 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
241 }
242
243 BackgroundDownloader::~BackgroundDownloader() {
244 }
245
246 void BackgroundDownloader::DoStartDownload(const GURL& url) {
247 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
248
249 BrowserThread::PostTask(
250 BrowserThread::FILE,
251 FROM_HERE,
252 base::Bind(&BackgroundDownloader::BeginDownload,
253 base::Unretained(this),
254 url));
255 }
256
257 // Called once when this class is asked to do a download. Creates or opens
258 // an existing bits job, hooks up the notifications, and starts the timer.
259 void BackgroundDownloader::BeginDownload(const GURL& url) {
260 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
261
262 DCHECK(!timer_);
263
264 HRESULT hr = QueueBitsJob(url);
265 if (FAILED(hr)) {
266 if (job_) {
267 job_->Cancel();
268 job_ = NULL;
269 }
270 EndDownload(hr);
271 return;
272 }
273
274 // A repeating timer retains the user task. This timer can be stopped and
275 // reset multiple times.
276 timer_.reset(new base::RepeatingTimer<BackgroundDownloader>);
277 timer_->Start(FROM_HERE,
278 base::TimeDelta::FromSeconds(kJobPollingIntervalSec),
279 this,
280 &BackgroundDownloader::OnDownloading);
281 }
282
283 // Called any time the timer fires.
284 void BackgroundDownloader::OnDownloading() {
285 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
286
287 timer_->Stop();
288
289 BG_JOB_STATE job_state = BG_JOB_STATE_ERROR;
290 HRESULT hr = job_->GetState(&job_state);
291 if (FAILED(hr)) {
292 EndDownload(hr);
293 return;
294 }
295
296 switch (job_state) {
297 case BG_JOB_STATE_TRANSFERRED:
298 OnStateTransferred();
299 return;
300
301 case BG_JOB_STATE_ERROR:
302 OnStateError();
303 return;
304
305 case BG_JOB_STATE_CANCELLED:
306 OnStateCancelled();
307 return;
308
309 case BG_JOB_STATE_ACKNOWLEDGED:
310 OnStateAcknowledged();
311 return;
312
313 // TODO: handle the non-final states, so that the download does not get
314 // stuck if BITS is not able to make progress on a given url.
315 case BG_JOB_STATE_TRANSIENT_ERROR:
316 case BG_JOB_STATE_QUEUED:
317 case BG_JOB_STATE_CONNECTING:
318 case BG_JOB_STATE_TRANSFERRING:
319 case BG_JOB_STATE_SUSPENDED:
320 default:
321 break;
322 }
323
324 timer_->Reset();
325 }
326
327 // Completes the BITS download, picks up the file path of the response, and
328 // notifies the CrxDownloader. The function should be called only once.
329 void BackgroundDownloader::EndDownload(HRESULT error) {
330 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
331
332 DCHECK(!is_completed_);
333 if (is_completed_)
334 return;
335
336 is_completed_ = true;
337
338 DCHECK(!timer_->IsRunning());
339 timer_.reset();
340
341 base::FilePath response;
342 HRESULT hr = error;
343 if (SUCCEEDED(hr)) {
344 std::vector<ScopedComPtr<IBackgroundCopyFile> > files;
345 GetFilesInJob(job_, &files);
346 DCHECK(files.size() == 1);
347 string16 local_name;
348 BG_FILE_PROGRESS progress = {0};
349 hr = GetJobFileProperties(files[0], &local_name, NULL, &progress);
350 if (SUCCEEDED(hr)) {
351 DCHECK(progress.Completed);
352 response = base::FilePath(local_name);
353 }
354 }
355
356 // Consider the url handled if it has been successfully downloaded or a
357 // 5xx has been received.
358 const bool is_handled = SUCCEEDED(hr) ||
359 IsHttpServerError(GetHttpStatusFromBitsError(error));
360
361 Result result;
362 result.error = error;
363 result.is_background_download = true;
364 result.response = response;
365 BrowserThread::PostTask(
366 BrowserThread::UI,
367 FROM_HERE,
368 base::Bind(&BackgroundDownloader::OnDownloadComplete,
369 base::Unretained(this),
370 is_handled,
371 result));
372
373 CleanupStaleJobs(bits_manager_);
374 }
375
376 // Called when the BITS job has been transferred successfully. Completes the
377 // BITS job by removing it from the BITS queue and making the download
378 // available to the caller.
379 void BackgroundDownloader::OnStateTransferred() {
380 HRESULT hr = job_->Complete();
381 if (SUCCEEDED(hr) || hr == BG_S_UNABLE_TO_DELETE_FILES)
382 hr = S_OK;
383 else
384 hr = job_->Cancel();
waffles 2013/12/06 22:08:57 As mentioned in chat, the hr= here seems wrong. Al
Sorin Jianu 2013/12/06 23:18:04 Done.
385
386 EndDownload(hr);
387 }
388
389 // Called when the job has encountered an error and no further progress can
390 // be made. Cancels this job and removes it from the BITS queue.
391 void BackgroundDownloader::OnStateError() {
392 ScopedComPtr<IBackgroundCopyError> copy_error;
393 HRESULT hr = job_->GetError(copy_error.Receive());
394 if (SUCCEEDED(hr)) {
395 BG_ERROR_CONTEXT error_context = BG_ERROR_CONTEXT_NONE;
396 HRESULT error_code = E_FAIL;
397 hr = copy_error->GetError(&error_context, &error_code);
398 if (SUCCEEDED(hr)) {
399 EndDownload(error_code);
400 return;
401 }
402 }
403
404 hr = job_->Cancel();
405 EndDownload(hr);
waffles 2013/12/06 22:08:57 I think this should be error_code.
Sorin Jianu 2013/12/06 23:18:04 If we get here, then the error_code could not be r
406 }
407
408 // Called when the download was cancelled. Since the observer should have
409 // been disconnected by now, this notification must not be seen.
410 void BackgroundDownloader::OnStateCancelled() {
411 EndDownload(E_UNEXPECTED);
412 }
413
414 // Called when the download was completed. Same as above.
415 void BackgroundDownloader::OnStateAcknowledged() {
416 EndDownload(E_UNEXPECTED);
417 }
418
419 // Creates or opens a job for the given url and queues it up. Tries to
420 // install a job observer but continues on if an observer can't be set up.
421 HRESULT BackgroundDownloader::QueueBitsJob(const GURL& url) {
422 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
423
424 HRESULT hr = S_OK;
425 if (bits_manager_ == NULL) {
426 hr = GetBitsManager(bits_manager_.Receive());
427 if (FAILED(hr))
428 return hr;
429 }
430
431 hr = CreateOrOpenJob(url);
432 if (FAILED(hr))
433 return hr;
434
435 if (hr == S_OK) {
436 hr = InitializeNewJob(url);
437 if (FAILED(hr))
438 return hr;
439 }
440
441 return job_->Resume();
442 }
443
444 HRESULT BackgroundDownloader::CreateOrOpenJob(const GURL& url) {
445 std::vector<ScopedComPtr<IBackgroundCopyJob> > jobs;
446 HRESULT hr = FindBitsJobIf(
447 std::bind2nd(JobFileUrlEqual(), base::SysUTF8ToWide(url.spec())),
448 bits_manager_,
449 &jobs);
450 if (SUCCEEDED(hr) && !jobs.empty()) {
451 job_ = jobs.front();
452 return S_FALSE;
453 }
454
455 // Use kJobDescription as a temporary job display name until the proper
456 // display name is initialized later on.
457 GUID guid = {0};
458 ScopedComPtr<IBackgroundCopyJob> job;
459 hr = bits_manager_->CreateJob(kJobDescription,
460 BG_JOB_TYPE_DOWNLOAD,
461 &guid,
462 job.Receive());
463 if (FAILED(hr))
464 return hr;
465
466 job_ = job;
467 return S_OK;
468 }
469
470 HRESULT BackgroundDownloader::InitializeNewJob(const GURL& url) {
471 const string16 filename(base::SysUTF8ToWide(url.ExtractFileName()));
472
473 base::FilePath tempdir;
474 if (!base::CreateNewTempDirectory(
475 FILE_PATH_LITERAL("chrome_BITS_"),
476 &tempdir))
477 return E_FAIL;
478
479 HRESULT hr = job_->AddFile(
480 base::SysUTF8ToWide(url.spec()).c_str(),
481 tempdir.Append(filename).AsUTF16Unsafe().c_str());
482 if (FAILED(hr))
483 return hr;
484
485 hr = job_->SetDisplayName(filename.c_str());
486 if (FAILED(hr))
487 return hr;
488
489 hr = job_->SetDescription(kJobDescription);
490 if (FAILED(hr))
491 return hr;
492
493 hr = job_->SetPriority(BG_JOB_PRIORITY_NORMAL);
494 if (FAILED(hr))
495 return hr;
496
497 return S_OK;
498 }
499
500 // Cleans up incompleted jobs that are too old.
501 HRESULT BackgroundDownloader::CleanupStaleJobs(
502 base::win::ScopedComPtr<IBackgroundCopyManager> bits_manager) {
503 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
504
505 if (!bits_manager)
506 return E_FAIL;
507
508 static base::Time last_sweep;
509
510 const base::TimeDelta time_delta(base::TimeDelta::FromDays(
511 kPurgeStaleJobsIntervalBetweenChecksDays));
512 const base::Time current_time(base::Time::Now());
513 if (last_sweep + time_delta > current_time)
514 return S_OK;
515
516 last_sweep = current_time;
517
518 std::vector<ScopedComPtr<IBackgroundCopyJob> > jobs;
519 HRESULT hr = FindBitsJobIf(
520 std::bind2nd(JobCreationOlderThanDays(), kPurgeStaleJobsAfterDays),
521 bits_manager,
522 &jobs);
523 if (FAILED(hr))
524 return hr;
525
526 for (size_t i = 0; i != jobs.size(); ++i) {
527 jobs[i]->Cancel();
528 }
529
530 return S_OK;
531 }
532
533 } // namespace component_updater
534
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698