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.
5 #include "components/component_updater/background_downloader_win.h"
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"
27 using base::win::ScopedCoMem
;
28 using base::win::ScopedComPtr
;
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.
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
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:
52 // Suspended--->Queued--->Connecting---->Transferring--->Transferred
54 // | | V V | (complete)
55 // +----------|---------+-----------------+-----+ V
56 // | | | | Acknowledged
58 // | Transient Error------->Error |
60 // | +-------+---------+--->-+
63 // +------<----------+ +---->Cancelled
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"
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.
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.
96 namespace component_updater
{
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";
104 // How often the code looks for changes in the BITS job state.
105 const int kJobPollingIntervalSec
= 4;
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;
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;
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;
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;
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;
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());
151 hr
= enum_files
->GetCount(&num_files
);
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
)
158 files
->push_back(file
);
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
) {
176 ScopedCoMem
<base::char16
> name
;
177 hr
= file
->GetLocalName(&name
);
180 local_name
->assign(name
);
184 ScopedCoMem
<base::char16
> name
;
185 hr
= file
->GetRemoteName(&name
);
188 remote_name
->assign(name
);
192 BG_FILE_PROGRESS bg_file_progress
= {};
193 hr
= file
->GetProgress(&bg_file_progress
);
196 *progress
= bg_file_progress
;
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;
214 BG_JOB_PROGRESS job_progress
= {0};
215 HRESULT hr
= job
->GetProgress(&job_progress
);
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
;
224 if (job_progress
.BytesTotal
<= kMaxNumBytes
&&
225 job_progress
.BytesTotal
!= BG_SIZE_UNKNOWN
)
226 *total_bytes
= job_progress
.BytesTotal
;
231 HRESULT
GetJobDescription(IBackgroundCopyJob
* job
, const base::string16
* name
) {
232 ScopedCoMem
<base::char16
> description
;
233 return job
->GetDescription(&description
);
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());
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
);
252 *error_code_out
= FAILED(error_code
) ? error_code
: E_FAIL
;
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());
269 hr
= enum_jobs
->GetCount(&job_count
);
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
&&
279 base::string16 job_description
;
280 hr
= GetJobDescription(current_job
, &job_description
);
281 if (job_description
.compare(kJobDescription
) == 0)
282 jobs
->push_back(current_job
);
286 return jobs
->empty() ? S_FALSE
: S_OK
;
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;
296 bool JobCreationOlderThanDays::operator()(IBackgroundCopyJob
* job
,
297 int num_days
) const {
298 BG_JOB_TIMES times
= {0};
299 HRESULT hr
= job
->GetTimes(×
);
303 const base::TimeDelta
time_delta(base::TimeDelta::FromDays(num_days
));
304 const base::Time
creation_time(base::Time::FromFileTime(times
.CreationTime
));
306 return creation_time
+ time_delta
< base::Time::Now();
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
&,
314 bool operator()(IBackgroundCopyJob
* job
,
315 const base::string16
& remote_name
) const;
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
);
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)
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
));
342 *bits_manager
= object
.Detach();
346 void CleanupJobFiles(IBackgroundCopyJob
* job
) {
347 std::vector
<ScopedComPtr
<IBackgroundCopyFile
> > files
;
348 if (FAILED(GetFilesInJob(job
, &files
)))
350 for (size_t i
= 0; i
!= files
.size(); ++i
) {
351 base::string16 local_name
;
352 HRESULT
hr(GetJobFileProperties(files
[i
], &local_name
, NULL
, NULL
));
354 DeleteFileAndEmptyParentDirectory(base::FilePath(local_name
));
358 // Cleans up incompleted jobs that are too old.
359 HRESULT
CleanupStaleJobs(
360 base::win::ScopedComPtr
<IBackgroundCopyManager
> bits_manager
) {
364 static base::Time last_sweep
;
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
)
372 last_sweep
= current_time
;
374 std::vector
<ScopedComPtr
<IBackgroundCopyJob
> > jobs
;
375 HRESULT hr
= FindBitsJobIf(
376 std::bind2nd(JobCreationOlderThanDays(), kPurgeStaleJobsAfterDays
),
382 for (size_t i
= 0; i
!= jobs
.size(); ++i
) {
384 CleanupJobFiles(jobs
[i
]);
392 BackgroundDownloader::BackgroundDownloader(
393 scoped_ptr
<CrxDownloader
> successor
,
394 net::URLRequestContextGetter
* context_getter
,
395 scoped_refptr
<base::SingleThreadTaskRunner
> task_runner
)
396 : CrxDownloader(successor
.Pass()),
397 main_task_runner_(base::MessageLoopProxy::current()),
398 context_getter_(context_getter
),
399 task_runner_(task_runner
),
400 is_completed_(false) {
403 BackgroundDownloader::~BackgroundDownloader() {
404 DCHECK(thread_checker_
.CalledOnValidThread());
406 // The following objects have thread affinity and can't be destroyed on the
407 // main thread. The resources managed by these objects are acquired at the
408 // beginning of a download and released at the end of the download. Most of
409 // the time, when this destructor is called, these resources have been already
410 // disposed by. Releasing the ownership here is a NOP. However, if the browser
411 // is shutting down while a download is in progress, the timer is active and
412 // the interface pointers are valid. Releasing the ownership means leaking
413 // these objects and their associated resources.
415 bits_manager_
.Detach();
419 void BackgroundDownloader::DoStartDownload(const GURL
& url
) {
420 DCHECK(thread_checker_
.CalledOnValidThread());
422 task_runner_
->PostTask(
425 &BackgroundDownloader::BeginDownload
, base::Unretained(this), url
));
428 // Called once when this class is asked to do a download. Creates or opens
429 // an existing bits job, hooks up the notifications, and starts the timer.
430 void BackgroundDownloader::BeginDownload(const GURL
& url
) {
431 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
435 is_completed_
= false;
436 download_start_time_
= base::Time::Now();
437 job_stuck_begin_time_
= download_start_time_
;
439 HRESULT hr
= QueueBitsJob(url
);
445 // A repeating timer retains the user task. This timer can be stopped and
446 // reset multiple times.
447 timer_
.reset(new base::RepeatingTimer
<BackgroundDownloader
>);
448 timer_
->Start(FROM_HERE
,
449 base::TimeDelta::FromSeconds(kJobPollingIntervalSec
),
451 &BackgroundDownloader::OnDownloading
);
454 // Called any time the timer fires.
455 void BackgroundDownloader::OnDownloading() {
456 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
460 DCHECK(!is_completed_
);
464 BG_JOB_STATE job_state
= BG_JOB_STATE_ERROR
;
465 HRESULT hr
= job_
->GetState(&job_state
);
472 case BG_JOB_STATE_TRANSFERRED
:
473 OnStateTransferred();
476 case BG_JOB_STATE_ERROR
:
480 case BG_JOB_STATE_CANCELLED
:
484 case BG_JOB_STATE_ACKNOWLEDGED
:
485 OnStateAcknowledged();
488 case BG_JOB_STATE_QUEUED
:
490 case BG_JOB_STATE_CONNECTING
:
492 case BG_JOB_STATE_SUSPENDED
:
496 case BG_JOB_STATE_TRANSIENT_ERROR
:
497 OnStateTransientError();
500 case BG_JOB_STATE_TRANSFERRING
:
501 OnStateTransferring();
509 // Completes the BITS download, picks up the file path of the response, and
510 // notifies the CrxDownloader. The function should be called only once.
511 void BackgroundDownloader::EndDownload(HRESULT error
) {
512 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
514 DCHECK(!is_completed_
);
515 is_completed_
= true;
519 const base::Time
download_end_time(base::Time::Now());
520 const base::TimeDelta download_time
=
521 download_end_time
>= download_start_time_
522 ? download_end_time
- download_start_time_
525 int64_t downloaded_bytes
= -1;
526 int64_t total_bytes
= -1;
527 GetJobByteCount(job_
, &downloaded_bytes
, &total_bytes
);
529 if (FAILED(error
) && job_
) {
531 CleanupJobFiles(job_
);
536 CleanupStaleJobs(bits_manager_
);
537 bits_manager_
= NULL
;
539 // Consider the url handled if it has been successfully downloaded or a
540 // 5xx has been received.
541 const bool is_handled
=
542 SUCCEEDED(error
) || IsHttpServerError(GetHttpStatusFromBitsError(error
));
544 const int error_to_report
= SUCCEEDED(error
) ? 0 : error
;
546 DownloadMetrics download_metrics
;
547 download_metrics
.url
= url();
548 download_metrics
.downloader
= DownloadMetrics::kBits
;
549 download_metrics
.error
= error_to_report
;
550 download_metrics
.downloaded_bytes
= downloaded_bytes
;
551 download_metrics
.total_bytes
= total_bytes
;
552 download_metrics
.download_time_ms
= download_time
.InMilliseconds();
555 result
.error
= error_to_report
;
556 result
.response
= response_
;
557 result
.downloaded_bytes
= downloaded_bytes
;
558 result
.total_bytes
= total_bytes
;
559 main_task_runner_
->PostTask(
561 base::Bind(&BackgroundDownloader::OnDownloadComplete
,
562 base::Unretained(this),
567 // Once the task is posted to the the main thread, this object may be deleted
568 // by its owner. It is not safe to access members of this object on the
569 // task runner from this point on. The timer is stopped and all BITS
570 // interface pointers have been released.
573 // Called when the BITS job has been transferred successfully. Completes the
574 // BITS job by removing it from the BITS queue and making the download
575 // available to the caller.
576 void BackgroundDownloader::OnStateTransferred() {
577 EndDownload(CompleteJob());
580 // Called when the job has encountered an error and no further progress can
581 // be made. Cancels this job and removes it from the BITS queue.
582 void BackgroundDownloader::OnStateError() {
583 HRESULT error_code
= S_OK
;
584 HRESULT hr
= GetJobError(job_
, &error_code
);
587 DCHECK(FAILED(error_code
));
588 EndDownload(error_code
);
591 // Called when the job has encountered a transient error, such as a
592 // network disconnect, a server error, or some other recoverable error.
593 void BackgroundDownloader::OnStateTransientError() {
594 // If the job appears to be stuck, handle the transient error as if
595 // it were a final error. This causes the job to be cancelled and a specific
596 // error be returned, if the error was available.
602 // Don't retry at all if the transient error was a 5xx.
603 HRESULT error_code
= S_OK
;
604 HRESULT hr
= GetJobError(job_
, &error_code
);
606 IsHttpServerError(GetHttpStatusFromBitsError(error_code
))) {
612 void BackgroundDownloader::OnStateQueued() {
614 EndDownload(E_ABORT
); // Return a generic error for now.
617 void BackgroundDownloader::OnStateTransferring() {
618 // Resets the baseline for detecting a stuck job since the job is transferring
619 // data and it is making progress.
620 job_stuck_begin_time_
= base::Time::Now();
622 int64_t downloaded_bytes
= -1;
623 int64_t total_bytes
= -1;
624 HRESULT hr
= GetJobByteCount(job_
, &downloaded_bytes
, &total_bytes
);
629 result
.downloaded_bytes
= downloaded_bytes
;
630 result
.total_bytes
= total_bytes
;
632 main_task_runner_
->PostTask(
634 base::Bind(&BackgroundDownloader::OnDownloadProgress
,
635 base::Unretained(this),
639 // Called when the download was cancelled. Since the observer should have
640 // been disconnected by now, this notification must not be seen.
641 void BackgroundDownloader::OnStateCancelled() {
642 EndDownload(E_UNEXPECTED
);
645 // Called when the download was completed. Same as above.
646 void BackgroundDownloader::OnStateAcknowledged() {
647 EndDownload(E_UNEXPECTED
);
650 // Creates or opens a job for the given url and queues it up. Tries to
651 // install a job observer but continues on if an observer can't be set up.
652 HRESULT
BackgroundDownloader::QueueBitsJob(const GURL
& url
) {
653 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
656 if (bits_manager_
== NULL
) {
657 hr
= GetBitsManager(bits_manager_
.Receive());
662 hr
= CreateOrOpenJob(url
);
667 hr
= InitializeNewJob(url
);
672 return job_
->Resume();
675 HRESULT
BackgroundDownloader::CreateOrOpenJob(const GURL
& url
) {
676 std::vector
<ScopedComPtr
<IBackgroundCopyJob
> > jobs
;
677 HRESULT hr
= FindBitsJobIf(
678 std::bind2nd(JobFileUrlEqual(), base::SysUTF8ToWide(url
.spec())),
681 if (SUCCEEDED(hr
) && !jobs
.empty()) {
686 // Use kJobDescription as a temporary job display name until the proper
687 // display name is initialized later on.
689 ScopedComPtr
<IBackgroundCopyJob
> job
;
690 hr
= bits_manager_
->CreateJob(
691 kJobDescription
, BG_JOB_TYPE_DOWNLOAD
, &guid
, job
.Receive());
699 HRESULT
BackgroundDownloader::InitializeNewJob(const GURL
& url
) {
700 const base::string16
filename(base::SysUTF8ToWide(url
.ExtractFileName()));
702 base::FilePath tempdir
;
703 if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_BITS_"),
707 HRESULT hr
= job_
->AddFile(base::SysUTF8ToWide(url
.spec()).c_str(),
708 tempdir
.Append(filename
).AsUTF16Unsafe().c_str());
712 hr
= job_
->SetDisplayName(filename
.c_str());
716 hr
= job_
->SetDescription(kJobDescription
);
720 hr
= job_
->SetPriority(BG_JOB_PRIORITY_NORMAL
);
724 hr
= job_
->SetMinimumRetryDelay(60 * kMinimumRetryDelayMin
);
728 const int kSecondsDay
= 60 * 60 * 24;
729 hr
= job_
->SetNoProgressTimeout(kSecondsDay
* kSetNoProgressTimeoutDays
);
736 bool BackgroundDownloader::IsStuck() {
737 const base::TimeDelta
job_stuck_timeout(
738 base::TimeDelta::FromMinutes(kJobStuckTimeoutMin
));
739 return job_stuck_begin_time_
+ job_stuck_timeout
< base::Time::Now();
742 HRESULT
BackgroundDownloader::CompleteJob() {
743 HRESULT hr
= job_
->Complete();
744 if (FAILED(hr
) && hr
!= BG_S_UNABLE_TO_DELETE_FILES
)
747 std::vector
<ScopedComPtr
<IBackgroundCopyFile
> > files
;
748 hr
= GetFilesInJob(job_
, &files
);
755 base::string16 local_name
;
756 BG_FILE_PROGRESS progress
= {0};
757 hr
= GetJobFileProperties(files
.front(), &local_name
, NULL
, &progress
);
761 // Sanity check the post-conditions of a successful download, including
762 // the file and job invariants. The byte counts for a job and its file
763 // must match as a job only contains one file.
764 DCHECK(progress
.Completed
);
765 DCHECK_EQ(progress
.BytesTotal
, progress
.BytesTransferred
);
767 response_
= base::FilePath(local_name
);
772 } // namespace component_updater