| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2009 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 "webkit/appcache/appcache_update_job.h" |
| 6 |
| 7 #include "base/compiler_specific.h" |
| 8 #include "base/message_loop.h" |
| 9 #include "net/base/io_buffer.h" |
| 10 #include "net/base/load_flags.h" |
| 11 #include "webkit/appcache/appcache_group.h" |
| 12 #include "webkit/appcache/appcache_host.h" |
| 13 |
| 14 namespace appcache { |
| 15 |
| 16 static const int kBufferSize = 4096; |
| 17 static const size_t kMaxConcurrentUrlFetches = 2; |
| 18 |
| 19 // Extra info associated with requests for use during response processing. |
| 20 // This info is deleted when the URLRequest is deleted. |
| 21 struct UpdateJobInfo : public URLRequest::UserData { |
| 22 enum RequestType { |
| 23 MANIFEST_FETCH, |
| 24 URL_FETCH, |
| 25 MANIFEST_REFETCH, |
| 26 }; |
| 27 |
| 28 explicit UpdateJobInfo(RequestType request_type) |
| 29 : type(request_type), |
| 30 buffer(new net::IOBuffer(kBufferSize)) { |
| 31 } |
| 32 |
| 33 RequestType type; |
| 34 scoped_refptr<net::IOBuffer> buffer; |
| 35 // TODO(jennb): need storage info to stream response data to storage |
| 36 }; |
| 37 |
| 38 // Helper class for collecting hosts per frontend when sending notifications |
| 39 // so that only one notification is sent for all hosts using the same frontend. |
| 40 class HostNotifier { |
| 41 public: |
| 42 typedef std::vector<int> HostIds; |
| 43 typedef std::map<AppCacheFrontend*, HostIds> NotifyHostMap; |
| 44 |
| 45 // Caller is responsible for ensuring there will be no duplicate hosts. |
| 46 void AddHost(AppCacheHost* host) { |
| 47 std::pair<NotifyHostMap::iterator , bool> ret = hosts_to_notify.insert( |
| 48 NotifyHostMap::value_type(host->frontend(), HostIds())); |
| 49 ret.first->second.push_back(host->host_id()); |
| 50 } |
| 51 |
| 52 void AddHosts(const std::set<AppCacheHost*>& hosts) { |
| 53 for (std::set<AppCacheHost*>::const_iterator it = hosts.begin(); |
| 54 it != hosts.end(); ++it) { |
| 55 AddHost(*it); |
| 56 } |
| 57 } |
| 58 |
| 59 void SendNotifications(EventID event_id) { |
| 60 for (NotifyHostMap::iterator it = hosts_to_notify.begin(); |
| 61 it != hosts_to_notify.end(); ++it) { |
| 62 AppCacheFrontend* frontend = it->first; |
| 63 frontend->OnEventRaised(it->second, event_id); |
| 64 } |
| 65 } |
| 66 |
| 67 private: |
| 68 NotifyHostMap hosts_to_notify; |
| 69 }; |
| 70 |
| 71 AppCacheUpdateJob::AppCacheUpdateJob(AppCacheService* service, |
| 72 AppCacheGroup* group) |
| 73 : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), |
| 74 service_(service), |
| 75 group_(group), |
| 76 update_type_(UNKNOWN_TYPE), |
| 77 internal_state_(FETCH_MANIFEST), |
| 78 master_entries_completed_(0), |
| 79 url_fetches_completed_(0), |
| 80 manifest_url_request_(NULL) { |
| 81 DCHECK(group_); |
| 82 manifest_url_ = group_->manifest_url(); |
| 83 } |
| 84 |
| 85 AppCacheUpdateJob::~AppCacheUpdateJob() { |
| 86 Cancel(); |
| 87 |
| 88 DCHECK(!manifest_url_request_); |
| 89 DCHECK(pending_url_fetches_.empty()); |
| 90 |
| 91 group_->SetUpdateStatus(AppCacheGroup::IDLE); |
| 92 } |
| 93 |
| 94 void AppCacheUpdateJob::StartUpdate(AppCacheHost* host, |
| 95 const GURL& new_master_resource) { |
| 96 DCHECK(group_->update_job() == this); |
| 97 |
| 98 if (!new_master_resource.is_empty()) { |
| 99 /* TODO(jennb): uncomment when processing master entries is implemented |
| 100 std::pair<PendingMasters::iterator, bool> ret = |
| 101 pending_master_entries_.insert( |
| 102 PendingMasters::value_type(new_master_resource, PendingHosts())); |
| 103 ret.first->second.push_back(host); |
| 104 */ |
| 105 } |
| 106 |
| 107 // Notify host (if any) if already checking or downloading. |
| 108 appcache::AppCacheGroup::UpdateStatus update_status = group_->update_status(); |
| 109 if (update_status == AppCacheGroup::CHECKING || |
| 110 update_status == AppCacheGroup::DOWNLOADING) { |
| 111 if (host) { |
| 112 NotifySingleHost(host, CHECKING_EVENT); |
| 113 if (update_status == AppCacheGroup::DOWNLOADING) |
| 114 NotifySingleHost(host, DOWNLOADING_EVENT); |
| 115 } |
| 116 return; |
| 117 } |
| 118 |
| 119 // Begin update process for the group. |
| 120 group_->SetUpdateStatus(AppCacheGroup::CHECKING); |
| 121 if (group_->HasCache()) { |
| 122 update_type_ = UPGRADE_ATTEMPT; |
| 123 NotifyAllAssociatedHosts(CHECKING_EVENT); |
| 124 } else { |
| 125 update_type_ = CACHE_ATTEMPT; |
| 126 DCHECK(host); |
| 127 NotifySingleHost(host, CHECKING_EVENT); |
| 128 } |
| 129 |
| 130 FetchManifest(true); |
| 131 } |
| 132 |
| 133 void AppCacheUpdateJob::FetchManifest(bool is_first_fetch) { |
| 134 DCHECK(!manifest_url_request_); |
| 135 manifest_url_request_ = new URLRequest(manifest_url_, this); |
| 136 UpdateJobInfo::RequestType fetch_type = is_first_fetch ? |
| 137 UpdateJobInfo::MANIFEST_FETCH : UpdateJobInfo::MANIFEST_REFETCH; |
| 138 manifest_url_request_->SetUserData(this, new UpdateJobInfo(fetch_type)); |
| 139 manifest_url_request_->set_context(service_->request_context()); |
| 140 // TODO(jennb): add "If-Modified-Since" if have previous date |
| 141 manifest_url_request_->set_load_flags( |
| 142 manifest_url_request_->load_flags() | net::LOAD_DISABLE_INTERCEPT); |
| 143 manifest_url_request_->Start(); |
| 144 } |
| 145 |
| 146 void AppCacheUpdateJob::OnResponseStarted(URLRequest *request) { |
| 147 if (request->status().is_success()) |
| 148 ReadResponseData(request); |
| 149 else |
| 150 OnResponseCompleted(request); |
| 151 } |
| 152 |
| 153 void AppCacheUpdateJob::ReadResponseData(URLRequest* request) { |
| 154 if (internal_state_ == CACHE_FAILURE) |
| 155 return; |
| 156 |
| 157 int bytes_read = 0; |
| 158 UpdateJobInfo* info = |
| 159 static_cast<UpdateJobInfo*>(request->GetUserData(this)); |
| 160 request->Read(info->buffer, kBufferSize, &bytes_read); |
| 161 OnReadCompleted(request, bytes_read); |
| 162 } |
| 163 |
| 164 void AppCacheUpdateJob::OnReadCompleted(URLRequest* request, int bytes_read) { |
| 165 bool data_consumed = true; |
| 166 if (request->status().is_success() && bytes_read > 0) { |
| 167 UpdateJobInfo* info = |
| 168 static_cast<UpdateJobInfo*>(request->GetUserData(this)); |
| 169 |
| 170 data_consumed = ConsumeResponseData(request, info, bytes_read); |
| 171 if (data_consumed) { |
| 172 bytes_read = 0; |
| 173 while (request->Read(info->buffer, kBufferSize, &bytes_read)) { |
| 174 if (bytes_read > 0) { |
| 175 data_consumed = ConsumeResponseData(request, info, bytes_read); |
| 176 if (!data_consumed) |
| 177 break; // wait for async data processing, then read more |
| 178 } else { |
| 179 break; |
| 180 } |
| 181 } |
| 182 } |
| 183 } |
| 184 |
| 185 if (data_consumed && !request->status().is_io_pending()) |
| 186 OnResponseCompleted(request); |
| 187 } |
| 188 |
| 189 bool AppCacheUpdateJob::ConsumeResponseData(URLRequest* request, |
| 190 UpdateJobInfo* info, |
| 191 int bytes_read) { |
| 192 switch (info->type) { |
| 193 case UpdateJobInfo::MANIFEST_FETCH: |
| 194 manifest_data_.append(info->buffer->data(), bytes_read); |
| 195 break; |
| 196 case UpdateJobInfo::URL_FETCH: |
| 197 // TODO(jennb): stream data to storage. will be async so need to wait |
| 198 // for callback before reading next chunk. |
| 199 // For now, schedule a task to continue reading to simulate async-ness. |
| 200 MessageLoop::current()->PostTask(FROM_HERE, |
| 201 method_factory_.NewRunnableMethod( |
| 202 &AppCacheUpdateJob::ReadResponseData, request)); |
| 203 return false; |
| 204 case UpdateJobInfo::MANIFEST_REFETCH: |
| 205 manifest_refetch_data_.append(info->buffer->data(), bytes_read); |
| 206 break; |
| 207 default: |
| 208 NOTREACHED(); |
| 209 } |
| 210 return true; |
| 211 } |
| 212 |
| 213 void AppCacheUpdateJob::OnReceivedRedirect(URLRequest* request, |
| 214 const GURL& new_url, |
| 215 bool* defer_redirect) { |
| 216 // Redirect is not allowed by the update process. |
| 217 request->Cancel(); |
| 218 OnResponseCompleted(request); |
| 219 } |
| 220 |
| 221 void AppCacheUpdateJob::OnResponseCompleted(URLRequest* request) { |
| 222 // TODO(jennb): think about retrying for 503s where retry-after is 0 |
| 223 UpdateJobInfo* info = |
| 224 static_cast<UpdateJobInfo*>(request->GetUserData(this)); |
| 225 switch (info->type) { |
| 226 case UpdateJobInfo::MANIFEST_FETCH: |
| 227 HandleManifestFetchCompleted(request); |
| 228 break; |
| 229 case UpdateJobInfo::URL_FETCH: |
| 230 HandleUrlFetchCompleted(request); |
| 231 break; |
| 232 case UpdateJobInfo::MANIFEST_REFETCH: |
| 233 HandleManifestRefetchCompleted(request); |
| 234 break; |
| 235 default: |
| 236 NOTREACHED(); |
| 237 } |
| 238 |
| 239 delete request; |
| 240 } |
| 241 |
| 242 void AppCacheUpdateJob::HandleManifestFetchCompleted(URLRequest* request) { |
| 243 DCHECK(internal_state_ == FETCH_MANIFEST); |
| 244 manifest_url_request_ = NULL; |
| 245 |
| 246 if (!request->status().is_success()) { |
| 247 LOG(INFO) << "Request non-success, status: " << request->status().status() |
| 248 << " os_error: " << request->status().os_error(); |
| 249 internal_state_ = CACHE_FAILURE; |
| 250 MaybeCompleteUpdate(); // if not done, run async cache failure steps |
| 251 return; |
| 252 } |
| 253 |
| 254 int response_code = request->GetResponseCode(); |
| 255 std::string mime_type; |
| 256 request->GetMimeType(&mime_type); |
| 257 |
| 258 if (response_code == 200 && mime_type == kManifestMimeType) { |
| 259 if (update_type_ == UPGRADE_ATTEMPT) |
| 260 CheckIfManifestChanged(); // continues asynchronously |
| 261 else |
| 262 ContinueHandleManifestFetchCompleted(true); |
| 263 } else if (response_code == 304 && update_type_ == UPGRADE_ATTEMPT) { |
| 264 ContinueHandleManifestFetchCompleted(false); |
| 265 } else if (response_code == 404 || response_code == 410) { |
| 266 group_->set_obsolete(true); |
| 267 NotifyAllAssociatedHosts(OBSOLETE_EVENT); |
| 268 NotifyAllPendingMasterHosts(ERROR_EVENT); |
| 269 DeleteSoon(); |
| 270 } else { |
| 271 LOG(INFO) << "Cache failure, response code: " << response_code; |
| 272 internal_state_ = CACHE_FAILURE; |
| 273 MaybeCompleteUpdate(); // if not done, run async cache failure steps |
| 274 } |
| 275 } |
| 276 |
| 277 void AppCacheUpdateJob::ContinueHandleManifestFetchCompleted(bool changed) { |
| 278 DCHECK(internal_state_ == FETCH_MANIFEST); |
| 279 |
| 280 if (!changed) { |
| 281 DCHECK(update_type_ == UPGRADE_ATTEMPT); |
| 282 internal_state_ = NO_UPDATE; |
| 283 MaybeCompleteUpdate(); // if not done, run async 6.9.4 step 7 substeps |
| 284 return; |
| 285 } |
| 286 |
| 287 Manifest manifest; |
| 288 if (!ParseManifest(manifest_url_, manifest_data_.data(), |
| 289 manifest_data_.length(), manifest)) { |
| 290 LOG(INFO) << "Failed to parse manifest: " << manifest_url_; |
| 291 internal_state_ = CACHE_FAILURE; |
| 292 MaybeCompleteUpdate(); // if not done, run async cache failure steps |
| 293 return; |
| 294 } |
| 295 |
| 296 // Proceed with update process. Section 6.9.4 steps 8-20. |
| 297 internal_state_ = DOWNLOADING; |
| 298 inprogress_cache_ = new AppCache(service_, service_->NewCacheId()); |
| 299 inprogress_cache_->set_owning_group(group_); |
| 300 BuildUrlFileList(manifest); |
| 301 inprogress_cache_->InitializeWithManifest(&manifest); |
| 302 |
| 303 // Associate all pending master hosts with the newly created cache. |
| 304 for (PendingMasters::iterator it = pending_master_entries_.begin(); |
| 305 it != pending_master_entries_.end(); ++it) { |
| 306 PendingHosts hosts = it->second; |
| 307 for (PendingHosts::iterator host_it = hosts.begin(); |
| 308 host_it != hosts.end(); ++host_it) { |
| 309 AppCacheHost* host = *host_it; |
| 310 host->AssociateCache(inprogress_cache_); |
| 311 } |
| 312 } |
| 313 |
| 314 group_->SetUpdateStatus(AppCacheGroup::DOWNLOADING); |
| 315 NotifyAllAssociatedHosts(DOWNLOADING_EVENT); |
| 316 FetchUrls(); |
| 317 MaybeCompleteUpdate(); // if not done, continues when async fetches complete |
| 318 } |
| 319 |
| 320 void AppCacheUpdateJob::HandleUrlFetchCompleted(URLRequest* request) { |
| 321 DCHECK(internal_state_ == DOWNLOADING); |
| 322 |
| 323 const GURL& url = request->original_url(); |
| 324 pending_url_fetches_.erase(url); |
| 325 ++url_fetches_completed_; |
| 326 |
| 327 int response_code = request->GetResponseCode(); |
| 328 AppCacheEntry& entry = url_file_list_.find(url)->second; |
| 329 |
| 330 if (request->status().is_success() && response_code == 200) { |
| 331 // TODO(jennb): associate storage with the new entry |
| 332 inprogress_cache_->AddEntry(url, entry); |
| 333 |
| 334 // Foreign entries will be detected during cache selection. |
| 335 // Note: 6.9.4, step 17.9 possible optimization: if resource is HTML or XML |
| 336 // file whose root element is an html element with a manifest attribute |
| 337 // whose value doesn't match the manifest url of the application cache |
| 338 // being processed, mark the entry as being foreign. |
| 339 } else { |
| 340 LOG(INFO) << "Request status: " << request->status().status() |
| 341 << " os_error: " << request->status().os_error() |
| 342 << " response code: " << response_code; |
| 343 |
| 344 // TODO(jennb): discard any stored data for this entry |
| 345 if (entry.IsExplicit() || entry.IsFallback()) { |
| 346 internal_state_ = CACHE_FAILURE; |
| 347 |
| 348 // Cancel any pending URL requests. |
| 349 for (PendingUrlFetches::iterator it = pending_url_fetches_.begin(); |
| 350 it != pending_url_fetches_.end(); ++it) { |
| 351 it->second->Cancel(); |
| 352 delete it->second; |
| 353 } |
| 354 |
| 355 url_fetches_completed_ += |
| 356 pending_url_fetches_.size() + urls_to_fetch_.size(); |
| 357 pending_url_fetches_.clear(); |
| 358 urls_to_fetch_.clear(); |
| 359 } else if (response_code == 404 || response_code == 410) { |
| 360 // Entry is skipped. They are dropped from the cache. |
| 361 } else if (update_type_ == UPGRADE_ATTEMPT) { |
| 362 // Copy the resource and its metadata from the newest complete cache. |
| 363 AppCache* cache = group_->newest_complete_cache(); |
| 364 AppCacheEntry* copy = cache->GetEntry(url); |
| 365 if (copy) |
| 366 CopyEntryToCache(url, *copy, &entry); |
| 367 } |
| 368 } |
| 369 |
| 370 // Fetch another URL now that one request has completed. |
| 371 if (internal_state_ != CACHE_FAILURE) |
| 372 FetchUrls(); |
| 373 |
| 374 MaybeCompleteUpdate(); |
| 375 } |
| 376 |
| 377 void AppCacheUpdateJob::HandleManifestRefetchCompleted(URLRequest* request) { |
| 378 DCHECK(internal_state_ == REFETCH_MANIFEST); |
| 379 manifest_url_request_ = NULL; |
| 380 |
| 381 int response_code = request->GetResponseCode(); |
| 382 if (response_code == 304 || manifest_data_ == manifest_refetch_data_) { |
| 383 AppCacheEntry entry(AppCacheEntry::MANIFEST); |
| 384 // TODO(jennb): add manifest_data_ to storage and put storage key in entry |
| 385 // Also store response headers from request for HTTP cache control. |
| 386 inprogress_cache_->AddOrModifyEntry(manifest_url_, entry); |
| 387 inprogress_cache_->set_update_time(base::TimeTicks::Now()); |
| 388 |
| 389 // TODO(jennb): start of part to make async (cache storage may fail; |
| 390 // group storage may fail) |
| 391 inprogress_cache_->set_complete(true); |
| 392 |
| 393 // TODO(jennb): write new cache to storage here |
| 394 group_->AddCache(inprogress_cache_); |
| 395 // TODO(jennb): write new group to storage here |
| 396 inprogress_cache_ = NULL; |
| 397 |
| 398 if (update_type_ == CACHE_ATTEMPT) { |
| 399 NotifyAllAssociatedHosts(CACHED_EVENT); |
| 400 } else { |
| 401 NotifyAllAssociatedHosts(UPDATE_READY_EVENT); |
| 402 } |
| 403 DeleteSoon(); |
| 404 // TODO(jennb): end of part that needs to be made async. |
| 405 } else { |
| 406 LOG(INFO) << "Request status: " << request->status().status() |
| 407 << " os_error: " << request->status().os_error() |
| 408 << " response code: " << response_code; |
| 409 ScheduleUpdateRetry(kRerunDelayMs); |
| 410 internal_state_ = CACHE_FAILURE; |
| 411 HandleCacheFailure(); |
| 412 } |
| 413 } |
| 414 |
| 415 void AppCacheUpdateJob::NotifySingleHost(AppCacheHost* host, |
| 416 EventID event_id) { |
| 417 std::vector<int> ids(1, host->host_id()); |
| 418 host->frontend()->OnEventRaised(ids, event_id); |
| 419 } |
| 420 |
| 421 void AppCacheUpdateJob::NotifyAllPendingMasterHosts(EventID event_id) { |
| 422 // Collect hosts so we only send one notification per frontend. |
| 423 // A host can only be associated with a single pending master entry |
| 424 // so no need to worry about duplicate hosts being added to the notifier. |
| 425 HostNotifier host_notifier; |
| 426 for (PendingMasters::iterator it = pending_master_entries_.begin(); |
| 427 it != pending_master_entries_.end(); ++it) { |
| 428 PendingHosts hosts = it->second; |
| 429 for (PendingHosts::iterator host_it = hosts.begin(); |
| 430 host_it != hosts.end(); ++host_it) { |
| 431 AppCacheHost* host = *host_it; |
| 432 host_notifier.AddHost(host); |
| 433 } |
| 434 } |
| 435 |
| 436 host_notifier.SendNotifications(event_id); |
| 437 } |
| 438 |
| 439 void AppCacheUpdateJob::NotifyAllAssociatedHosts(EventID event_id) { |
| 440 // Collect hosts so we only send one notification per frontend. |
| 441 // A host can only be associated with a single cache so no need to worry |
| 442 // about duplicate hosts being added to the notifier. |
| 443 HostNotifier host_notifier; |
| 444 if (inprogress_cache_) { |
| 445 DCHECK(internal_state_ == DOWNLOADING || internal_state_ == CACHE_FAILURE); |
| 446 host_notifier.AddHosts(inprogress_cache_->associated_hosts()); |
| 447 } |
| 448 |
| 449 AppCacheGroup::Caches old_caches = group_->old_caches(); |
| 450 for (AppCacheGroup::Caches::const_iterator it = old_caches.begin(); |
| 451 it != old_caches.end(); ++it) { |
| 452 host_notifier.AddHosts((*it)->associated_hosts()); |
| 453 } |
| 454 |
| 455 AppCache* newest_cache = group_->newest_complete_cache(); |
| 456 if (newest_cache) |
| 457 host_notifier.AddHosts(newest_cache->associated_hosts()); |
| 458 |
| 459 // TODO(jennb): if progress event, also pass params lengthComputable=true, |
| 460 // total = url_file_list_.size(), loaded=url_fetches_completed_. |
| 461 host_notifier.SendNotifications(event_id); |
| 462 } |
| 463 |
| 464 void AppCacheUpdateJob::CheckIfManifestChanged() { |
| 465 DCHECK(update_type_ == UPGRADE_ATTEMPT); |
| 466 /* |
| 467 AppCacheEntry* entry = |
| 468 group_->newest_complete_cache()->GetEntry(manifest_url_); |
| 469 */ |
| 470 // TODO(jennb): load manifest data from entry (async), continues in callback |
| 471 // callback invokes ContinueCheckIfManifestChanged |
| 472 // For now, schedule a task to continue checking with fake loaded data |
| 473 MessageLoop::current()->PostTask(FROM_HERE, |
| 474 method_factory_.NewRunnableMethod( |
| 475 &AppCacheUpdateJob::ContinueCheckIfManifestChanged, |
| 476 simulate_manifest_changed_ ? "different" : manifest_data_)); |
| 477 } |
| 478 |
| 479 void AppCacheUpdateJob::ContinueCheckIfManifestChanged( |
| 480 const std::string& loaded_manifest) { |
| 481 ContinueHandleManifestFetchCompleted(manifest_data_ != loaded_manifest); |
| 482 } |
| 483 |
| 484 void AppCacheUpdateJob::BuildUrlFileList(const Manifest& manifest) { |
| 485 for (base::hash_set<std::string>::const_iterator it = |
| 486 manifest.explicit_urls.begin(); |
| 487 it != manifest.explicit_urls.end(); ++it) { |
| 488 AddUrlToFileList(GURL(*it), AppCacheEntry::EXPLICIT); |
| 489 } |
| 490 |
| 491 const std::vector<FallbackNamespace>& fallbacks = |
| 492 manifest.fallback_namespaces; |
| 493 for (std::vector<FallbackNamespace>::const_iterator it = fallbacks.begin(); |
| 494 it != fallbacks.end(); ++it) { |
| 495 AddUrlToFileList(it->second, AppCacheEntry::FALLBACK); |
| 496 } |
| 497 |
| 498 // Add all master entries from newest complete cache. |
| 499 if (update_type_ == UPGRADE_ATTEMPT) { |
| 500 const AppCache::EntryMap& entries = |
| 501 group_->newest_complete_cache()->entries(); |
| 502 for (AppCache::EntryMap::const_iterator it = entries.begin(); |
| 503 it != entries.end(); ++it) { |
| 504 const AppCacheEntry& entry = it->second; |
| 505 if (entry.IsMaster()) |
| 506 AddUrlToFileList(it->first, AppCacheEntry::MASTER); |
| 507 } |
| 508 } |
| 509 } |
| 510 |
| 511 void AppCacheUpdateJob::AddUrlToFileList(const GURL& url, int type) { |
| 512 std::pair<AppCache::EntryMap::iterator, bool> ret = url_file_list_.insert( |
| 513 AppCache::EntryMap::value_type(url, AppCacheEntry(type))); |
| 514 |
| 515 if (ret.second) |
| 516 urls_to_fetch_.push_back(UrlToFetch(url, false)); |
| 517 else |
| 518 ret.first->second.add_types(type); // URL already exists. Merge types. |
| 519 } |
| 520 |
| 521 void AppCacheUpdateJob::FetchUrls() { |
| 522 DCHECK(internal_state_ == DOWNLOADING); |
| 523 |
| 524 // Fetch each URL in the list according to section 6.9.4 step 17.1-17.3. |
| 525 // Fetch up to the concurrent limit. Other fetches will be triggered as each |
| 526 // each fetch completes. |
| 527 while (pending_url_fetches_.size() < kMaxConcurrentUrlFetches && |
| 528 !urls_to_fetch_.empty()) { |
| 529 // Notify about progress first to ensure it starts from 0% in case any |
| 530 // entries are skipped. |
| 531 NotifyAllAssociatedHosts(PROGRESS_EVENT); |
| 532 |
| 533 const GURL url = urls_to_fetch_.front().first; |
| 534 bool storage_checked = urls_to_fetch_.front().second; |
| 535 urls_to_fetch_.pop_front(); |
| 536 |
| 537 AppCache::EntryMap::iterator it = url_file_list_.find(url); |
| 538 DCHECK(it != url_file_list_.end()); |
| 539 AppCacheEntry& entry = it->second; |
| 540 if (ShouldSkipUrlFetch(entry)) { |
| 541 ++url_fetches_completed_; |
| 542 } else if (!storage_checked && MaybeLoadFromNewestCache(url, entry)) { |
| 543 // Continues asynchronously after data is loaded from newest cache. |
| 544 } else { |
| 545 // Send URL request for the resource. |
| 546 URLRequest* request = new URLRequest(url, this); |
| 547 request->SetUserData(this, new UpdateJobInfo(UpdateJobInfo::URL_FETCH)); |
| 548 request->set_context(service_->request_context()); |
| 549 request->set_load_flags( |
| 550 request->load_flags() | net::LOAD_DISABLE_INTERCEPT); |
| 551 request->Start(); |
| 552 pending_url_fetches_.insert(PendingUrlFetches::value_type(url, request)); |
| 553 } |
| 554 } |
| 555 } |
| 556 |
| 557 bool AppCacheUpdateJob::ShouldSkipUrlFetch(const AppCacheEntry& entry) { |
| 558 if (entry.IsExplicit() || entry.IsFallback()) { |
| 559 return false; |
| 560 } |
| 561 |
| 562 // TODO(jennb): decide if entry should be skipped to expire it from cache |
| 563 return false; |
| 564 } |
| 565 |
| 566 bool AppCacheUpdateJob::MaybeLoadFromNewestCache(const GURL& url, |
| 567 AppCacheEntry& entry) { |
| 568 if (update_type_ != UPGRADE_ATTEMPT) |
| 569 return false; |
| 570 |
| 571 AppCache* newest = group_->newest_complete_cache(); |
| 572 AppCacheEntry* copy_me = newest->GetEntry(url); |
| 573 if (!copy_me) |
| 574 return false; |
| 575 |
| 576 // TODO(jennb): load HTTP headers for copy_me and wait for callback |
| 577 // In callback: |
| 578 // if HTTP caching semantics for entry allows its use, |
| 579 // CopyEntryData(url, copy_me, entry) |
| 580 // ++urls_fetches_completed_; |
| 581 // Else, add url back to front of urls_to_fetch and call FetchUrls(). |
| 582 // flag url somehow so that FetchUrls() doesn't end up here again. |
| 583 // For now: post a message to pretend entry could not be copied |
| 584 MessageLoop::current()->PostTask(FROM_HERE, |
| 585 method_factory_.NewRunnableMethod( |
| 586 &AppCacheUpdateJob::SimulateFailedLoadFromNewestCache, url)); |
| 587 return true; |
| 588 } |
| 589 |
| 590 // TODO(jennb): delete this after have real storage code |
| 591 void AppCacheUpdateJob::SimulateFailedLoadFromNewestCache(const GURL& url) { |
| 592 if (internal_state_ == CACHE_FAILURE) |
| 593 return; |
| 594 |
| 595 // Re-insert url at front of fetch list. Indicate storage has been checked. |
| 596 urls_to_fetch_.push_front(AppCacheUpdateJob::UrlToFetch(url, true)); |
| 597 FetchUrls(); |
| 598 } |
| 599 |
| 600 void AppCacheUpdateJob::CopyEntryToCache(const GURL& url, |
| 601 const AppCacheEntry& src, |
| 602 AppCacheEntry* dest) { |
| 603 DCHECK(dest); |
| 604 // TODO(jennb): copy storage key from src to dest |
| 605 inprogress_cache_->AddEntry(url, *dest); |
| 606 } |
| 607 |
| 608 bool AppCacheUpdateJob::MaybeCompleteUpdate() { |
| 609 if (master_entries_completed_ != pending_master_entries_.size() || |
| 610 url_fetches_completed_ != url_file_list_.size() ) { |
| 611 return false; |
| 612 } |
| 613 |
| 614 switch (internal_state_) { |
| 615 case NO_UPDATE: |
| 616 // 6.9.4 steps 7.3-7.7. |
| 617 NotifyAllAssociatedHosts(NO_UPDATE_EVENT); |
| 618 pending_master_entries_.clear(); |
| 619 DeleteSoon(); |
| 620 break; |
| 621 case DOWNLOADING: |
| 622 internal_state_ = REFETCH_MANIFEST; |
| 623 FetchManifest(false); |
| 624 return false; |
| 625 case CACHE_FAILURE: |
| 626 HandleCacheFailure(); |
| 627 break; |
| 628 default: |
| 629 NOTREACHED(); |
| 630 } |
| 631 return true; |
| 632 } |
| 633 |
| 634 void AppCacheUpdateJob::HandleCacheFailure() { |
| 635 // 6.9.4 cache failure steps 2-8. |
| 636 NotifyAllAssociatedHosts(ERROR_EVENT); |
| 637 pending_master_entries_.clear(); |
| 638 |
| 639 // Discard the inprogress cache. |
| 640 // TODO(jennb): cleanup possible storage for entries in the cache |
| 641 if (inprogress_cache_) { |
| 642 inprogress_cache_->set_owning_group(NULL); |
| 643 inprogress_cache_ = NULL; |
| 644 } |
| 645 |
| 646 // For a CACHE_ATTEMPT, group will be discarded when this update |
| 647 // job removes its reference to the group. Nothing more to do here. |
| 648 |
| 649 DeleteSoon(); |
| 650 } |
| 651 |
| 652 void AppCacheUpdateJob::ScheduleUpdateRetry(int delay_ms) { |
| 653 // TODO(jennb): post a delayed task with the "same parameters" as this job |
| 654 // to retry the update at a later time. Need group, URLs of pending master |
| 655 // entries and their hosts. |
| 656 } |
| 657 |
| 658 void AppCacheUpdateJob::Cancel() { |
| 659 if (manifest_url_request_) { |
| 660 delete manifest_url_request_; |
| 661 manifest_url_request_ = NULL; |
| 662 } |
| 663 |
| 664 // TODO(jennb): code other cancel cleanup (pending url requests, storage) |
| 665 } |
| 666 |
| 667 void AppCacheUpdateJob::DeleteSoon() { |
| 668 // TODO(jennb): revisit if update should be deleting itself |
| 669 MessageLoop::current()->DeleteSoon(FROM_HERE, this); |
| 670 } |
| 671 } // namespace appcache |
| OLD | NEW |