Chromium Code Reviews| Index: webkit/appcache/appcache_update_job.cc |
| =================================================================== |
| --- webkit/appcache/appcache_update_job.cc (revision 30060) |
| +++ webkit/appcache/appcache_update_job.cc (working copy) |
| @@ -20,7 +20,8 @@ |
| // Extra info associated with requests for use during response processing. |
| // This info is deleted when the URLRequest is deleted. |
| -struct UpdateJobInfo : public URLRequest::UserData { |
| +class UpdateJobInfo : public URLRequest::UserData { |
| + public: |
| enum RequestType { |
| MANIFEST_FETCH, |
| URL_FETCH, |
| @@ -28,16 +29,40 @@ |
| }; |
| explicit UpdateJobInfo(RequestType request_type) |
| - : type(request_type), |
| - buffer(new net::IOBuffer(kBufferSize)), |
| - retry_503_attempts(0) { |
| + : type_(request_type), |
| + buffer_(new net::IOBuffer(kBufferSize)), |
| + retry_503_attempts_(0), |
| + update_job_(NULL), |
| + request_(NULL), |
| + wrote_response_info_(false), |
| + ALLOW_THIS_IN_INITIALIZER_LIST(write_callback_( |
| + this, &UpdateJobInfo::OnWriteComplete)) { |
| } |
| - RequestType type; |
| - scoped_refptr<net::IOBuffer> buffer; |
| - // TODO(jennb): need storage info to stream response data to storage |
| + void SetUpResponseWriter(AppCacheResponseWriter* writer, |
| + AppCacheUpdateJob* update, |
| + URLRequest* request) { |
| + DCHECK(!response_writer_.get()); |
| + response_writer_.reset(writer); |
| + update_job_ = update; |
| + request_ = request; |
| + } |
| - int retry_503_attempts; |
| + void OnWriteComplete(int result) { |
| + // A completed write may delete the URL request and this object. |
| + update_job_->OnWriteResponseComplete(result, request_, this); |
| + } |
| + |
| + RequestType type_; |
| + scoped_refptr<net::IOBuffer> buffer_; |
| + int retry_503_attempts_; |
| + |
| + // Info needed to write responses to storage and process callbacks. |
| + scoped_ptr<AppCacheResponseWriter> response_writer_; |
| + AppCacheUpdateJob* update_job_; |
| + URLRequest* request_; |
| + bool wrote_response_info_; |
| + net::CompletionCallbackImpl<UpdateJobInfo> write_callback_; |
| }; |
| // Helper class for collecting hosts per frontend when sending notifications |
| @@ -82,7 +107,11 @@ |
| internal_state_(FETCH_MANIFEST), |
| master_entries_completed_(0), |
| url_fetches_completed_(0), |
| - manifest_url_request_(NULL) { |
| + manifest_url_request_(NULL), |
| + ALLOW_THIS_IN_INITIALIZER_LIST(manifest_info_write_callback_( |
| + this, &AppCacheUpdateJob::OnManifestInfoWriteComplete)), |
| + ALLOW_THIS_IN_INITIALIZER_LIST(manifest_data_write_callback_( |
| + this, &AppCacheUpdateJob::OnManifestDataWriteComplete)) { |
| DCHECK(group_); |
| manifest_url_ = group_->manifest_url(); |
| } |
| @@ -160,13 +189,14 @@ |
| void AppCacheUpdateJob::ReadResponseData(URLRequest* request) { |
| if (internal_state_ == CACHE_FAILURE || internal_state_ == CANCELLED || |
| - internal_state_ == COMPLETED) |
| + internal_state_ == COMPLETED) { |
| return; |
| + } |
| int bytes_read = 0; |
| UpdateJobInfo* info = |
| static_cast<UpdateJobInfo*>(request->GetUserData(this)); |
| - request->Read(info->buffer, kBufferSize, &bytes_read); |
| + request->Read(info->buffer_, kBufferSize, &bytes_read); |
| OnReadCompleted(request, bytes_read); |
| } |
| @@ -179,7 +209,7 @@ |
| data_consumed = ConsumeResponseData(request, info, bytes_read); |
| if (data_consumed) { |
| bytes_read = 0; |
| - while (request->Read(info->buffer, kBufferSize, &bytes_read)) { |
| + while (request->Read(info->buffer_, kBufferSize, &bytes_read)) { |
| if (bytes_read > 0) { |
| data_consumed = ConsumeResponseData(request, info, bytes_read); |
| if (!data_consumed) |
| @@ -198,20 +228,22 @@ |
| bool AppCacheUpdateJob::ConsumeResponseData(URLRequest* request, |
| UpdateJobInfo* info, |
| int bytes_read) { |
| - switch (info->type) { |
| + DCHECK_GT(bytes_read, 0); |
| + switch (info->type_) { |
| case UpdateJobInfo::MANIFEST_FETCH: |
| - manifest_data_.append(info->buffer->data(), bytes_read); |
| + manifest_data_.append(info->buffer_->data(), bytes_read); |
| break; |
| case UpdateJobInfo::URL_FETCH: |
| - // TODO(jennb): stream data to storage. will be async so need to wait |
| - // for callback before reading next chunk. |
| - // For now, schedule a task to continue reading to simulate async-ness. |
| - MessageLoop::current()->PostTask(FROM_HERE, |
| - method_factory_.NewRunnableMethod( |
| - &AppCacheUpdateJob::ReadResponseData, request)); |
| - return false; |
| + if (!info->response_writer_.get()) { |
| + info->SetUpResponseWriter( |
| + service_->storage()->CreateResponseWriter(manifest_url_), |
| + this, request); |
|
michaeln
2009/10/30 18:48:16
indents are off here
jennb
2009/10/30 18:50:33
Done.
|
| + } |
| + info->response_writer_->WriteData(info->buffer_, bytes_read, |
| + &info->write_callback_); |
| + return false; // wait for async write completion to continue reading |
| case UpdateJobInfo::MANIFEST_REFETCH: |
| - manifest_refetch_data_.append(info->buffer->data(), bytes_read); |
| + manifest_refetch_data_.append(info->buffer_->data(), bytes_read); |
| break; |
| default: |
| NOTREACHED(); |
| @@ -219,6 +251,29 @@ |
| return true; |
| } |
| +void AppCacheUpdateJob::OnWriteResponseComplete(int result, |
| + URLRequest* request, |
| + UpdateJobInfo* info) { |
| + DCHECK(internal_state_ == DOWNLOADING); |
| + |
| + if (result < 0) { |
| + request->Cancel(); |
| + OnResponseCompleted(request); |
| + return; |
| + } |
| + |
| + if (!info->wrote_response_info_) { |
| + info->wrote_response_info_ = true; |
| + scoped_refptr<HttpResponseInfoIOBuffer> io_buffer = |
| + new HttpResponseInfoIOBuffer( |
| + new net::HttpResponseInfo(request->response_info())); |
| + info->response_writer_->WriteInfo(io_buffer, &info->write_callback_); |
| + return; |
| + } |
| + |
| + ReadResponseData(request); |
| +} |
| + |
| void AppCacheUpdateJob::OnReceivedRedirect(URLRequest* request, |
| const GURL& new_url, |
| bool* defer_redirect) { |
| @@ -237,7 +292,7 @@ |
| UpdateJobInfo* info = |
| static_cast<UpdateJobInfo*>(request->GetUserData(this)); |
| - switch (info->type) { |
| + switch (info->type_) { |
| case UpdateJobInfo::MANIFEST_FETCH: |
| HandleManifestFetchCompleted(request); |
| break; |
| @@ -257,7 +312,7 @@ |
| bool AppCacheUpdateJob::RetryRequest(URLRequest* request) { |
| UpdateJobInfo* info = |
| static_cast<UpdateJobInfo*>(request->GetUserData(this)); |
| - if (info->retry_503_attempts >= kMax503Retries) { |
| + if (info->retry_503_attempts_ >= kMax503Retries) { |
| return false; |
| } |
| @@ -266,13 +321,13 @@ |
| const GURL& url = request->original_url(); |
| URLRequest* retry = new URLRequest(url, this); |
| - UpdateJobInfo* retry_info = new UpdateJobInfo(info->type); |
| - retry_info->retry_503_attempts = info->retry_503_attempts + 1; |
| + UpdateJobInfo* retry_info = new UpdateJobInfo(info->type_); |
| + retry_info->retry_503_attempts_ = info->retry_503_attempts_ + 1; |
| retry->SetUserData(this, retry_info); |
| retry->set_context(request->context()); |
| retry->set_load_flags(request->load_flags()); |
| - switch (info->type) { |
| + switch (info->type_) { |
| case UpdateJobInfo::MANIFEST_FETCH: |
| case UpdateJobInfo::MANIFEST_REFETCH: |
| manifest_url_request_ = retry; |
| @@ -307,6 +362,8 @@ |
| int response_code = request->GetResponseCode(); |
| std::string mime_type; |
| request->GetMimeType(&mime_type); |
| + manifest_response_info_.reset( |
| + new net::HttpResponseInfo(request->response_info())); |
| if ((response_code / 100 == 2) && mime_type == kManifestMimeType) { |
| if (update_type_ == UPGRADE_ATTEMPT) |
| @@ -315,20 +372,29 @@ |
| ContinueHandleManifestFetchCompleted(true); |
| } else if (response_code == 304 && update_type_ == UPGRADE_ATTEMPT) { |
| ContinueHandleManifestFetchCompleted(false); |
| + } else if (response_code == 404 || response_code == 410) { |
| + service_->storage()->MakeGroupObsolete(group_, this); // async |
| } else { |
| - if (response_code == 404 || response_code == 410) { |
| - group_->set_obsolete(true); |
| - NotifyAllAssociatedHosts(OBSOLETE_EVENT); |
| - NotifyAllPendingMasterHosts(ERROR_EVENT); |
| - internal_state_ = COMPLETED; |
| - } else { |
| - LOG(INFO) << "Cache failure, response code: " << response_code; |
| - internal_state_ = CACHE_FAILURE; |
| - } |
| + LOG(INFO) << "Cache failure, response code: " << response_code; |
| + internal_state_ = CACHE_FAILURE; |
| MaybeCompleteUpdate(); // if not done, run async cache failure steps |
| } |
| } |
| +void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup* group, |
| + bool success) { |
| + NotifyAllPendingMasterHosts(ERROR_EVENT); |
| + if (success) { |
| + DCHECK(group->is_obsolete()); |
| + NotifyAllAssociatedHosts(OBSOLETE_EVENT); |
| + internal_state_ = COMPLETED; |
| + } else { |
| + // Treat failure to mark group obsolete as a cache failure. |
| + internal_state_ = CACHE_FAILURE; |
| + } |
| + MaybeCompleteUpdate(); |
| +} |
| + |
| void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) { |
| DCHECK(internal_state_ == FETCH_MANIFEST); |
| @@ -382,8 +448,14 @@ |
| int response_code = request->GetResponseCode(); |
| AppCacheEntry& entry = url_file_list_.find(url)->second; |
| + UpdateJobInfo* info = |
| + static_cast<UpdateJobInfo*>(request->GetUserData(this)); |
| + |
| if (request->status().is_success() && (response_code / 100 == 2)) { |
| - // TODO(jennb): associate storage with the new entry |
| + // Associate storage with the new entry. |
| + DCHECK(info->response_writer_.get()); |
| + entry.set_response_id(info->response_writer_->response_id()); |
| + |
| inprogress_cache_->AddEntry(url, entry); |
| // Foreign entries will be detected during cache selection. |
| @@ -396,7 +468,9 @@ |
| << " os_error: " << request->status().os_error() |
| << " response code: " << response_code; |
| - // TODO(jennb): discard any stored data for this entry |
| + // TODO(jennb): Discard any stored data for this entry? May be unnecessary |
| + // if handled automatically by storage layer. |
| + |
| if (entry.IsExplicit() || entry.IsFallback()) { |
| internal_state_ = CACHE_FAILURE; |
| @@ -434,35 +508,88 @@ |
| int response_code = request->GetResponseCode(); |
| if (response_code == 304 || manifest_data_ == manifest_refetch_data_) { |
| - AppCacheEntry entry(AppCacheEntry::MANIFEST); |
| - // TODO(jennb): add manifest_data_ to storage and put storage key in entry |
| - // Also store response headers from request for HTTP cache control. |
| + // Only need to store response in storage if manifest is not already an |
| + // an entry in the cache. |
| + AppCacheEntry* entry = inprogress_cache_->GetEntry(manifest_url_); |
| + if (entry) { |
| + entry->add_types(AppCacheEntry::MANIFEST); |
| + CompleteInprogressCache(); |
| + } else { |
| + manifest_response_writer_.reset( |
| + service_->storage()->CreateResponseWriter(manifest_url_)); |
| + scoped_refptr<HttpResponseInfoIOBuffer> io_buffer = |
| + new HttpResponseInfoIOBuffer(manifest_response_info_.release()); |
| + manifest_response_writer_->WriteInfo(io_buffer, |
| + &manifest_info_write_callback_); |
| + } |
| + } else { |
| + LOG(INFO) << "Request status: " << request->status().status() |
| + << " os_error: " << request->status().os_error() |
| + << " response code: " << response_code; |
| + HandleManifestRefetchFailure(); |
| + } |
| +} |
| + |
| +void AppCacheUpdateJob::OnManifestInfoWriteComplete(int result) { |
| + if (result > 0) { |
| + scoped_refptr<net::StringIOBuffer> io_buffer = |
| + new net::StringIOBuffer(manifest_data_); |
| + manifest_response_writer_->WriteData(io_buffer, manifest_data_.length(), |
| + &manifest_data_write_callback_); |
| + } else { |
| + // Treat storage failure as if refetch of manifest failed. |
| + HandleManifestRefetchFailure(); |
| + } |
| +} |
| + |
| +void AppCacheUpdateJob::OnManifestDataWriteComplete(int result) { |
| + if (result > 0) { |
| + AppCacheEntry entry(AppCacheEntry::MANIFEST, |
| + manifest_response_writer_->response_id()); |
| inprogress_cache_->AddOrModifyEntry(manifest_url_, entry); |
| - inprogress_cache_->set_update_time(base::TimeTicks::Now()); |
| + CompleteInprogressCache(); |
| + } else { |
| + // Treat storage failure as if refetch of manifest failed. |
| + HandleManifestRefetchFailure(); |
| + } |
| +} |
| - // TODO(jennb): start of part to make async (cache/group storage may fail) |
| - inprogress_cache_->set_complete(true); |
| - group_->AddCache(inprogress_cache_); |
| - protect_new_cache_.swap(inprogress_cache_); |
| +void AppCacheUpdateJob::CompleteInprogressCache() { |
| + inprogress_cache_->set_update_time(base::TimeTicks::Now()); |
| + inprogress_cache_->set_complete(true); |
| - // TODO(jennb): write new group and cache to storage here |
| + protect_former_newest_cache_ = group_->newest_complete_cache(); |
| + group_->AddCache(inprogress_cache_); |
| + protect_new_cache_.swap(inprogress_cache_); |
| - if (update_type_ == CACHE_ATTEMPT) { |
| + service_->storage()->StoreGroupAndNewestCache(group_, this); // async |
| +} |
| + |
| +void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup* group, |
| + bool success) { |
| + if (success) { |
| + if (update_type_ == CACHE_ATTEMPT) |
| NotifyAllAssociatedHosts(CACHED_EVENT); |
| - } else { |
| + else |
| NotifyAllAssociatedHosts(UPDATE_READY_EVENT); |
| - } |
| internal_state_ = COMPLETED; |
| - // TODO(jennb): end of part that needs to be made async. |
| + MaybeCompleteUpdate(); // will definitely complete |
| } else { |
| - LOG(INFO) << "Request status: " << request->status().status() |
| - << " os_error: " << request->status().os_error() |
| - << " response code: " << response_code; |
| + // TODO(jennb): Change storage so clients won't need to revert group state? |
| + // Change group back to reflect former newest group. |
| + group_->RestoreCacheAsNewest(protect_former_newest_cache_); |
| + protect_new_cache_ = NULL; |
| + |
| + // Treat storage failure as if manifest refetch failed. |
| + HandleManifestRefetchFailure(); |
| + } |
| + protect_former_newest_cache_ = NULL; |
| +} |
| + |
| +void AppCacheUpdateJob::HandleManifestRefetchFailure() { |
| ScheduleUpdateRetry(kRerunDelayMs); |
| internal_state_ = CACHE_FAILURE; |
| - } |
| - |
| - MaybeCompleteUpdate(); // will definitely complete |
| + MaybeCompleteUpdate(); // will definitely complete |
| } |
| void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host, |
| @@ -654,7 +781,7 @@ |
| const AppCacheEntry& src, |
| AppCacheEntry* dest) { |
| DCHECK(dest); |
| - // TODO(jennb): copy storage key from src to dest |
| + dest->set_response_id(src.response_id()); |
| inprogress_cache_->AddEntry(url, *dest); |
| } |
| @@ -718,18 +845,28 @@ |
| pending_master_entries_.clear(); |
| DiscardInprogressCache(); |
| - // TODO(jennb): cancel any storage callbacks |
| + |
| + // Delete response writer to avoid any callbacks. |
| + if (manifest_response_writer_.get()) |
| + manifest_response_writer_.reset(); |
| + |
| + service_->storage()->CancelDelegateCallbacks(this); |
| } |
| void AppCacheUpdateJob::DiscardInprogressCache() { |
| if (!inprogress_cache_) |
| return; |
| - // TODO(jennb): cleanup stored responses for entries in the cache |
| + // TODO(jennb): Cleanup stored responses for entries in the cache? |
| + // May not be necessary if handled automatically by storage layer. |
| + |
| inprogress_cache_ = NULL; |
| } |
| void AppCacheUpdateJob::DeleteSoon() { |
| + manifest_response_writer_.reset(); |
| + service_->storage()->CancelDelegateCallbacks(this); |
| + |
| // Break the connection with the group so the group cannot call delete |
| // on this object after we've posted a task to delete ourselves. |
| group_->SetUpdateStatus(AppCacheGroup::IDLE); |