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