OLD | NEW |
---|---|
(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(×); | |
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 | |
OLD | NEW |