This source file includes following definitions.
- FormatUrlErrorMessage
- AddHost
- AddHosts
- SendNotifications
- SendProgressNotifications
- SendErrorNotifications
- existing_response_info
- redirect_response_code_
- Start
- OnReceivedRedirect
- OnResponseStarted
- OnReadCompleted
- AddConditionalHeaders
- OnWriteComplete
- ReadResponseData
- ConsumeResponseData
- OnResponseCompleted
- MaybeRetryRequest
- storage_
- StartUpdate
- CreateResponseWriter
- HandleCacheFailure
- FetchManifest
- HandleManifestFetchCompleted
- OnGroupMadeObsolete
- ContinueHandleManifestFetchCompleted
- HandleUrlFetchCompleted
- HandleMasterEntryFetchCompleted
- HandleManifestRefetchCompleted
- OnManifestInfoWriteComplete
- OnManifestDataWriteComplete
- StoreGroupAndCache
- OnGroupAndNewestCacheStored
- NotifySingleHost
- NotifyAllAssociatedHosts
- NotifyAllProgress
- NotifyAllFinalProgress
- NotifyAllError
- AddAllAssociatedHostsToNotifier
- OnDestructionImminent
- OnServiceReinitialized
- CheckIfManifestChanged
- OnManifestDataReadComplete
- BuildUrlFileList
- AddUrlToFileList
- FetchUrls
- CancelAllUrlFetches
- ShouldSkipUrlFetch
- AlreadyFetchedEntry
- AddMasterEntryToFetchList
- FetchMasterEntries
- CancelAllMasterEntryFetches
- MaybeLoadFromNewestCache
- OnResponseInfoLoaded
- LoadFromNewestCacheFailed
- MaybeCompleteUpdate
- ScheduleUpdateRetry
- Cancel
- ClearPendingMasterEntries
- DiscardInprogressCache
- DiscardDuplicateResponses
- LogHistogramStats
- DeleteSoon
#include "webkit/browser/appcache/appcache_update_job.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/compiler_specific.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/request_priority.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request_context.h"
#include "webkit/browser/appcache/appcache_group.h"
#include "webkit/browser/appcache/appcache_histograms.h"
namespace appcache {
static const int kBufferSize = 32768;
static const size_t kMaxConcurrentUrlFetches = 2;
static const int kMax503Retries = 3;
static std::string FormatUrlErrorMessage(
const char* format, const GURL& url,
AppCacheUpdateJob::ResultType error,
int response_code) {
int code = response_code;
if (error != AppCacheUpdateJob::SERVER_ERROR)
code = static_cast<int>(error);
return base::StringPrintf(format, code, url.spec().c_str());
}
class HostNotifier {
public:
typedef std::vector<int> HostIds;
typedef std::map<AppCacheFrontend*, HostIds> NotifyHostMap;
void AddHost(AppCacheHost* host) {
std::pair<NotifyHostMap::iterator , bool> ret = hosts_to_notify.insert(
NotifyHostMap::value_type(host->frontend(), HostIds()));
ret.first->second.push_back(host->host_id());
}
void AddHosts(const std::set<AppCacheHost*>& hosts) {
for (std::set<AppCacheHost*>::const_iterator it = hosts.begin();
it != hosts.end(); ++it) {
AddHost(*it);
}
}
void SendNotifications(EventID event_id) {
for (NotifyHostMap::iterator it = hosts_to_notify.begin();
it != hosts_to_notify.end(); ++it) {
AppCacheFrontend* frontend = it->first;
frontend->OnEventRaised(it->second, event_id);
}
}
void SendProgressNotifications(
const GURL& url, int num_total, int num_complete) {
for (NotifyHostMap::iterator it = hosts_to_notify.begin();
it != hosts_to_notify.end(); ++it) {
AppCacheFrontend* frontend = it->first;
frontend->OnProgressEventRaised(it->second, url,
num_total, num_complete);
}
}
void SendErrorNotifications(const ErrorDetails& details) {
DCHECK(!details.message.empty());
for (NotifyHostMap::iterator it = hosts_to_notify.begin();
it != hosts_to_notify.end(); ++it) {
AppCacheFrontend* frontend = it->first;
frontend->OnErrorEventRaised(it->second, details);
}
}
private:
NotifyHostMap hosts_to_notify;
};
AppCacheUpdateJob::UrlToFetch::UrlToFetch(const GURL& url,
bool checked,
AppCacheResponseInfo* info)
: url(url),
storage_checked(checked),
existing_response_info(info) {
}
AppCacheUpdateJob::UrlToFetch::~UrlToFetch() {
}
AppCacheUpdateJob::URLFetcher::URLFetcher(const GURL& url,
FetchType fetch_type,
AppCacheUpdateJob* job)
: url_(url),
job_(job),
fetch_type_(fetch_type),
retry_503_attempts_(0),
buffer_(new net::IOBuffer(kBufferSize)),
request_(job->service_->request_context()
->CreateRequest(url, net::DEFAULT_PRIORITY, this, NULL)),
result_(UPDATE_OK),
redirect_response_code_(-1) {}
AppCacheUpdateJob::URLFetcher::~URLFetcher() {
}
void AppCacheUpdateJob::URLFetcher::Start() {
request_->set_first_party_for_cookies(job_->manifest_url_);
request_->SetLoadFlags(request_->load_flags() |
net::LOAD_DISABLE_INTERCEPT);
if (existing_response_headers_.get())
AddConditionalHeaders(existing_response_headers_.get());
request_->Start();
}
void AppCacheUpdateJob::URLFetcher::OnReceivedRedirect(
net::URLRequest* request, const GURL& new_url, bool* defer_redirect) {
DCHECK(request_ == request);
job_->MadeProgress();
redirect_response_code_ = request->GetResponseCode();
request->Cancel();
result_ = REDIRECT_ERROR;
OnResponseCompleted();
}
void AppCacheUpdateJob::URLFetcher::OnResponseStarted(
net::URLRequest *request) {
DCHECK(request == request_);
int response_code = -1;
if (request->status().is_success()) {
response_code = request->GetResponseCode();
job_->MadeProgress();
}
if ((response_code / 100) == 2) {
if (url_.SchemeIsSecure() &&
url_.GetOrigin() != job_->manifest_url_.GetOrigin()) {
if (request->response_headers()->
HasHeaderValue("cache-control", "no-store")) {
DCHECK_EQ(-1, redirect_response_code_);
request->Cancel();
result_ = SERVER_ERROR;
OnResponseCompleted();
return;
}
}
if (fetch_type_ == URL_FETCH || fetch_type_ == MASTER_ENTRY_FETCH) {
response_writer_.reset(job_->CreateResponseWriter());
scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
new HttpResponseInfoIOBuffer(
new net::HttpResponseInfo(request->response_info())));
response_writer_->WriteInfo(
io_buffer.get(),
base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
} else {
ReadResponseData();
}
} else {
if (response_code > 0)
result_ = SERVER_ERROR;
else
result_ = NETWORK_ERROR;
OnResponseCompleted();
}
}
void AppCacheUpdateJob::URLFetcher::OnReadCompleted(
net::URLRequest* request, int bytes_read) {
DCHECK(request_ == request);
bool data_consumed = true;
if (request->status().is_success() && bytes_read > 0) {
job_->MadeProgress();
data_consumed = ConsumeResponseData(bytes_read);
if (data_consumed) {
bytes_read = 0;
while (request->Read(buffer_.get(), kBufferSize, &bytes_read)) {
if (bytes_read > 0) {
data_consumed = ConsumeResponseData(bytes_read);
if (!data_consumed)
break;
} else {
break;
}
}
}
}
if (data_consumed && !request->status().is_io_pending()) {
DCHECK_EQ(UPDATE_OK, result_);
OnResponseCompleted();
}
}
void AppCacheUpdateJob::URLFetcher::AddConditionalHeaders(
const net::HttpResponseHeaders* headers) {
DCHECK(request_.get() && headers);
net::HttpRequestHeaders extra_headers;
const std::string last_modified = "Last-Modified";
std::string last_modified_value;
headers->EnumerateHeader(NULL, last_modified, &last_modified_value);
if (!last_modified_value.empty()) {
extra_headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince,
last_modified_value);
}
const std::string etag = "ETag";
std::string etag_value;
headers->EnumerateHeader(NULL, etag, &etag_value);
if (!etag_value.empty()) {
extra_headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch,
etag_value);
}
if (!extra_headers.IsEmpty())
request_->SetExtraRequestHeaders(extra_headers);
}
void AppCacheUpdateJob::URLFetcher::OnWriteComplete(int result) {
if (result < 0) {
request_->Cancel();
result_ = DISKCACHE_ERROR;
OnResponseCompleted();
return;
}
ReadResponseData();
}
void AppCacheUpdateJob::URLFetcher::ReadResponseData() {
InternalUpdateState state = job_->internal_state_;
if (state == CACHE_FAILURE || state == CANCELLED || state == COMPLETED)
return;
int bytes_read = 0;
request_->Read(buffer_.get(), kBufferSize, &bytes_read);
OnReadCompleted(request_.get(), bytes_read);
}
bool AppCacheUpdateJob::URLFetcher::ConsumeResponseData(int bytes_read) {
DCHECK_GT(bytes_read, 0);
switch (fetch_type_) {
case MANIFEST_FETCH:
case MANIFEST_REFETCH:
manifest_data_.append(buffer_->data(), bytes_read);
break;
case URL_FETCH:
case MASTER_ENTRY_FETCH:
DCHECK(response_writer_.get());
response_writer_->WriteData(
buffer_.get(),
bytes_read,
base::Bind(&URLFetcher::OnWriteComplete, base::Unretained(this)));
return false;
default:
NOTREACHED();
}
return true;
}
void AppCacheUpdateJob::URLFetcher::OnResponseCompleted() {
if (request_->status().is_success())
job_->MadeProgress();
if (request_->status().is_success() &&
request_->GetResponseCode() == 503 &&
MaybeRetryRequest()) {
return;
}
switch (fetch_type_) {
case MANIFEST_FETCH:
job_->HandleManifestFetchCompleted(this);
break;
case URL_FETCH:
job_->HandleUrlFetchCompleted(this);
break;
case MASTER_ENTRY_FETCH:
job_->HandleMasterEntryFetchCompleted(this);
break;
case MANIFEST_REFETCH:
job_->HandleManifestRefetchCompleted(this);
break;
default:
NOTREACHED();
}
delete this;
}
bool AppCacheUpdateJob::URLFetcher::MaybeRetryRequest() {
if (retry_503_attempts_ >= kMax503Retries ||
!request_->response_headers()->HasHeaderValue("retry-after", "0")) {
return false;
}
++retry_503_attempts_;
result_ = UPDATE_OK;
request_ = job_->service_->request_context()->CreateRequest(
url_, net::DEFAULT_PRIORITY, this, NULL);
Start();
return true;
}
AppCacheUpdateJob::AppCacheUpdateJob(AppCacheService* service,
AppCacheGroup* group)
: service_(service),
manifest_url_(group->manifest_url()),
group_(group),
update_type_(UNKNOWN_TYPE),
internal_state_(FETCH_MANIFEST),
master_entries_completed_(0),
url_fetches_completed_(0),
manifest_fetcher_(NULL),
stored_state_(UNSTORED),
storage_(service->storage()) {
service_->AddObserver(this);
}
AppCacheUpdateJob::~AppCacheUpdateJob() {
if (service_)
service_->RemoveObserver(this);
if (internal_state_ != COMPLETED)
Cancel();
DCHECK(!manifest_fetcher_);
DCHECK(pending_url_fetches_.empty());
DCHECK(!inprogress_cache_.get());
DCHECK(pending_master_entries_.empty());
DCHECK(master_entry_fetches_.empty());
if (group_)
group_->SetUpdateStatus(AppCacheGroup::IDLE);
}
void AppCacheUpdateJob::StartUpdate(AppCacheHost* host,
const GURL& new_master_resource) {
DCHECK(group_->update_job() == this);
DCHECK(!group_->is_obsolete());
bool is_new_pending_master_entry = false;
if (!new_master_resource.is_empty()) {
DCHECK(new_master_resource == host->pending_master_entry_url());
DCHECK(!new_master_resource.has_ref());
DCHECK(new_master_resource.GetOrigin() == manifest_url_.GetOrigin());
if (IsTerminating()) {
group_->QueueUpdate(host, new_master_resource);
return;
}
std::pair<PendingMasters::iterator, bool> ret =
pending_master_entries_.insert(
PendingMasters::value_type(new_master_resource, PendingHosts()));
is_new_pending_master_entry = ret.second;
ret.first->second.push_back(host);
host->AddObserver(this);
}
AppCacheGroup::UpdateStatus update_status = group_->update_status();
if (update_status == AppCacheGroup::CHECKING ||
update_status == AppCacheGroup::DOWNLOADING) {
if (host) {
NotifySingleHost(host, CHECKING_EVENT);
if (update_status == AppCacheGroup::DOWNLOADING)
NotifySingleHost(host, DOWNLOADING_EVENT);
if (!new_master_resource.is_empty()) {
AddMasterEntryToFetchList(host, new_master_resource,
is_new_pending_master_entry);
}
}
return;
}
MadeProgress();
group_->SetUpdateStatus(AppCacheGroup::CHECKING);
if (group_->HasCache()) {
update_type_ = UPGRADE_ATTEMPT;
NotifyAllAssociatedHosts(CHECKING_EVENT);
} else {
update_type_ = CACHE_ATTEMPT;
DCHECK(host);
NotifySingleHost(host, CHECKING_EVENT);
}
if (!new_master_resource.is_empty()) {
AddMasterEntryToFetchList(host, new_master_resource,
is_new_pending_master_entry);
}
FetchManifest(true);
}
AppCacheResponseWriter* AppCacheUpdateJob::CreateResponseWriter() {
AppCacheResponseWriter* writer =
storage_->CreateResponseWriter(manifest_url_,
group_->group_id());
stored_response_ids_.push_back(writer->response_id());
return writer;
}
void AppCacheUpdateJob::HandleCacheFailure(const ErrorDetails& error_details,
ResultType result,
const GURL& failed_resource_url) {
DCHECK(internal_state_ != CACHE_FAILURE);
DCHECK(!error_details.message.empty());
DCHECK(result != UPDATE_OK);
internal_state_ = CACHE_FAILURE;
LogHistogramStats(result, failed_resource_url);
CancelAllUrlFetches();
CancelAllMasterEntryFetches(error_details);
NotifyAllError(error_details);
DiscardInprogressCache();
internal_state_ = COMPLETED;
DeleteSoon();
}
void AppCacheUpdateJob::FetchManifest(bool is_first_fetch) {
DCHECK(!manifest_fetcher_);
manifest_fetcher_ = new URLFetcher(
manifest_url_,
is_first_fetch ? URLFetcher::MANIFEST_FETCH :
URLFetcher::MANIFEST_REFETCH,
this);
if (is_first_fetch) {
AppCacheEntry* entry = (update_type_ == UPGRADE_ATTEMPT) ?
group_->newest_complete_cache()->GetEntry(manifest_url_) : NULL;
if (entry) {
storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
entry->response_id(), this);
} else {
manifest_fetcher_->Start();
}
} else {
DCHECK(internal_state_ == REFETCH_MANIFEST);
DCHECK(manifest_response_info_.get());
manifest_fetcher_->set_existing_response_headers(
manifest_response_info_->headers.get());
manifest_fetcher_->Start();
}
}
void AppCacheUpdateJob::HandleManifestFetchCompleted(
URLFetcher* fetcher) {
DCHECK_EQ(internal_state_, FETCH_MANIFEST);
DCHECK_EQ(manifest_fetcher_, fetcher);
manifest_fetcher_ = NULL;
net::URLRequest* request = fetcher->request();
int response_code = -1;
bool is_valid_response_code = false;
if (request->status().is_success()) {
response_code = request->GetResponseCode();
is_valid_response_code = (response_code / 100 == 2);
}
if (is_valid_response_code) {
manifest_data_ = fetcher->manifest_data();
manifest_response_info_.reset(
new net::HttpResponseInfo(request->response_info()));
if (update_type_ == UPGRADE_ATTEMPT)
CheckIfManifestChanged();
else
ContinueHandleManifestFetchCompleted(true);
} else if (response_code == 304 && update_type_ == UPGRADE_ATTEMPT) {
ContinueHandleManifestFetchCompleted(false);
} else if ((response_code == 404 || response_code == 410) &&
update_type_ == UPGRADE_ATTEMPT) {
storage_->MakeGroupObsolete(group_, this, response_code);
} else {
const char* kFormatString = "Manifest fetch failed (%d) %s";
std::string message = FormatUrlErrorMessage(
kFormatString, manifest_url_, fetcher->result(), response_code);
HandleCacheFailure(ErrorDetails(message,
appcache::MANIFEST_ERROR,
manifest_url_,
response_code,
false ),
fetcher->result(),
GURL());
}
}
void AppCacheUpdateJob::OnGroupMadeObsolete(AppCacheGroup* group,
bool success,
int response_code) {
DCHECK(master_entry_fetches_.empty());
CancelAllMasterEntryFetches(ErrorDetails(
"The cache has been made obsolete, "
"the manifest file returned 404 or 410",
appcache::MANIFEST_ERROR,
GURL(),
response_code,
false ));
if (success) {
DCHECK(group->is_obsolete());
NotifyAllAssociatedHosts(OBSOLETE_EVENT);
internal_state_ = COMPLETED;
MaybeCompleteUpdate();
} else {
HandleCacheFailure(ErrorDetails("Failed to mark the cache as obsolete",
UNKNOWN_ERROR,
GURL(),
0,
false ),
DB_ERROR,
GURL());
}
}
void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) {
DCHECK(internal_state_ == FETCH_MANIFEST);
if (!changed) {
DCHECK(update_type_ == UPGRADE_ATTEMPT);
internal_state_ = NO_UPDATE;
FetchMasterEntries();
MaybeCompleteUpdate();
return;
}
Manifest manifest;
if (!ParseManifest(manifest_url_, manifest_data_.data(),
manifest_data_.length(), manifest)) {
const char* kFormatString = "Failed to parse manifest %s";
const std::string message = base::StringPrintf(kFormatString,
manifest_url_.spec().c_str());
HandleCacheFailure(
ErrorDetails(
message, SIGNATURE_ERROR, GURL(), 0, false ),
MANIFEST_ERROR,
GURL());
VLOG(1) << message;
return;
}
internal_state_ = DOWNLOADING;
inprogress_cache_ = new AppCache(storage_, storage_->NewCacheId());
BuildUrlFileList(manifest);
inprogress_cache_->InitializeWithManifest(&manifest);
for (PendingMasters::iterator it = pending_master_entries_.begin();
it != pending_master_entries_.end(); ++it) {
PendingHosts& hosts = it->second;
for (PendingHosts::iterator host_it = hosts.begin();
host_it != hosts.end(); ++host_it) {
(*host_it)
->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
}
}
group_->SetUpdateStatus(AppCacheGroup::DOWNLOADING);
NotifyAllAssociatedHosts(DOWNLOADING_EVENT);
FetchUrls();
FetchMasterEntries();
MaybeCompleteUpdate();
}
void AppCacheUpdateJob::HandleUrlFetchCompleted(URLFetcher* fetcher) {
DCHECK(internal_state_ == DOWNLOADING);
net::URLRequest* request = fetcher->request();
const GURL& url = request->original_url();
pending_url_fetches_.erase(url);
NotifyAllProgress(url);
++url_fetches_completed_;
int response_code = request->status().is_success()
? request->GetResponseCode()
: fetcher->redirect_response_code();
AppCacheEntry& entry = url_file_list_.find(url)->second;
if (response_code / 100 == 2) {
DCHECK(fetcher->response_writer());
entry.set_response_id(fetcher->response_writer()->response_id());
entry.set_response_size(fetcher->response_writer()->amount_written());
if (!inprogress_cache_->AddOrModifyEntry(url, entry))
duplicate_response_ids_.push_back(entry.response_id());
} else {
VLOG(1) << "Request status: " << request->status().status()
<< " error: " << request->status().error()
<< " response code: " << response_code;
if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept()) {
if (response_code == 304 && fetcher->existing_entry().has_response_id()) {
entry.set_response_id(fetcher->existing_entry().response_id());
entry.set_response_size(fetcher->existing_entry().response_size());
inprogress_cache_->AddOrModifyEntry(url, entry);
} else {
const char* kFormatString = "Resource fetch failed (%d) %s";
std::string message = FormatUrlErrorMessage(
kFormatString, url, fetcher->result(), response_code);
ResultType result = fetcher->result();
bool is_cross_origin = url.GetOrigin() != manifest_url_.GetOrigin();
switch (result) {
case DISKCACHE_ERROR:
HandleCacheFailure(
ErrorDetails(
message, UNKNOWN_ERROR, GURL(), 0, is_cross_origin),
result,
url);
break;
case NETWORK_ERROR:
HandleCacheFailure(
ErrorDetails(message, RESOURCE_ERROR, url, 0, is_cross_origin),
result,
url);
break;
default:
HandleCacheFailure(ErrorDetails(message,
RESOURCE_ERROR,
url,
response_code,
is_cross_origin),
result,
url);
break;
}
return;
}
} else if (response_code == 404 || response_code == 410) {
} else if (update_type_ == UPGRADE_ATTEMPT &&
fetcher->existing_entry().has_response_id()) {
entry.set_response_id(fetcher->existing_entry().response_id());
entry.set_response_size(fetcher->existing_entry().response_size());
inprogress_cache_->AddOrModifyEntry(url, entry);
}
}
DCHECK(internal_state_ != CACHE_FAILURE);
FetchUrls();
MaybeCompleteUpdate();
}
void AppCacheUpdateJob::HandleMasterEntryFetchCompleted(
URLFetcher* fetcher) {
DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
net::URLRequest* request = fetcher->request();
const GURL& url = request->original_url();
master_entry_fetches_.erase(url);
++master_entries_completed_;
int response_code = request->status().is_success()
? request->GetResponseCode() : -1;
PendingMasters::iterator found = pending_master_entries_.find(url);
DCHECK(found != pending_master_entries_.end());
PendingHosts& hosts = found->second;
if (response_code / 100 == 2) {
AppCache* cache = inprogress_cache_.get() ? inprogress_cache_.get()
: group_->newest_complete_cache();
DCHECK(fetcher->response_writer());
AppCacheEntry master_entry(AppCacheEntry::MASTER,
fetcher->response_writer()->response_id(),
fetcher->response_writer()->amount_written());
if (cache->AddOrModifyEntry(url, master_entry))
added_master_entries_.push_back(url);
else
duplicate_response_ids_.push_back(master_entry.response_id());
if (!inprogress_cache_.get()) {
DCHECK(cache == group_->newest_complete_cache());
for (PendingHosts::iterator host_it = hosts.begin();
host_it != hosts.end(); ++host_it) {
(*host_it)->AssociateCompleteCache(cache);
}
}
} else {
HostNotifier host_notifier;
for (PendingHosts::iterator host_it = hosts.begin();
host_it != hosts.end(); ++host_it) {
AppCacheHost* host = *host_it;
host_notifier.AddHost(host);
if (inprogress_cache_.get())
host->AssociateNoCache(GURL());
host->RemoveObserver(this);
}
hosts.clear();
const char* kFormatString = "Manifest fetch failed (%d) %s";
std::string message = FormatUrlErrorMessage(
kFormatString, request->url(), fetcher->result(), response_code);
host_notifier.SendErrorNotifications(
ErrorDetails(message,
appcache::MANIFEST_ERROR,
request->url(),
response_code,
false ));
if (inprogress_cache_.get()) {
pending_master_entries_.erase(found);
--master_entries_completed_;
if (update_type_ == CACHE_ATTEMPT && pending_master_entries_.empty()) {
HandleCacheFailure(ErrorDetails(message,
appcache::MANIFEST_ERROR,
request->url(),
response_code,
false ),
fetcher->result(),
GURL());
return;
}
}
}
DCHECK(internal_state_ != CACHE_FAILURE);
FetchMasterEntries();
MaybeCompleteUpdate();
}
void AppCacheUpdateJob::HandleManifestRefetchCompleted(
URLFetcher* fetcher) {
DCHECK(internal_state_ == REFETCH_MANIFEST);
DCHECK(manifest_fetcher_ == fetcher);
manifest_fetcher_ = NULL;
net::URLRequest* request = fetcher->request();
int response_code = request->status().is_success()
? request->GetResponseCode() : -1;
if (response_code == 304 || manifest_data_ == fetcher->manifest_data()) {
AppCacheEntry* entry = inprogress_cache_->GetEntry(manifest_url_);
if (entry) {
entry->add_types(AppCacheEntry::MANIFEST);
StoreGroupAndCache();
} else {
manifest_response_writer_.reset(CreateResponseWriter());
scoped_refptr<HttpResponseInfoIOBuffer> io_buffer(
new HttpResponseInfoIOBuffer(manifest_response_info_.release()));
manifest_response_writer_->WriteInfo(
io_buffer.get(),
base::Bind(&AppCacheUpdateJob::OnManifestInfoWriteComplete,
base::Unretained(this)));
}
} else {
VLOG(1) << "Request status: " << request->status().status()
<< " error: " << request->status().error()
<< " response code: " << response_code;
ScheduleUpdateRetry(kRerunDelayMs);
if (response_code == 200) {
HandleCacheFailure(ErrorDetails("Manifest changed during update",
CHANGED_ERROR,
GURL(),
0,
false ),
MANIFEST_ERROR,
GURL());
} else {
const char* kFormatString = "Manifest re-fetch failed (%d) %s";
std::string message = FormatUrlErrorMessage(
kFormatString, manifest_url_, fetcher->result(), response_code);
HandleCacheFailure(ErrorDetails(message,
appcache::MANIFEST_ERROR,
GURL(),
response_code,
false ),
fetcher->result(),
GURL());
}
}
}
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.get(),
manifest_data_.length(),
base::Bind(&AppCacheUpdateJob::OnManifestDataWriteComplete,
base::Unretained(this)));
} else {
HandleCacheFailure(
ErrorDetails("Failed to write the manifest headers to storage",
UNKNOWN_ERROR,
GURL(),
0,
false ),
DISKCACHE_ERROR,
GURL());
}
}
void AppCacheUpdateJob::OnManifestDataWriteComplete(int result) {
if (result > 0) {
AppCacheEntry entry(AppCacheEntry::MANIFEST,
manifest_response_writer_->response_id(),
manifest_response_writer_->amount_written());
if (!inprogress_cache_->AddOrModifyEntry(manifest_url_, entry))
duplicate_response_ids_.push_back(entry.response_id());
StoreGroupAndCache();
} else {
HandleCacheFailure(
ErrorDetails("Failed to write the manifest data to storage",
UNKNOWN_ERROR,
GURL(),
0,
false ),
DISKCACHE_ERROR,
GURL());
}
}
void AppCacheUpdateJob::StoreGroupAndCache() {
DCHECK(stored_state_ == UNSTORED);
stored_state_ = STORING;
scoped_refptr<AppCache> newest_cache;
if (inprogress_cache_.get())
newest_cache.swap(inprogress_cache_);
else
newest_cache = group_->newest_complete_cache();
newest_cache->set_update_time(base::Time::Now());
DCHECK_EQ(manifest_url_, group_->manifest_url());
storage_->StoreGroupAndNewestCache(group_, newest_cache.get(), this);
}
void AppCacheUpdateJob::OnGroupAndNewestCacheStored(AppCacheGroup* group,
AppCache* newest_cache,
bool success,
bool would_exceed_quota) {
DCHECK(stored_state_ == STORING);
if (success) {
stored_state_ = STORED;
MaybeCompleteUpdate();
} else {
stored_state_ = UNSTORED;
if (newest_cache != group->newest_complete_cache())
inprogress_cache_ = newest_cache;
ResultType result = DB_ERROR;
ErrorReason reason = UNKNOWN_ERROR;
std::string message("Failed to commit new cache to storage");
if (would_exceed_quota) {
message.append(", would exceed quota");
result = QUOTA_ERROR;
reason = appcache::QUOTA_ERROR;
}
HandleCacheFailure(
ErrorDetails(message, reason, GURL(), 0, false ),
result,
GURL());
}
}
void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host,
EventID event_id) {
std::vector<int> ids(1, host->host_id());
host->frontend()->OnEventRaised(ids, event_id);
}
void AppCacheUpdateJob::NotifyAllAssociatedHosts(EventID event_id) {
HostNotifier host_notifier;
AddAllAssociatedHostsToNotifier(&host_notifier);
host_notifier.SendNotifications(event_id);
}
void AppCacheUpdateJob::NotifyAllProgress(const GURL& url) {
HostNotifier host_notifier;
AddAllAssociatedHostsToNotifier(&host_notifier);
host_notifier.SendProgressNotifications(
url, url_file_list_.size(), url_fetches_completed_);
}
void AppCacheUpdateJob::NotifyAllFinalProgress() {
DCHECK(url_file_list_.size() == url_fetches_completed_);
NotifyAllProgress(GURL());
}
void AppCacheUpdateJob::NotifyAllError(const ErrorDetails& details) {
HostNotifier host_notifier;
AddAllAssociatedHostsToNotifier(&host_notifier);
host_notifier.SendErrorNotifications(details);
}
void AppCacheUpdateJob::AddAllAssociatedHostsToNotifier(
HostNotifier* host_notifier) {
if (inprogress_cache_.get()) {
DCHECK(internal_state_ == DOWNLOADING || internal_state_ == CACHE_FAILURE);
host_notifier->AddHosts(inprogress_cache_->associated_hosts());
}
AppCacheGroup::Caches old_caches = group_->old_caches();
for (AppCacheGroup::Caches::const_iterator it = old_caches.begin();
it != old_caches.end(); ++it) {
host_notifier->AddHosts((*it)->associated_hosts());
}
AppCache* newest_cache = group_->newest_complete_cache();
if (newest_cache)
host_notifier->AddHosts(newest_cache->associated_hosts());
}
void AppCacheUpdateJob::OnDestructionImminent(AppCacheHost* host) {
PendingMasters::iterator found =
pending_master_entries_.find(host->pending_master_entry_url());
DCHECK(found != pending_master_entries_.end());
PendingHosts& hosts = found->second;
PendingHosts::iterator it = std::find(hosts.begin(), hosts.end(), host);
DCHECK(it != hosts.end());
hosts.erase(it);
}
void AppCacheUpdateJob::OnServiceReinitialized(
AppCacheStorageReference* old_storage_ref) {
if (old_storage_ref->storage() == storage_)
disabled_storage_reference_ = old_storage_ref;
}
void AppCacheUpdateJob::CheckIfManifestChanged() {
DCHECK(update_type_ == UPGRADE_ATTEMPT);
AppCacheEntry* entry = NULL;
if (group_->newest_complete_cache())
entry = group_->newest_complete_cache()->GetEntry(manifest_url_);
if (!entry) {
if (service_->storage() == storage_) {
AppCacheService* service = service_;
HandleCacheFailure(
ErrorDetails("Manifest entry not found in existing cache",
UNKNOWN_ERROR,
GURL(),
0,
false ),
DB_ERROR,
GURL());
AppCacheHistograms::AddMissingManifestEntrySample();
service->DeleteAppCacheGroup(manifest_url_, net::CompletionCallback());
}
return;
}
manifest_response_reader_.reset(
storage_->CreateResponseReader(manifest_url_,
group_->group_id(),
entry->response_id()));
read_manifest_buffer_ = new net::IOBuffer(kBufferSize);
manifest_response_reader_->ReadData(
read_manifest_buffer_.get(),
kBufferSize,
base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
base::Unretained(this)));
}
void AppCacheUpdateJob::OnManifestDataReadComplete(int result) {
if (result > 0) {
loaded_manifest_data_.append(read_manifest_buffer_->data(), result);
manifest_response_reader_->ReadData(
read_manifest_buffer_.get(),
kBufferSize,
base::Bind(&AppCacheUpdateJob::OnManifestDataReadComplete,
base::Unretained(this)));
} else {
read_manifest_buffer_ = NULL;
manifest_response_reader_.reset();
ContinueHandleManifestFetchCompleted(
result < 0 || manifest_data_ != loaded_manifest_data_);
}
}
void AppCacheUpdateJob::BuildUrlFileList(const Manifest& manifest) {
for (base::hash_set<std::string>::const_iterator it =
manifest.explicit_urls.begin();
it != manifest.explicit_urls.end(); ++it) {
AddUrlToFileList(GURL(*it), AppCacheEntry::EXPLICIT);
}
const std::vector<Namespace>& intercepts =
manifest.intercept_namespaces;
for (std::vector<Namespace>::const_iterator it = intercepts.begin();
it != intercepts.end(); ++it) {
int flags = AppCacheEntry::INTERCEPT;
if (it->is_executable)
flags |= AppCacheEntry::EXECUTABLE;
AddUrlToFileList(it->target_url, flags);
}
const std::vector<Namespace>& fallbacks =
manifest.fallback_namespaces;
for (std::vector<Namespace>::const_iterator it = fallbacks.begin();
it != fallbacks.end(); ++it) {
AddUrlToFileList(it->target_url, AppCacheEntry::FALLBACK);
}
if (update_type_ == UPGRADE_ATTEMPT) {
const AppCache::EntryMap& entries =
group_->newest_complete_cache()->entries();
for (AppCache::EntryMap::const_iterator it = entries.begin();
it != entries.end(); ++it) {
const AppCacheEntry& entry = it->second;
if (entry.IsMaster())
AddUrlToFileList(it->first, AppCacheEntry::MASTER);
}
}
}
void AppCacheUpdateJob::AddUrlToFileList(const GURL& url, int type) {
std::pair<AppCache::EntryMap::iterator, bool> ret = url_file_list_.insert(
AppCache::EntryMap::value_type(url, AppCacheEntry(type)));
if (ret.second)
urls_to_fetch_.push_back(UrlToFetch(url, false, NULL));
else
ret.first->second.add_types(type);
}
void AppCacheUpdateJob::FetchUrls() {
DCHECK(internal_state_ == DOWNLOADING);
while (pending_url_fetches_.size() < kMaxConcurrentUrlFetches &&
!urls_to_fetch_.empty()) {
UrlToFetch url_to_fetch = urls_to_fetch_.front();
urls_to_fetch_.pop_front();
AppCache::EntryMap::iterator it = url_file_list_.find(url_to_fetch.url);
DCHECK(it != url_file_list_.end());
AppCacheEntry& entry = it->second;
if (ShouldSkipUrlFetch(entry)) {
NotifyAllProgress(url_to_fetch.url);
++url_fetches_completed_;
} else if (AlreadyFetchedEntry(url_to_fetch.url, entry.types())) {
NotifyAllProgress(url_to_fetch.url);
++url_fetches_completed_;
} else if (!url_to_fetch.storage_checked &&
MaybeLoadFromNewestCache(url_to_fetch.url, entry)) {
} else {
URLFetcher* fetcher = new URLFetcher(
url_to_fetch.url, URLFetcher::URL_FETCH, this);
if (url_to_fetch.existing_response_info.get()) {
DCHECK(group_->newest_complete_cache());
AppCacheEntry* existing_entry =
group_->newest_complete_cache()->GetEntry(url_to_fetch.url);
DCHECK(existing_entry);
DCHECK(existing_entry->response_id() ==
url_to_fetch.existing_response_info->response_id());
fetcher->set_existing_response_headers(
url_to_fetch.existing_response_info->http_response_info()->headers
.get());
fetcher->set_existing_entry(*existing_entry);
}
fetcher->Start();
pending_url_fetches_.insert(
PendingUrlFetches::value_type(url_to_fetch.url, fetcher));
}
}
}
void AppCacheUpdateJob::CancelAllUrlFetches() {
for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
it != pending_url_fetches_.end(); ++it) {
delete it->second;
}
url_fetches_completed_ +=
pending_url_fetches_.size() + urls_to_fetch_.size();
pending_url_fetches_.clear();
urls_to_fetch_.clear();
}
bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry& entry) {
if (entry.IsExplicit() || entry.IsFallback() || entry.IsIntercept())
return false;
return false;
}
bool AppCacheUpdateJob::AlreadyFetchedEntry(const GURL& url,
int entry_type) {
DCHECK(internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE);
AppCacheEntry* existing =
inprogress_cache_.get() ? inprogress_cache_->GetEntry(url)
: group_->newest_complete_cache()->GetEntry(url);
if (existing) {
existing->add_types(entry_type);
return true;
}
return false;
}
void AppCacheUpdateJob::AddMasterEntryToFetchList(AppCacheHost* host,
const GURL& url,
bool is_new) {
DCHECK(!IsTerminating());
if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE) {
AppCache* cache;
if (inprogress_cache_.get()) {
host->AssociateIncompleteCache(inprogress_cache_.get(), manifest_url_);
cache = inprogress_cache_.get();
} else {
cache = group_->newest_complete_cache();
}
AppCacheEntry* entry = cache->GetEntry(url);
if (entry) {
entry->add_types(AppCacheEntry::MASTER);
if (internal_state_ == NO_UPDATE && !inprogress_cache_.get()) {
host->AssociateCompleteCache(cache);
}
if (is_new)
++master_entries_completed_;
return;
}
}
if (master_entry_fetches_.find(url) == master_entry_fetches_.end()) {
master_entries_to_fetch_.insert(url);
if (internal_state_ == DOWNLOADING || internal_state_ == NO_UPDATE)
FetchMasterEntries();
}
}
void AppCacheUpdateJob::FetchMasterEntries() {
DCHECK(internal_state_ == NO_UPDATE || internal_state_ == DOWNLOADING);
while (master_entry_fetches_.size() < kMaxConcurrentUrlFetches &&
!master_entries_to_fetch_.empty()) {
const GURL& url = *master_entries_to_fetch_.begin();
if (AlreadyFetchedEntry(url, AppCacheEntry::MASTER)) {
++master_entries_completed_;
if (internal_state_ == NO_UPDATE) {
DCHECK(!inprogress_cache_.get());
AppCache* cache = group_->newest_complete_cache();
PendingMasters::iterator found = pending_master_entries_.find(url);
DCHECK(found != pending_master_entries_.end());
PendingHosts& hosts = found->second;
for (PendingHosts::iterator host_it = hosts.begin();
host_it != hosts.end(); ++host_it) {
(*host_it)->AssociateCompleteCache(cache);
}
}
} else {
URLFetcher* fetcher = new URLFetcher(
url, URLFetcher::MASTER_ENTRY_FETCH, this);
fetcher->Start();
master_entry_fetches_.insert(PendingUrlFetches::value_type(url, fetcher));
}
master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
}
}
void AppCacheUpdateJob::CancelAllMasterEntryFetches(
const ErrorDetails& error_details) {
for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
it != master_entry_fetches_.end(); ++it) {
delete it->second;
master_entries_to_fetch_.insert(it->first);
}
master_entry_fetches_.clear();
master_entries_completed_ += master_entries_to_fetch_.size();
HostNotifier host_notifier;
while (!master_entries_to_fetch_.empty()) {
const GURL& url = *master_entries_to_fetch_.begin();
PendingMasters::iterator found = pending_master_entries_.find(url);
DCHECK(found != pending_master_entries_.end());
PendingHosts& hosts = found->second;
for (PendingHosts::iterator host_it = hosts.begin();
host_it != hosts.end(); ++host_it) {
AppCacheHost* host = *host_it;
host->AssociateNoCache(GURL());
host_notifier.AddHost(host);
host->RemoveObserver(this);
}
hosts.clear();
master_entries_to_fetch_.erase(master_entries_to_fetch_.begin());
}
host_notifier.SendErrorNotifications(error_details);
}
bool AppCacheUpdateJob::MaybeLoadFromNewestCache(const GURL& url,
AppCacheEntry& entry) {
if (update_type_ != UPGRADE_ATTEMPT)
return false;
AppCache* newest = group_->newest_complete_cache();
AppCacheEntry* copy_me = newest->GetEntry(url);
if (!copy_me || !copy_me->has_response_id())
return false;
loading_responses_.insert(
LoadingResponses::value_type(copy_me->response_id(), url));
storage_->LoadResponseInfo(manifest_url_, group_->group_id(),
copy_me->response_id(),
this);
return true;
}
void AppCacheUpdateJob::OnResponseInfoLoaded(
AppCacheResponseInfo* response_info, int64 response_id) {
const net::HttpResponseInfo* http_info = response_info ?
response_info->http_response_info() : NULL;
if (internal_state_ == FETCH_MANIFEST) {
if (http_info)
manifest_fetcher_->set_existing_response_headers(
http_info->headers.get());
manifest_fetcher_->Start();
return;
}
LoadingResponses::iterator found = loading_responses_.find(response_id);
DCHECK(found != loading_responses_.end());
const GURL& url = found->second;
if (!http_info) {
LoadFromNewestCacheFailed(url, NULL);
} else {
const std::string name = "vary";
std::string value;
void* iter = NULL;
if (!http_info->headers.get() ||
http_info->headers->RequiresValidation(http_info->request_time,
http_info->response_time,
base::Time::Now()) ||
http_info->headers->EnumerateHeader(&iter, name, &value)) {
LoadFromNewestCacheFailed(url, response_info);
} else {
DCHECK(group_->newest_complete_cache());
AppCacheEntry* copy_me = group_->newest_complete_cache()->GetEntry(url);
DCHECK(copy_me);
DCHECK(copy_me->response_id() == response_id);
AppCache::EntryMap::iterator it = url_file_list_.find(url);
DCHECK(it != url_file_list_.end());
AppCacheEntry& entry = it->second;
entry.set_response_id(response_id);
entry.set_response_size(copy_me->response_size());
inprogress_cache_->AddOrModifyEntry(url, entry);
NotifyAllProgress(url);
++url_fetches_completed_;
}
}
loading_responses_.erase(found);
MaybeCompleteUpdate();
}
void AppCacheUpdateJob::LoadFromNewestCacheFailed(
const GURL& url, AppCacheResponseInfo* response_info) {
if (internal_state_ == CACHE_FAILURE)
return;
urls_to_fetch_.push_front(UrlToFetch(url, true, response_info));
FetchUrls();
}
void AppCacheUpdateJob::MaybeCompleteUpdate() {
DCHECK(internal_state_ != CACHE_FAILURE);
if (master_entries_completed_ != pending_master_entries_.size() ||
url_fetches_completed_ != url_file_list_.size()) {
DCHECK(internal_state_ != COMPLETED);
return;
}
switch (internal_state_) {
case NO_UPDATE:
if (master_entries_completed_ > 0) {
switch (stored_state_) {
case UNSTORED:
StoreGroupAndCache();
return;
case STORING:
return;
case STORED:
break;
}
}
NotifyAllAssociatedHosts(NO_UPDATE_EVENT);
DiscardDuplicateResponses();
internal_state_ = COMPLETED;
break;
case DOWNLOADING:
internal_state_ = REFETCH_MANIFEST;
FetchManifest(false);
break;
case REFETCH_MANIFEST:
DCHECK(stored_state_ == STORED);
NotifyAllFinalProgress();
if (update_type_ == CACHE_ATTEMPT)
NotifyAllAssociatedHosts(CACHED_EVENT);
else
NotifyAllAssociatedHosts(UPDATE_READY_EVENT);
DiscardDuplicateResponses();
internal_state_ = COMPLETED;
LogHistogramStats(UPDATE_OK, GURL());
break;
case CACHE_FAILURE:
NOTREACHED();
break;
default:
break;
}
if (internal_state_ == COMPLETED)
DeleteSoon();
}
void AppCacheUpdateJob::ScheduleUpdateRetry(int delay_ms) {
}
void AppCacheUpdateJob::Cancel() {
internal_state_ = CANCELLED;
LogHistogramStats(CANCELLED_ERROR, GURL());
if (manifest_fetcher_) {
delete manifest_fetcher_;
manifest_fetcher_ = NULL;
}
for (PendingUrlFetches::iterator it = pending_url_fetches_.begin();
it != pending_url_fetches_.end(); ++it) {
delete it->second;
}
pending_url_fetches_.clear();
for (PendingUrlFetches::iterator it = master_entry_fetches_.begin();
it != master_entry_fetches_.end(); ++it) {
delete it->second;
}
master_entry_fetches_.clear();
ClearPendingMasterEntries();
DiscardInprogressCache();
if (manifest_response_writer_)
manifest_response_writer_.reset();
storage_->CancelDelegateCallbacks(this);
}
void AppCacheUpdateJob::ClearPendingMasterEntries() {
for (PendingMasters::iterator it = pending_master_entries_.begin();
it != pending_master_entries_.end(); ++it) {
PendingHosts& hosts = it->second;
for (PendingHosts::iterator host_it = hosts.begin();
host_it != hosts.end(); ++host_it) {
(*host_it)->RemoveObserver(this);
}
}
pending_master_entries_.clear();
}
void AppCacheUpdateJob::DiscardInprogressCache() {
if (stored_state_ == STORING) {
inprogress_cache_ = NULL;
added_master_entries_.clear();
return;
}
storage_->DoomResponses(manifest_url_, stored_response_ids_);
if (!inprogress_cache_.get()) {
if (group_ && group_->newest_complete_cache()) {
for (std::vector<GURL>::iterator iter = added_master_entries_.begin();
iter != added_master_entries_.end(); ++iter) {
group_->newest_complete_cache()->RemoveEntry(*iter);
}
}
added_master_entries_.clear();
return;
}
AppCache::AppCacheHosts& hosts = inprogress_cache_->associated_hosts();
while (!hosts.empty())
(*hosts.begin())->AssociateNoCache(GURL());
inprogress_cache_ = NULL;
added_master_entries_.clear();
}
void AppCacheUpdateJob::DiscardDuplicateResponses() {
storage_->DoomResponses(manifest_url_, duplicate_response_ids_);
}
void AppCacheUpdateJob::LogHistogramStats(
ResultType result, const GURL& failed_resource_url) {
AppCacheHistograms::CountUpdateJobResult(result, manifest_url_.GetOrigin());
if (result == UPDATE_OK)
return;
int percent_complete = 0;
if (url_file_list_.size() > 0) {
size_t actual_fetches_completed = url_fetches_completed_;
if (!failed_resource_url.is_empty() && actual_fetches_completed)
--actual_fetches_completed;
percent_complete = (static_cast<double>(actual_fetches_completed) /
static_cast<double>(url_file_list_.size())) * 100.0;
percent_complete = std::min(percent_complete, 99);
}
bool was_making_progress =
base::Time::Now() - last_progress_time_ <
base::TimeDelta::FromMinutes(5);
bool off_origin_resource_failure =
!failed_resource_url.is_empty() &&
(failed_resource_url.GetOrigin() != manifest_url_.GetOrigin());
AppCacheHistograms::LogUpdateFailureStats(
manifest_url_.GetOrigin(),
percent_complete,
was_making_progress,
off_origin_resource_failure);
}
void AppCacheUpdateJob::DeleteSoon() {
ClearPendingMasterEntries();
manifest_response_writer_.reset();
storage_->CancelDelegateCallbacks(this);
service_->RemoveObserver(this);
service_ = NULL;
group_->SetUpdateStatus(AppCacheGroup::IDLE);
group_ = NULL;
base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}
}