OLD | NEW |
| (Empty) |
1 // Copyright 2014 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 "components/component_updater/background_downloader_win.h" | |
6 | |
7 #include <atlbase.h> | |
8 #include <atlcom.h> | |
9 | |
10 #include <stdint.h> | |
11 #include <functional> | |
12 #include <iomanip> | |
13 #include <limits> | |
14 #include <vector> | |
15 | |
16 #include "base/bind.h" | |
17 #include "base/bind_helpers.h" | |
18 #include "base/files/file_util.h" | |
19 #include "base/message_loop/message_loop_proxy.h" | |
20 #include "base/single_thread_task_runner.h" | |
21 #include "base/strings/sys_string_conversions.h" | |
22 #include "base/win/scoped_co_mem.h" | |
23 #include "components/component_updater/component_updater_utils.h" | |
24 #include "ui/base/win/atl_module.h" | |
25 #include "url/gurl.h" | |
26 | |
27 using base::win::ScopedCoMem; | |
28 using base::win::ScopedComPtr; | |
29 | |
30 // The class BackgroundDownloader in this module is an adapter between | |
31 // the CrxDownloader interface and the BITS service interfaces. | |
32 // The interface exposed on the CrxDownloader code runs on the main thread, | |
33 // while the BITS specific code runs on a separate thread passed in by the | |
34 // client. For every url to download, a BITS job is created, unless there is | |
35 // already an existing job for that url, in which case, the downloader | |
36 // connects to it. Once a job is associated with the url, the code looks for | |
37 // changes in the BITS job state. The checks are triggered by a timer. | |
38 // The BITS job contains just one file to download. There could only be one | |
39 // download in progress at a time. If Chrome closes down before the download is | |
40 // complete, the BITS job remains active and finishes in the background, without | |
41 // any intervention. The job can be completed next time the code runs, if the | |
42 // file is still needed, otherwise it will be cleaned up on a periodic basis. | |
43 // | |
44 // To list the BITS jobs for a user, use the |bitsadmin| tool. The command line | |
45 // to do that is: "bitsadmin /list /verbose". Another useful command is | |
46 // "bitsadmin /info" and provide the job id returned by the previous /list | |
47 // command. | |
48 // | |
49 // Ignoring the suspend/resume issues since this code is not using them, the | |
50 // job state machine implemented by BITS is something like this: | |
51 // | |
52 // Suspended--->Queued--->Connecting---->Transferring--->Transferred | |
53 // | ^ | | | | |
54 // | | V V | (complete) | |
55 // +----------|---------+-----------------+-----+ V | |
56 // | | | | Acknowledged | |
57 // | V V | | |
58 // | Transient Error------->Error | | |
59 // | | | |(cancel) | |
60 // | +-------+---------+--->-+ | |
61 // | V | | |
62 // | (resume) | | | |
63 // +------<----------+ +---->Cancelled | |
64 // | |
65 // The job is created in the "suspended" state. Once |Resume| is called, | |
66 // BITS queues up the job, then tries to connect, begins transferring the | |
67 // job bytes, and moves the job to the "transferred state, after the job files | |
68 // have been transferred. When calling |Complete| for a job, the job files are | |
69 // made available to the caller, and the job is moved to the "acknowledged" | |
70 // state. | |
71 // At any point, the job can be cancelled, in which case, the job is moved | |
72 // to the "cancelled state" and the job object is removed from the BITS queue. | |
73 // Along the way, the job can encounter recoverable and non-recoverable errors. | |
74 // BITS moves the job to "transient error" or "error", depending on which kind | |
75 // of error has occured. | |
76 // If the job has reached the "transient error" state, BITS retries the | |
77 // job after a certain programmable delay. If the job can't be completed in a | |
78 // certain time interval, BITS stops retrying and errors the job out. This time | |
79 // interval is also programmable. | |
80 // If the job is in either of the error states, the job parameters can be | |
81 // adjusted to handle the error, after which the job can be resumed, and the | |
82 // whole cycle starts again. | |
83 // Jobs that are not touched in 90 days (or a value set by group policy) are | |
84 // automatically disposed off by BITS. This concludes the brief description of | |
85 // a job lifetime, according to BITS. | |
86 // | |
87 // In addition to how BITS is managing the life time of the job, there are a | |
88 // couple of special cases defined by the BackgroundDownloader. | |
89 // First, if the job encounters any of the 5xx HTTP responses, the job is | |
90 // not retried, in order to avoid DDOS-ing the servers. | |
91 // Second, there is a simple mechanism to detect stuck jobs, and allow the rest | |
92 // of the code to move on to trying other urls or trying other components. | |
93 // Last, after completing a job, irrespective of the outcome, the jobs older | |
94 // than a week are proactively cleaned up. | |
95 | |
96 namespace component_updater { | |
97 | |
98 namespace { | |
99 | |
100 // All jobs created by this module have a specific description so they can | |
101 // be found at run-time or by using system administration tools. | |
102 const base::char16 kJobDescription[] = L"Chrome Component Updater"; | |
103 | |
104 // How often the code looks for changes in the BITS job state. | |
105 const int kJobPollingIntervalSec = 4; | |
106 | |
107 // How long BITS waits before retrying a job after the job encountered | |
108 // a transient error. If this value is not set, the BITS default is 10 minutes. | |
109 const int kMinimumRetryDelayMin = 1; | |
110 | |
111 // How long to wait for stuck jobs. Stuck jobs could be queued for too long, | |
112 // have trouble connecting, could be suspended for any reason, or they have | |
113 // encountered some transient error. | |
114 const int kJobStuckTimeoutMin = 15; | |
115 | |
116 // How long BITS waits before giving up on a job that could not be completed | |
117 // since the job has encountered its first transient error. If this value is | |
118 // not set, the BITS default is 14 days. | |
119 const int kSetNoProgressTimeoutDays = 1; | |
120 | |
121 // How often the jobs which were started but not completed for any reason | |
122 // are cleaned up. Reasons for jobs to be left behind include browser restarts, | |
123 // system restarts, etc. Also, the check to purge stale jobs only happens | |
124 // at most once a day. If the job clean up code is not running, the BITS | |
125 // default policy is to cancel jobs after 90 days of inactivity. | |
126 const int kPurgeStaleJobsAfterDays = 7; | |
127 const int kPurgeStaleJobsIntervalBetweenChecksDays = 1; | |
128 | |
129 // Returns the status code from a given BITS error. | |
130 int GetHttpStatusFromBitsError(HRESULT error) { | |
131 // BITS errors are defined in bitsmsg.h. Although not documented, it is | |
132 // clear that all errors corresponding to http status code have the high | |
133 // word equal to 0x8019 and the low word equal to the http status code. | |
134 const int kHttpStatusFirst = 100; // Continue. | |
135 const int kHttpStatusLast = 505; // Version not supported. | |
136 bool is_valid = HIWORD(error) == 0x8019 && | |
137 LOWORD(error) >= kHttpStatusFirst && | |
138 LOWORD(error) <= kHttpStatusLast; | |
139 return is_valid ? LOWORD(error) : 0; | |
140 } | |
141 | |
142 // Returns the files in a BITS job. | |
143 HRESULT GetFilesInJob(IBackgroundCopyJob* job, | |
144 std::vector<ScopedComPtr<IBackgroundCopyFile> >* files) { | |
145 ScopedComPtr<IEnumBackgroundCopyFiles> enum_files; | |
146 HRESULT hr = job->EnumFiles(enum_files.Receive()); | |
147 if (FAILED(hr)) | |
148 return hr; | |
149 | |
150 ULONG num_files = 0; | |
151 hr = enum_files->GetCount(&num_files); | |
152 if (FAILED(hr)) | |
153 return hr; | |
154 | |
155 for (ULONG i = 0; i != num_files; ++i) { | |
156 ScopedComPtr<IBackgroundCopyFile> file; | |
157 if (enum_files->Next(1, file.Receive(), NULL) == S_OK && file.get()) | |
158 files->push_back(file); | |
159 } | |
160 | |
161 return S_OK; | |
162 } | |
163 | |
164 // Returns the file name, the url, and some per-file progress information. | |
165 // The function out parameters can be NULL if that data is not requested. | |
166 HRESULT GetJobFileProperties(IBackgroundCopyFile* file, | |
167 base::string16* local_name, | |
168 base::string16* remote_name, | |
169 BG_FILE_PROGRESS* progress) { | |
170 if (!file) | |
171 return E_FAIL; | |
172 | |
173 HRESULT hr = S_OK; | |
174 | |
175 if (local_name) { | |
176 ScopedCoMem<base::char16> name; | |
177 hr = file->GetLocalName(&name); | |
178 if (FAILED(hr)) | |
179 return hr; | |
180 local_name->assign(name); | |
181 } | |
182 | |
183 if (remote_name) { | |
184 ScopedCoMem<base::char16> name; | |
185 hr = file->GetRemoteName(&name); | |
186 if (FAILED(hr)) | |
187 return hr; | |
188 remote_name->assign(name); | |
189 } | |
190 | |
191 if (progress) { | |
192 BG_FILE_PROGRESS bg_file_progress = {}; | |
193 hr = file->GetProgress(&bg_file_progress); | |
194 if (FAILED(hr)) | |
195 return hr; | |
196 *progress = bg_file_progress; | |
197 } | |
198 | |
199 return hr; | |
200 } | |
201 | |
202 // Returns the number of bytes downloaded and bytes to download for all files | |
203 // in the job. If the values are not known or if an error has occurred, | |
204 // a value of -1 is reported. | |
205 HRESULT GetJobByteCount(IBackgroundCopyJob* job, | |
206 int64_t* downloaded_bytes, | |
207 int64_t* total_bytes) { | |
208 *downloaded_bytes = -1; | |
209 *total_bytes = -1; | |
210 | |
211 if (!job) | |
212 return E_FAIL; | |
213 | |
214 BG_JOB_PROGRESS job_progress = {0}; | |
215 HRESULT hr = job->GetProgress(&job_progress); | |
216 if (FAILED(hr)) | |
217 return hr; | |
218 | |
219 const uint64_t kMaxNumBytes = | |
220 static_cast<uint64_t>(std::numeric_limits<int64_t>::max()); | |
221 if (job_progress.BytesTransferred <= kMaxNumBytes) | |
222 *downloaded_bytes = job_progress.BytesTransferred; | |
223 | |
224 if (job_progress.BytesTotal <= kMaxNumBytes && | |
225 job_progress.BytesTotal != BG_SIZE_UNKNOWN) | |
226 *total_bytes = job_progress.BytesTotal; | |
227 | |
228 return S_OK; | |
229 } | |
230 | |
231 HRESULT GetJobDescription(IBackgroundCopyJob* job, const base::string16* name) { | |
232 ScopedCoMem<base::char16> description; | |
233 return job->GetDescription(&description); | |
234 } | |
235 | |
236 // Returns the job error code in |error_code| if the job is in the transient | |
237 // or the final error state. Otherwise, the job error is not available and | |
238 // the function fails. | |
239 HRESULT GetJobError(IBackgroundCopyJob* job, HRESULT* error_code_out) { | |
240 *error_code_out = S_OK; | |
241 ScopedComPtr<IBackgroundCopyError> copy_error; | |
242 HRESULT hr = job->GetError(copy_error.Receive()); | |
243 if (FAILED(hr)) | |
244 return hr; | |
245 | |
246 BG_ERROR_CONTEXT error_context = BG_ERROR_CONTEXT_NONE; | |
247 HRESULT error_code = S_OK; | |
248 hr = copy_error->GetError(&error_context, &error_code); | |
249 if (FAILED(hr)) | |
250 return hr; | |
251 | |
252 *error_code_out = FAILED(error_code) ? error_code : E_FAIL; | |
253 return S_OK; | |
254 } | |
255 | |
256 // Finds the component updater jobs matching the given predicate. | |
257 // Returns S_OK if the function has found at least one job, returns S_FALSE if | |
258 // no job was found, and it returns an error otherwise. | |
259 template <class Predicate> | |
260 HRESULT FindBitsJobIf(Predicate pred, | |
261 IBackgroundCopyManager* bits_manager, | |
262 std::vector<ScopedComPtr<IBackgroundCopyJob> >* jobs) { | |
263 ScopedComPtr<IEnumBackgroundCopyJobs> enum_jobs; | |
264 HRESULT hr = bits_manager->EnumJobs(0, enum_jobs.Receive()); | |
265 if (FAILED(hr)) | |
266 return hr; | |
267 | |
268 ULONG job_count = 0; | |
269 hr = enum_jobs->GetCount(&job_count); | |
270 if (FAILED(hr)) | |
271 return hr; | |
272 | |
273 // Iterate over jobs, run the predicate, and select the job only if | |
274 // the job description matches the component updater jobs. | |
275 for (ULONG i = 0; i != job_count; ++i) { | |
276 ScopedComPtr<IBackgroundCopyJob> current_job; | |
277 if (enum_jobs->Next(1, current_job.Receive(), NULL) == S_OK && | |
278 pred(current_job.get())) { | |
279 base::string16 job_description; | |
280 hr = GetJobDescription(current_job.get(), &job_description); | |
281 if (job_description.compare(kJobDescription) == 0) | |
282 jobs->push_back(current_job); | |
283 } | |
284 } | |
285 | |
286 return jobs->empty() ? S_FALSE : S_OK; | |
287 } | |
288 | |
289 // Compares the job creation time and returns true if the job creation time | |
290 // is older than |num_days|. | |
291 struct JobCreationOlderThanDays | |
292 : public std::binary_function<IBackgroundCopyJob*, int, bool> { | |
293 bool operator()(IBackgroundCopyJob* job, int num_days) const; | |
294 }; | |
295 | |
296 bool JobCreationOlderThanDays::operator()(IBackgroundCopyJob* job, | |
297 int num_days) const { | |
298 BG_JOB_TIMES times = {0}; | |
299 HRESULT hr = job->GetTimes(×); | |
300 if (FAILED(hr)) | |
301 return false; | |
302 | |
303 const base::TimeDelta time_delta(base::TimeDelta::FromDays(num_days)); | |
304 const base::Time creation_time(base::Time::FromFileTime(times.CreationTime)); | |
305 | |
306 return creation_time + time_delta < base::Time::Now(); | |
307 } | |
308 | |
309 // Compares the url of a file in a job and returns true if the remote name | |
310 // of any file in a job matches the argument. | |
311 struct JobFileUrlEqual : public std::binary_function<IBackgroundCopyJob*, | |
312 const base::string16&, | |
313 bool> { | |
314 bool operator()(IBackgroundCopyJob* job, | |
315 const base::string16& remote_name) const; | |
316 }; | |
317 | |
318 bool JobFileUrlEqual::operator()(IBackgroundCopyJob* job, | |
319 const base::string16& remote_name) const { | |
320 std::vector<ScopedComPtr<IBackgroundCopyFile> > files; | |
321 HRESULT hr = GetFilesInJob(job, &files); | |
322 if (FAILED(hr)) | |
323 return false; | |
324 | |
325 for (size_t i = 0; i != files.size(); ++i) { | |
326 ScopedCoMem<base::char16> name; | |
327 if (SUCCEEDED(files[i]->GetRemoteName(&name)) && | |
328 remote_name.compare(name) == 0) | |
329 return true; | |
330 } | |
331 | |
332 return false; | |
333 } | |
334 | |
335 // Creates an instance of the BITS manager. | |
336 HRESULT GetBitsManager(IBackgroundCopyManager** bits_manager) { | |
337 ScopedComPtr<IBackgroundCopyManager> object; | |
338 HRESULT hr = object.CreateInstance(__uuidof(BackgroundCopyManager)); | |
339 if (FAILED(hr)) { | |
340 return hr; | |
341 } | |
342 *bits_manager = object.Detach(); | |
343 return S_OK; | |
344 } | |
345 | |
346 void CleanupJobFiles(IBackgroundCopyJob* job) { | |
347 std::vector<ScopedComPtr<IBackgroundCopyFile> > files; | |
348 if (FAILED(GetFilesInJob(job, &files))) | |
349 return; | |
350 for (size_t i = 0; i != files.size(); ++i) { | |
351 base::string16 local_name; | |
352 HRESULT hr(GetJobFileProperties(files[i].get(), &local_name, NULL, NULL)); | |
353 if (SUCCEEDED(hr)) | |
354 DeleteFileAndEmptyParentDirectory(base::FilePath(local_name)); | |
355 } | |
356 } | |
357 | |
358 // Cleans up incompleted jobs that are too old. | |
359 HRESULT CleanupStaleJobs( | |
360 base::win::ScopedComPtr<IBackgroundCopyManager> bits_manager) { | |
361 if (!bits_manager.get()) | |
362 return E_FAIL; | |
363 | |
364 static base::Time last_sweep; | |
365 | |
366 const base::TimeDelta time_delta( | |
367 base::TimeDelta::FromDays(kPurgeStaleJobsIntervalBetweenChecksDays)); | |
368 const base::Time current_time(base::Time::Now()); | |
369 if (last_sweep + time_delta > current_time) | |
370 return S_OK; | |
371 | |
372 last_sweep = current_time; | |
373 | |
374 std::vector<ScopedComPtr<IBackgroundCopyJob> > jobs; | |
375 HRESULT hr = FindBitsJobIf( | |
376 std::bind2nd(JobCreationOlderThanDays(), kPurgeStaleJobsAfterDays), | |
377 bits_manager.get(), &jobs); | |
378 if (FAILED(hr)) | |
379 return hr; | |
380 | |
381 for (size_t i = 0; i != jobs.size(); ++i) { | |
382 jobs[i]->Cancel(); | |
383 CleanupJobFiles(jobs[i].get()); | |
384 } | |
385 | |
386 return S_OK; | |
387 } | |
388 | |
389 } // namespace | |
390 | |
391 BackgroundDownloader::BackgroundDownloader( | |
392 scoped_ptr<CrxDownloader> successor, | |
393 net::URLRequestContextGetter* context_getter, | |
394 scoped_refptr<base::SingleThreadTaskRunner> task_runner) | |
395 : CrxDownloader(successor.Pass()), | |
396 main_task_runner_(base::MessageLoopProxy::current()), | |
397 context_getter_(context_getter), | |
398 task_runner_(task_runner), | |
399 is_completed_(false) { | |
400 } | |
401 | |
402 BackgroundDownloader::~BackgroundDownloader() { | |
403 DCHECK(thread_checker_.CalledOnValidThread()); | |
404 | |
405 // The following objects have thread affinity and can't be destroyed on the | |
406 // main thread. The resources managed by these objects are acquired at the | |
407 // beginning of a download and released at the end of the download. Most of | |
408 // the time, when this destructor is called, these resources have been already | |
409 // disposed by. Releasing the ownership here is a NOP. However, if the browser | |
410 // is shutting down while a download is in progress, the timer is active and | |
411 // the interface pointers are valid. Releasing the ownership means leaking | |
412 // these objects and their associated resources. | |
413 timer_.release(); | |
414 bits_manager_.Detach(); | |
415 job_.Detach(); | |
416 } | |
417 | |
418 void BackgroundDownloader::DoStartDownload(const GURL& url) { | |
419 DCHECK(thread_checker_.CalledOnValidThread()); | |
420 | |
421 task_runner_->PostTask( | |
422 FROM_HERE, | |
423 base::Bind( | |
424 &BackgroundDownloader::BeginDownload, base::Unretained(this), url)); | |
425 } | |
426 | |
427 // Called once when this class is asked to do a download. Creates or opens | |
428 // an existing bits job, hooks up the notifications, and starts the timer. | |
429 void BackgroundDownloader::BeginDownload(const GURL& url) { | |
430 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
431 | |
432 DCHECK(!timer_); | |
433 | |
434 is_completed_ = false; | |
435 download_start_time_ = base::Time::Now(); | |
436 job_stuck_begin_time_ = download_start_time_; | |
437 | |
438 HRESULT hr = QueueBitsJob(url); | |
439 if (FAILED(hr)) { | |
440 EndDownload(hr); | |
441 return; | |
442 } | |
443 | |
444 // A repeating timer retains the user task. This timer can be stopped and | |
445 // reset multiple times. | |
446 timer_.reset(new base::RepeatingTimer<BackgroundDownloader>); | |
447 timer_->Start(FROM_HERE, | |
448 base::TimeDelta::FromSeconds(kJobPollingIntervalSec), | |
449 this, | |
450 &BackgroundDownloader::OnDownloading); | |
451 } | |
452 | |
453 // Called any time the timer fires. | |
454 void BackgroundDownloader::OnDownloading() { | |
455 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
456 | |
457 DCHECK(job_.get()); | |
458 | |
459 DCHECK(!is_completed_); | |
460 if (is_completed_) | |
461 return; | |
462 | |
463 BG_JOB_STATE job_state = BG_JOB_STATE_ERROR; | |
464 HRESULT hr = job_->GetState(&job_state); | |
465 if (FAILED(hr)) { | |
466 EndDownload(hr); | |
467 return; | |
468 } | |
469 | |
470 switch (job_state) { | |
471 case BG_JOB_STATE_TRANSFERRED: | |
472 OnStateTransferred(); | |
473 return; | |
474 | |
475 case BG_JOB_STATE_ERROR: | |
476 OnStateError(); | |
477 return; | |
478 | |
479 case BG_JOB_STATE_CANCELLED: | |
480 OnStateCancelled(); | |
481 return; | |
482 | |
483 case BG_JOB_STATE_ACKNOWLEDGED: | |
484 OnStateAcknowledged(); | |
485 return; | |
486 | |
487 case BG_JOB_STATE_QUEUED: | |
488 // Fall through. | |
489 case BG_JOB_STATE_CONNECTING: | |
490 // Fall through. | |
491 case BG_JOB_STATE_SUSPENDED: | |
492 OnStateQueued(); | |
493 break; | |
494 | |
495 case BG_JOB_STATE_TRANSIENT_ERROR: | |
496 OnStateTransientError(); | |
497 break; | |
498 | |
499 case BG_JOB_STATE_TRANSFERRING: | |
500 OnStateTransferring(); | |
501 break; | |
502 | |
503 default: | |
504 break; | |
505 } | |
506 } | |
507 | |
508 // Completes the BITS download, picks up the file path of the response, and | |
509 // notifies the CrxDownloader. The function should be called only once. | |
510 void BackgroundDownloader::EndDownload(HRESULT error) { | |
511 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
512 | |
513 DCHECK(!is_completed_); | |
514 is_completed_ = true; | |
515 | |
516 timer_.reset(); | |
517 | |
518 const base::Time download_end_time(base::Time::Now()); | |
519 const base::TimeDelta download_time = | |
520 download_end_time >= download_start_time_ | |
521 ? download_end_time - download_start_time_ | |
522 : base::TimeDelta(); | |
523 | |
524 int64_t downloaded_bytes = -1; | |
525 int64_t total_bytes = -1; | |
526 GetJobByteCount(job_.get(), &downloaded_bytes, &total_bytes); | |
527 | |
528 if (FAILED(error) && job_.get()) { | |
529 job_->Cancel(); | |
530 CleanupJobFiles(job_.get()); | |
531 } | |
532 | |
533 job_ = NULL; | |
534 | |
535 CleanupStaleJobs(bits_manager_); | |
536 bits_manager_ = NULL; | |
537 | |
538 // Consider the url handled if it has been successfully downloaded or a | |
539 // 5xx has been received. | |
540 const bool is_handled = | |
541 SUCCEEDED(error) || IsHttpServerError(GetHttpStatusFromBitsError(error)); | |
542 | |
543 const int error_to_report = SUCCEEDED(error) ? 0 : error; | |
544 | |
545 DownloadMetrics download_metrics; | |
546 download_metrics.url = url(); | |
547 download_metrics.downloader = DownloadMetrics::kBits; | |
548 download_metrics.error = error_to_report; | |
549 download_metrics.downloaded_bytes = downloaded_bytes; | |
550 download_metrics.total_bytes = total_bytes; | |
551 download_metrics.download_time_ms = download_time.InMilliseconds(); | |
552 | |
553 Result result; | |
554 result.error = error_to_report; | |
555 result.response = response_; | |
556 result.downloaded_bytes = downloaded_bytes; | |
557 result.total_bytes = total_bytes; | |
558 main_task_runner_->PostTask( | |
559 FROM_HERE, | |
560 base::Bind(&BackgroundDownloader::OnDownloadComplete, | |
561 base::Unretained(this), | |
562 is_handled, | |
563 result, | |
564 download_metrics)); | |
565 | |
566 // Once the task is posted to the the main thread, this object may be deleted | |
567 // by its owner. It is not safe to access members of this object on the | |
568 // task runner from this point on. The timer is stopped and all BITS | |
569 // interface pointers have been released. | |
570 } | |
571 | |
572 // Called when the BITS job has been transferred successfully. Completes the | |
573 // BITS job by removing it from the BITS queue and making the download | |
574 // available to the caller. | |
575 void BackgroundDownloader::OnStateTransferred() { | |
576 EndDownload(CompleteJob()); | |
577 } | |
578 | |
579 // Called when the job has encountered an error and no further progress can | |
580 // be made. Cancels this job and removes it from the BITS queue. | |
581 void BackgroundDownloader::OnStateError() { | |
582 HRESULT error_code = S_OK; | |
583 HRESULT hr = GetJobError(job_.get(), &error_code); | |
584 if (FAILED(hr)) | |
585 error_code = hr; | |
586 DCHECK(FAILED(error_code)); | |
587 EndDownload(error_code); | |
588 } | |
589 | |
590 // Called when the job has encountered a transient error, such as a | |
591 // network disconnect, a server error, or some other recoverable error. | |
592 void BackgroundDownloader::OnStateTransientError() { | |
593 // If the job appears to be stuck, handle the transient error as if | |
594 // it were a final error. This causes the job to be cancelled and a specific | |
595 // error be returned, if the error was available. | |
596 if (IsStuck()) { | |
597 OnStateError(); | |
598 return; | |
599 } | |
600 | |
601 // Don't retry at all if the transient error was a 5xx. | |
602 HRESULT error_code = S_OK; | |
603 HRESULT hr = GetJobError(job_.get(), &error_code); | |
604 if (SUCCEEDED(hr) && | |
605 IsHttpServerError(GetHttpStatusFromBitsError(error_code))) { | |
606 OnStateError(); | |
607 return; | |
608 } | |
609 } | |
610 | |
611 void BackgroundDownloader::OnStateQueued() { | |
612 if (IsStuck()) | |
613 EndDownload(E_ABORT); // Return a generic error for now. | |
614 } | |
615 | |
616 void BackgroundDownloader::OnStateTransferring() { | |
617 // Resets the baseline for detecting a stuck job since the job is transferring | |
618 // data and it is making progress. | |
619 job_stuck_begin_time_ = base::Time::Now(); | |
620 | |
621 int64_t downloaded_bytes = -1; | |
622 int64_t total_bytes = -1; | |
623 HRESULT hr = GetJobByteCount(job_.get(), &downloaded_bytes, &total_bytes); | |
624 if (FAILED(hr)) | |
625 return; | |
626 | |
627 Result result; | |
628 result.downloaded_bytes = downloaded_bytes; | |
629 result.total_bytes = total_bytes; | |
630 | |
631 main_task_runner_->PostTask( | |
632 FROM_HERE, | |
633 base::Bind(&BackgroundDownloader::OnDownloadProgress, | |
634 base::Unretained(this), | |
635 result)); | |
636 } | |
637 | |
638 // Called when the download was cancelled. Since the observer should have | |
639 // been disconnected by now, this notification must not be seen. | |
640 void BackgroundDownloader::OnStateCancelled() { | |
641 EndDownload(E_UNEXPECTED); | |
642 } | |
643 | |
644 // Called when the download was completed. Same as above. | |
645 void BackgroundDownloader::OnStateAcknowledged() { | |
646 EndDownload(E_UNEXPECTED); | |
647 } | |
648 | |
649 // Creates or opens a job for the given url and queues it up. Tries to | |
650 // install a job observer but continues on if an observer can't be set up. | |
651 HRESULT BackgroundDownloader::QueueBitsJob(const GURL& url) { | |
652 DCHECK(task_runner_->RunsTasksOnCurrentThread()); | |
653 | |
654 HRESULT hr = S_OK; | |
655 if (bits_manager_.get() == NULL) { | |
656 hr = GetBitsManager(bits_manager_.Receive()); | |
657 if (FAILED(hr)) | |
658 return hr; | |
659 } | |
660 | |
661 hr = CreateOrOpenJob(url); | |
662 if (FAILED(hr)) | |
663 return hr; | |
664 | |
665 if (hr == S_OK) { | |
666 hr = InitializeNewJob(url); | |
667 if (FAILED(hr)) | |
668 return hr; | |
669 } | |
670 | |
671 return job_->Resume(); | |
672 } | |
673 | |
674 HRESULT BackgroundDownloader::CreateOrOpenJob(const GURL& url) { | |
675 std::vector<ScopedComPtr<IBackgroundCopyJob> > jobs; | |
676 HRESULT hr = FindBitsJobIf( | |
677 std::bind2nd(JobFileUrlEqual(), base::SysUTF8ToWide(url.spec())), | |
678 bits_manager_.get(), &jobs); | |
679 if (SUCCEEDED(hr) && !jobs.empty()) { | |
680 job_ = jobs.front(); | |
681 return S_FALSE; | |
682 } | |
683 | |
684 // Use kJobDescription as a temporary job display name until the proper | |
685 // display name is initialized later on. | |
686 GUID guid = {0}; | |
687 ScopedComPtr<IBackgroundCopyJob> job; | |
688 hr = bits_manager_->CreateJob( | |
689 kJobDescription, BG_JOB_TYPE_DOWNLOAD, &guid, job.Receive()); | |
690 if (FAILED(hr)) | |
691 return hr; | |
692 | |
693 job_ = job; | |
694 return S_OK; | |
695 } | |
696 | |
697 HRESULT BackgroundDownloader::InitializeNewJob(const GURL& url) { | |
698 const base::string16 filename(base::SysUTF8ToWide(url.ExtractFileName())); | |
699 | |
700 base::FilePath tempdir; | |
701 if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_BITS_"), | |
702 &tempdir)) | |
703 return E_FAIL; | |
704 | |
705 HRESULT hr = job_->AddFile(base::SysUTF8ToWide(url.spec()).c_str(), | |
706 tempdir.Append(filename).AsUTF16Unsafe().c_str()); | |
707 if (FAILED(hr)) | |
708 return hr; | |
709 | |
710 hr = job_->SetDisplayName(filename.c_str()); | |
711 if (FAILED(hr)) | |
712 return hr; | |
713 | |
714 hr = job_->SetDescription(kJobDescription); | |
715 if (FAILED(hr)) | |
716 return hr; | |
717 | |
718 hr = job_->SetPriority(BG_JOB_PRIORITY_NORMAL); | |
719 if (FAILED(hr)) | |
720 return hr; | |
721 | |
722 hr = job_->SetMinimumRetryDelay(60 * kMinimumRetryDelayMin); | |
723 if (FAILED(hr)) | |
724 return hr; | |
725 | |
726 const int kSecondsDay = 60 * 60 * 24; | |
727 hr = job_->SetNoProgressTimeout(kSecondsDay * kSetNoProgressTimeoutDays); | |
728 if (FAILED(hr)) | |
729 return hr; | |
730 | |
731 return S_OK; | |
732 } | |
733 | |
734 bool BackgroundDownloader::IsStuck() { | |
735 const base::TimeDelta job_stuck_timeout( | |
736 base::TimeDelta::FromMinutes(kJobStuckTimeoutMin)); | |
737 return job_stuck_begin_time_ + job_stuck_timeout < base::Time::Now(); | |
738 } | |
739 | |
740 HRESULT BackgroundDownloader::CompleteJob() { | |
741 HRESULT hr = job_->Complete(); | |
742 if (FAILED(hr) && hr != BG_S_UNABLE_TO_DELETE_FILES) | |
743 return hr; | |
744 | |
745 std::vector<ScopedComPtr<IBackgroundCopyFile> > files; | |
746 hr = GetFilesInJob(job_.get(), &files); | |
747 if (FAILED(hr)) | |
748 return hr; | |
749 | |
750 if (files.empty()) | |
751 return E_UNEXPECTED; | |
752 | |
753 base::string16 local_name; | |
754 BG_FILE_PROGRESS progress = {0}; | |
755 hr = GetJobFileProperties(files.front().get(), &local_name, NULL, &progress); | |
756 if (FAILED(hr)) | |
757 return hr; | |
758 | |
759 // Sanity check the post-conditions of a successful download, including | |
760 // the file and job invariants. The byte counts for a job and its file | |
761 // must match as a job only contains one file. | |
762 DCHECK(progress.Completed); | |
763 DCHECK_EQ(progress.BytesTotal, progress.BytesTransferred); | |
764 | |
765 response_ = base::FilePath(local_name); | |
766 | |
767 return S_OK; | |
768 } | |
769 | |
770 } // namespace component_updater | |
OLD | NEW |