1 // Copyright (c) 2012 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/browser/appcache/appcache_group.h" 6 7 #include <algorithm> 8 9 #include "base/bind.h" 10 #include "base/logging.h" 11 #include "base/message_loop/message_loop.h" 12 #include "webkit/browser/appcache/appcache.h" 13 #include "webkit/browser/appcache/appcache_host.h" 14 #include "webkit/browser/appcache/appcache_service.h" 15 #include "webkit/browser/appcache/appcache_storage.h" 16 #include "webkit/browser/appcache/appcache_update_job.h" 17 18 namespace appcache { 19 20 class AppCacheGroup; 21 22 // Use this helper class because we cannot make AppCacheGroup a derived class 23 // of AppCacheHost::Observer as it would create a circular dependency between 24 // AppCacheHost and AppCacheGroup. 25 class AppCacheGroup::HostObserver : public AppCacheHost::Observer { 26 public: 27 explicit HostObserver(AppCacheGroup* group) : group_(group) {} 28 29 // Methods for AppCacheHost::Observer. 30 virtual void OnCacheSelectionComplete(AppCacheHost* host) OVERRIDE {} // N/A 31 virtual void OnDestructionImminent(AppCacheHost* host) OVERRIDE { 32 group_->HostDestructionImminent(host); 33 } 34 private: 35 AppCacheGroup* group_; 36 }; 37 38 AppCacheGroup::AppCacheGroup(AppCacheStorage* storage, 39 const GURL& manifest_url, 40 int64 group_id) 41 : group_id_(group_id), 42 manifest_url_(manifest_url), 43 update_status_(IDLE), 44 is_obsolete_(false), 45 is_being_deleted_(false), 46 newest_complete_cache_(NULL), 47 update_job_(NULL), 48 storage_(storage), 49 is_in_dtor_(false) { 50 storage_->working_set()->AddGroup(this); 51 host_observer_.reset(new HostObserver(this)); 52 } 53 54 AppCacheGroup::~AppCacheGroup() { 55 DCHECK(old_caches_.empty()); 56 DCHECK(!newest_complete_cache_); 57 DCHECK(restart_update_task_.IsCancelled()); 58 DCHECK(queued_updates_.empty()); 59 60 is_in_dtor_ = true; 61 62 if (update_job_) 63 delete update_job_; 64 DCHECK_EQ(IDLE, update_status_); 65 66 storage_->working_set()->RemoveGroup(this); 67 storage_->DeleteResponses(manifest_url_, newly_deletable_response_ids_); 68 } 69 70 void AppCacheGroup::AddUpdateObserver(UpdateObserver* observer) { 71 // If observer being added is a host that has been queued for later update, 72 // add observer to a different observer list. 73 AppCacheHost* host = static_cast<AppCacheHost*>(observer); 74 if (queued_updates_.find(host) != queued_updates_.end()) 75 queued_observers_.AddObserver(observer); 76 else 77 observers_.AddObserver(observer); 78 } 79 80 void AppCacheGroup::RemoveUpdateObserver(UpdateObserver* observer) { 81 observers_.RemoveObserver(observer); 82 queued_observers_.RemoveObserver(observer); 83 } 84 85 void AppCacheGroup::AddCache(AppCache* complete_cache) { 86 DCHECK(complete_cache->is_complete()); 87 complete_cache->set_owning_group(this); 88 89 if (!newest_complete_cache_) { 90 newest_complete_cache_ = complete_cache; 91 return; 92 } 93 94 if (complete_cache->IsNewerThan(newest_complete_cache_)) { 95 old_caches_.push_back(newest_complete_cache_); 96 newest_complete_cache_ = complete_cache; 97 98 // Update hosts of older caches to add a reference to the newest cache. 99 for (Caches::iterator it = old_caches_.begin(); 100 it != old_caches_.end(); ++it) { 101 AppCache::AppCacheHosts& hosts = (*it)->associated_hosts(); 102 for (AppCache::AppCacheHosts::iterator host_it = hosts.begin(); 103 host_it != hosts.end(); ++host_it) { 104 (*host_it)->SetSwappableCache(this); 105 } 106 } 107 } else { 108 old_caches_.push_back(complete_cache); 109 } 110 } 111 112 void AppCacheGroup::RemoveCache(AppCache* cache) { 113 DCHECK(cache->associated_hosts().empty()); 114 if (cache == newest_complete_cache_) { 115 AppCache* tmp_cache = newest_complete_cache_; 116 newest_complete_cache_ = NULL; 117 tmp_cache->set_owning_group(NULL); // may cause this group to be deleted 118 } else { 119 scoped_refptr<AppCacheGroup> protect(this); 120 121 Caches::iterator it = 122 std::find(old_caches_.begin(), old_caches_.end(), cache); 123 if (it != old_caches_.end()) { 124 AppCache* tmp_cache = *it; 125 old_caches_.erase(it); 126 tmp_cache->set_owning_group(NULL); // may cause group to be released 127 } 128 129 if (!is_obsolete() && old_caches_.empty() && 130 !newly_deletable_response_ids_.empty()) { 131 storage_->DeleteResponses(manifest_url_, newly_deletable_response_ids_); 132 newly_deletable_response_ids_.clear(); 133 } 134 } 135 } 136 137 void AppCacheGroup::AddNewlyDeletableResponseIds( 138 std::vector<int64>* response_ids) { 139 if (is_being_deleted() || (!is_obsolete() && old_caches_.empty())) { 140 storage_->DeleteResponses(manifest_url_, *response_ids); 141 response_ids->clear(); 142 return; 143 } 144 145 if (newly_deletable_response_ids_.empty()) { 146 newly_deletable_response_ids_.swap(*response_ids); 147 return; 148 } 149 newly_deletable_response_ids_.insert( 150 newly_deletable_response_ids_.end(), 151 response_ids->begin(), response_ids->end()); 152 response_ids->clear(); 153 } 154 155 void AppCacheGroup::StartUpdateWithNewMasterEntry( 156 AppCacheHost* host, const GURL& new_master_resource) { 157 DCHECK(!is_obsolete() && !is_being_deleted()); 158 if (is_in_dtor_) 159 return; 160 161 if (!update_job_) 162 update_job_ = new AppCacheUpdateJob(storage_->service(), this); 163 164 update_job_->StartUpdate(host, new_master_resource); 165 166 // Run queued update immediately as an update has been started manually. 167 if (!restart_update_task_.IsCancelled()) { 168 restart_update_task_.Cancel(); 169 RunQueuedUpdates(); 170 } 171 } 172 173 void AppCacheGroup::CancelUpdate() { 174 if (update_job_) { 175 delete update_job_; 176 DCHECK(!update_job_); 177 DCHECK_EQ(IDLE, update_status_); 178 } 179 } 180 181 void AppCacheGroup::QueueUpdate(AppCacheHost* host, 182 const GURL& new_master_resource) { 183 DCHECK(update_job_ && host && !new_master_resource.is_empty()); 184 queued_updates_.insert(QueuedUpdates::value_type(host, new_master_resource)); 185 186 // Need to know when host is destroyed. 187 host->AddObserver(host_observer_.get()); 188 189 // If host is already observing for updates, move host to queued observers 190 // list so that host is not notified when the current update completes. 191 if (FindObserver(host, observers_)) { 192 observers_.RemoveObserver(host); 193 queued_observers_.AddObserver(host); 194 } 195 } 196 197 void AppCacheGroup::RunQueuedUpdates() { 198 if (!restart_update_task_.IsCancelled()) 199 restart_update_task_.Cancel(); 200 201 if (queued_updates_.empty()) 202 return; 203 204 QueuedUpdates updates_to_run; 205 queued_updates_.swap(updates_to_run); 206 DCHECK(queued_updates_.empty()); 207 208 for (QueuedUpdates::iterator it = updates_to_run.begin(); 209 it != updates_to_run.end(); ++it) { 210 AppCacheHost* host = it->first; 211 host->RemoveObserver(host_observer_.get()); 212 if (FindObserver(host, queued_observers_)) { 213 queued_observers_.RemoveObserver(host); 214 observers_.AddObserver(host); 215 } 216 217 if (!is_obsolete() && !is_being_deleted()) 218 StartUpdateWithNewMasterEntry(host, it->second); 219 } 220 } 221 222 bool AppCacheGroup::FindObserver(UpdateObserver* find_me, 223 const ObserverList<UpdateObserver>& observer_list) { 224 return observer_list.HasObserver(find_me); 225 } 226 227 void AppCacheGroup::ScheduleUpdateRestart(int delay_ms) { 228 DCHECK(restart_update_task_.IsCancelled()); 229 restart_update_task_.Reset( 230 base::Bind(&AppCacheGroup::RunQueuedUpdates, this)); 231 base::MessageLoop::current()->PostDelayedTask( 232 FROM_HERE, 233 restart_update_task_.callback(), 234 base::TimeDelta::FromMilliseconds(delay_ms)); 235 } 236 237 void AppCacheGroup::HostDestructionImminent(AppCacheHost* host) { 238 queued_updates_.erase(host); 239 if (queued_updates_.empty() && !restart_update_task_.IsCancelled()) 240 restart_update_task_.Cancel(); 241 } 242 243 void AppCacheGroup::SetUpdateStatus(UpdateStatus status) { 244 if (status == update_status_) 245 return; 246 247 update_status_ = status; 248 249 if (status != IDLE) { 250 DCHECK(update_job_); 251 } else { 252 update_job_ = NULL; 253 254 // Observers may release us in these callbacks, so we protect against 255 // deletion by adding an extra ref in this scope (but only if we're not 256 // in our destructor). 257 scoped_refptr<AppCacheGroup> protect(is_in_dtor_ ? NULL : this); 258 FOR_EACH_OBSERVER(UpdateObserver, observers_, OnUpdateComplete(this)); 259 if (!queued_updates_.empty()) 260 ScheduleUpdateRestart(kUpdateRestartDelayMs); 261 } 262 } 263 264 } // namespace appcache 265