From d6d4f57562555379db537cadce9adc850f1015fc Mon Sep 17 00:00:00 2001 From: "dcamp@mozilla.com" Date: Tue, 24 Jul 2007 23:31:27 -0700 Subject: [PATCH] update the offline cache atomically. b=389223, r=biesi, sr=jst --- .../offline/nsDOMOfflineLoadStatusList.cpp | 83 ++++++----- dom/src/offline/nsDOMOfflineLoadStatusList.h | 6 +- netwerk/base/public/nsICachingChannel.idl | 8 +- netwerk/cache/public/nsICacheService.idl | 11 +- .../cache/public/nsIOfflineCacheSession.idl | 133 +++++++++++------- netwerk/cache/src/nsCacheService.cpp | 37 +++++ netwerk/cache/src/nsCacheService.h | 3 + netwerk/cache/src/nsCacheSession.cpp | 4 + netwerk/cache/src/nsDiskCacheDeviceSQL.cpp | 130 ++++++++++++++--- netwerk/cache/src/nsDiskCacheDeviceSQL.h | 6 + netwerk/protocol/http/src/nsHttpChannel.cpp | 32 ++++- netwerk/protocol/http/src/nsHttpChannel.h | 1 + uriloader/prefetch/nsOfflineCacheUpdate.cpp | 128 ++++++++++++++--- uriloader/prefetch/nsOfflineCacheUpdate.h | 12 +- 14 files changed, 460 insertions(+), 134 deletions(-) diff --git a/dom/src/offline/nsDOMOfflineLoadStatusList.cpp b/dom/src/offline/nsDOMOfflineLoadStatusList.cpp index d5711171415..206e1ec3b55 100644 --- a/dom/src/offline/nsDOMOfflineLoadStatusList.cpp +++ b/dom/src/offline/nsDOMOfflineLoadStatusList.cpp @@ -47,6 +47,7 @@ #include "nsNetCID.h" #include "nsICacheService.h" #include "nsICacheSession.h" +#include "nsIOfflineCacheUpdate.h" #include "nsContentUtils.h" #include "nsDOMError.h" #include "nsNetUtil.h" @@ -154,7 +155,6 @@ NS_INTERFACE_MAP_BEGIN(nsDOMOfflineLoadStatusList) NS_INTERFACE_MAP_ENTRY(nsIDOMLoadStatusList) NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget) NS_INTERFACE_MAP_ENTRY(nsIObserver) - NS_INTERFACE_MAP_ENTRY(nsIOfflineCacheUpdateObserver) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(LoadStatusList) NS_INTERFACE_MAP_END @@ -199,7 +199,7 @@ nsDOMOfflineLoadStatusList::Init() rv = cacheUpdateService->GetUpdate(i, getter_AddRefs(cacheUpdate)); NS_ENSURE_SUCCESS(rv, rv); - rv = WatchUpdate(cacheUpdate); + UpdateAdded(cacheUpdate); NS_ENSURE_SUCCESS(rv, rv); } @@ -210,6 +210,8 @@ nsDOMOfflineLoadStatusList::Init() rv = observerServ->AddObserver(this, "offline-cache-update-added", PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); + rv = observerServ->AddObserver(this, "offline-cache-update-completed", PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } @@ -231,7 +233,7 @@ nsDOMOfflineLoadStatusList::FindWrapper(nsIDOMLoadStatus *aStatus, } nsresult -nsDOMOfflineLoadStatusList::WatchUpdate(nsIOfflineCacheUpdate *aUpdate) +nsDOMOfflineLoadStatusList::UpdateAdded(nsIOfflineCacheUpdate *aUpdate) { nsCAutoString owner; nsresult rv = aUpdate->GetUpdateDomain(owner); @@ -256,19 +258,50 @@ nsDOMOfflineLoadStatusList::WatchUpdate(nsIOfflineCacheUpdate *aUpdate) mItems.AppendObject(wrapper); - rv = SendLoadEvent(NS_LITERAL_STRING(LOADREQUESTED_STR), - mLoadRequestedEventListeners, - wrapper); - NS_ENSURE_SUCCESS(rv, rv); + SendLoadEvent(NS_LITERAL_STRING(LOADREQUESTED_STR), + mLoadRequestedEventListeners, + wrapper); } - NS_ENSURE_SUCCESS(rv, rv); - - rv = aUpdate->AddObserver(this, PR_TRUE); - NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } +NS_IMETHODIMP +nsDOMOfflineLoadStatusList::UpdateCompleted(nsIOfflineCacheUpdate *aUpdate) +{ + nsCAutoString owner; + nsresult rv = aUpdate->GetUpdateDomain(owner); + NS_ENSURE_SUCCESS(rv, rv); + + if (owner != mHostPort) { + // This update doesn't belong to us + return NS_OK; + } + + PRUint32 numItems; + rv = aUpdate->GetCount(&numItems); + NS_ENSURE_SUCCESS(rv, rv); + + for (PRUint32 i = 0; i < numItems; i++) { + nsCOMPtr status; + rv = aUpdate->Item(i, getter_AddRefs(status)); + NS_ENSURE_SUCCESS(rv, rv); + + PRUint32 index; + nsCOMPtr wrapper = FindWrapper(status, &index); + if (wrapper) { + mItems.RemoveObjectAt(index); + nsresult rv = SendLoadEvent(NS_LITERAL_STRING(LOADCOMPLETED_STR), + mLoadCompletedEventListeners, + wrapper); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + + // // nsDOMOfflineLoadStatusList::nsIDOMLoadStatusList // @@ -468,29 +501,13 @@ nsDOMOfflineLoadStatusList::Observe(nsISupports *aSubject, if (!strcmp(aTopic, "offline-cache-update-added")) { nsCOMPtr update = do_QueryInterface(aSubject); if (update) { - rv = WatchUpdate(update); - NS_ENSURE_SUCCESS(rv, rv); + UpdateAdded(update); + } + } else if (!strcmp(aTopic, "offline-cache-update-completed")) { + nsCOMPtr update = do_QueryInterface(aSubject); + if (update) { + UpdateCompleted(update); } - } - - return NS_OK; -} - -// -// nsDOMLoadStatusList::nsIOfflineCacheUpdateObserver -// - -NS_IMETHODIMP -nsDOMOfflineLoadStatusList::ItemCompleted(nsIDOMLoadStatus *aItem) -{ - PRUint32 index; - nsCOMPtr wrapper = FindWrapper(aItem, &index); - if (wrapper) { - mItems.RemoveObjectAt(index); - nsresult rv = SendLoadEvent(NS_LITERAL_STRING(LOADCOMPLETED_STR), - mLoadCompletedEventListeners, - wrapper); - NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; diff --git a/dom/src/offline/nsDOMOfflineLoadStatusList.h b/dom/src/offline/nsDOMOfflineLoadStatusList.h index b347e604ad2..aefb08c7705 100644 --- a/dom/src/offline/nsDOMOfflineLoadStatusList.h +++ b/dom/src/offline/nsDOMOfflineLoadStatusList.h @@ -59,7 +59,6 @@ class nsDOMOfflineLoadStatus; class nsDOMOfflineLoadStatusList : public nsIDOMLoadStatusList, public nsIDOMEventTarget, public nsIObserver, - public nsIOfflineCacheUpdateObserver, public nsSupportsWeakReference { public: @@ -67,7 +66,6 @@ public: NS_DECL_NSIDOMLOADSTATUSLIST NS_DECL_NSIDOMEVENTTARGET NS_DECL_NSIOBSERVER - NS_DECL_NSIOFFLINECACHEUPDATEOBSERVER nsDOMOfflineLoadStatusList(nsIURI *aURI); virtual ~nsDOMOfflineLoadStatusList(); @@ -75,7 +73,8 @@ public: nsresult Init(); private : - nsresult WatchUpdate (nsIOfflineCacheUpdate *aUpdate); + nsresult UpdateAdded (nsIOfflineCacheUpdate *aUpdate); + nsresult UpdateCompleted (nsIOfflineCacheUpdate *aUpdate); nsIDOMLoadStatus *FindWrapper (nsIDOMLoadStatus *aStatus, PRUint32 *aIndex); void NotifyEventListeners(const nsCOMArray& aListeners, @@ -95,6 +94,7 @@ private : nsCOMArray mLoadRequestedEventListeners; nsCOMArray mLoadCompletedEventListeners; + nsCOMArray mUpdateCompletedEventListeners; }; class nsDOMLoadStatusEvent : public nsDOMEvent, diff --git a/netwerk/base/public/nsICachingChannel.idl b/netwerk/base/public/nsICachingChannel.idl index 5971ff8b4ab..d1d9bd6a64f 100644 --- a/netwerk/base/public/nsICachingChannel.idl +++ b/netwerk/base/public/nsICachingChannel.idl @@ -50,7 +50,7 @@ interface nsIFile; * 3) Support for uniquely identifying cached data in cases when the URL * is insufficient (e.g., HTTP form submission). */ -[scriptable, uuid(afafb719-bdf5-49c8-a4a9-db39ec331c9b)] +[scriptable, uuid(09556ba7-b13d-47d2-b154-fe690b063899)] interface nsICachingChannel : nsISupports { /** @@ -105,6 +105,12 @@ interface nsICachingChannel : nsISupports */ attribute boolean cacheForOfflineUse; + /** + * The session into which to cache offline data. If not specified, + * data will be placed in "HTTP-offline" + */ + attribute ACString offlineCacheClientID; + /** * Get the "file" where the cached data can be found. This is valid for * as long as a reference to the cache token is held. This may return diff --git a/netwerk/cache/public/nsICacheService.idl b/netwerk/cache/public/nsICacheService.idl index 9255f3383f0..728bf35c4a3 100644 --- a/netwerk/cache/public/nsICacheService.idl +++ b/netwerk/cache/public/nsICacheService.idl @@ -48,7 +48,7 @@ interface nsICacheListener; interface nsICacheSession; interface nsICacheVisitor; -[scriptable, uuid(de114eb4-29fc-4959-b2f7-2d03eb9bc771)] +[scriptable, uuid(98dd0187-aad4-4cab-82c5-1adddef3629d)] interface nsICacheService : nsISupports { /** @@ -84,6 +84,15 @@ interface nsICacheService : nsISupports * Evicts all entries in all devices implied by the storage policy. */ void evictEntries(in nsCacheStoragePolicy storagePolicy); + + /** + * Return a unique, temporary cache client ID. + * + * This is used by the offline cache. The offline cache lets clients + * accumulate entries in a temporary client and merge them in as a group + * using nsIOfflineCacheSession.mergeTemporaryClient(). + */ + ACString createTemporaryClientID(in nsCacheStoragePolicy storagePolicy); }; %{C++ diff --git a/netwerk/cache/public/nsIOfflineCacheSession.idl b/netwerk/cache/public/nsIOfflineCacheSession.idl index 93607279046..d98403ff9e2 100644 --- a/netwerk/cache/public/nsIOfflineCacheSession.idl +++ b/netwerk/cache/public/nsIOfflineCacheSession.idl @@ -40,37 +40,39 @@ #include "nsISupports.idl" #include "nsICache.idl" -[scriptable, uuid(0058c32b-0d93-4cf8-a561-e6f749c8a7b1)] +/** + * The offline cache is meant to reliably store resources for + * offline use. The expected semantics are: + * + * a) Once populated, the cache will not evict an application resource + * unless explicitly asked. + * + * b) Resources no longer in use by the application should be evicted. + * + * c) If the cache fills up, new entries should be rejected rather + * than throwing out old ones. + * + * The offline cache uses domains to concretely represent an + * application. It maintains a list of resources to be pinned for + * each domain. This list is separate from actual cache + * population - the caller is still responsible for placing items + * in the cache, and ownership can be declared without a + * corresponding entry. + * + * A key can optionally be associated with a specific URI within + * the domain. + */ + +[scriptable, uuid(de7875e5-a7e2-4d8d-ad01-807ef55ef8c0)] interface nsIOfflineCacheSession : nsISupports { - /** - * The offline cache is meant to reliably store resources for - * offline use. The expected semantics are: - * - * a) Once populated, the cache will not evict an application resource - * unless explicitly asked. - * - * b) Resources no longer in use by the application should be evicted. - * - * c) If the cache fills up, new entries should be rejected rather - * than throwing out old ones. - * - * The offline cache uses domains to concretely represent an - * application. It maintains a list of resources to be pinned for - * each domain. This list is separate from actual cache - * population - the caller is still responsible for placing items - * in the cache, and ownership can be declared without a - * corresponding entry. - * - * A key can optionally be associated with a specific URI within - * the domain. - */ - /** * Gets the list of owner domains in the cache. * - * @param count The number of domains returned - * @param uris The domains that own resources in the cache + * @param count + * The number of domains returned + * @param uris + * The domains that own resources in the cache */ void getOwnerDomains(out unsigned long count, [array, size_is(count)]out string domains); @@ -78,9 +80,12 @@ interface nsIOfflineCacheSession : nsISupports /** * Gets the list of owner URIs associated with a domain. * - * @param ownerDomain The domain to query - * @param count The number of uris returned - * @param uris The uris in this domain that own resources + * @param ownerDomain + * The domain to query + * @param count + * The number of uris returned + * @param uris + * The uris in this domain that own resources */ void getOwnerURIs(in ACString ownerDomain, out unsigned long count, @@ -96,12 +101,16 @@ interface nsIOfflineCacheSession : nsISupports * an entry is created with this key, it will be owned by the * domain/URI pair. * - * @param ownerDomain The domain that owns the resources. - * @param ownerURI The specific URI that owns the resources. This can - * be empty if no URI specifically owns the resources. - * @param count The number of keys in keys. - * @param keys The keys that the domain/URI pair own. This can be empty - * to clear ownership for the domain/URI pair. + * @param ownerDomain + * The domain that owns the resources. + * @param ownerURI + * The specific URI that owns the resources. This can be empty if + * no URI specifically owns the resources. + * @param count + * The number of keys in keys. + * @param keys + * The keys that the domain/URI pair own. This can be empty to + * clear ownership for the domain/URI pair. */ void setOwnedKeys(in ACString ownerDomain, in ACString ownerURI, @@ -111,11 +120,15 @@ interface nsIOfflineCacheSession : nsISupports /** * Gets the list of resources owned by a given domain/URI pair. * - * @param ownerDomain The domain that owns the resources. - * @param ownerURI The specific URI that owns the resources. This can - * be empty if no URI specifically owns the resources. - * @param count The number of keys in keys. - * @param keys The keys that the domain/URI pair own. + * @param ownerDomain + * The domain that owns the resources. + * @param ownerURI + * The specific URI that owns the resources. This can be empty + * if no URI specifically owns the resources. + * @param count + * The number of keys in keys. + * @param keys + * The keys that the domain/URI pair own. */ void getOwnedKeys(in ACString ownerDomain, in ACString ownerURI, @@ -129,10 +142,13 @@ interface nsIOfflineCacheSession : nsISupports * an entry is created with this key, it will be owned by the * domain/URI pair. * - * @param ownerDomain The domain that owns the resources. - * @param ownerURI The specific URI that owns the resources. This can - * be empty if no URI specifically owns the resources. - * @param key The key to add. + * @param ownerDomain + * The domain that owns the resources. + * @param ownerURI + * The specific URI that owns the resources. This can be empty + * if no URI specifically owns the resources. + * @param key + * The key to add. */ void addOwnedKey(in ACString ownerDomain, in ACString ownerURI, @@ -144,9 +160,11 @@ interface nsIOfflineCacheSession : nsISupports * If the key does not exist, an NS_ERROR_NOT_AVAILABLE exception * will be thrown. * - * @param ownerDomain The domain that owns the resources. - * @param ownerURI The specific URI that owns the resources. This can - * be empty if no URI specifically owns the resources. + * @param ownerDomain + * The domain that owns the resources. + * @param ownerURI + * The specific URI that owns the resources. This can be empty + * if no URI specifically owns the resources. * @param key The key to remove. */ void removeOwnedKey(in ACString ownerDomain, @@ -156,9 +174,11 @@ interface nsIOfflineCacheSession : nsISupports /** * Checks whether a key is owned by a given domain/URI pair. * - * @param ownerDomain The domain that owns the resources. - * @param ownerURI The specific URI that owns the resources. This can - * be empty if no URI specifically owns the resources. + * @param ownerDomain + * The domain that owns the resources. + * @param ownerURI + * The specific URI that owns the resources. This can be empty + * if no URI specifically owns the resources. * @param key The key to check */ boolean keyIsOwned(in ACString ownerDomain, @@ -177,4 +197,17 @@ interface nsIOfflineCacheSession : nsISupports * Evict all entries that are not owned by a domain. */ void evictUnownedEntries(); + + /** + * Merge the items from a temporary clientID in to this client. This lets + * offline cache updates accumulate in a temporary client and be moved + * in all at once. + * + * Entries in the temporary client will replace any entries in this client + * with the same cache key. + * + * Ownership lists for a given domain/URI pair from the temporary client + * will replace ownership lists for the same domain/URI pair. + */ + void mergeTemporaryClientID(in ACString temporaryClientID); }; diff --git a/netwerk/cache/src/nsCacheService.cpp b/netwerk/cache/src/nsCacheService.cpp index 81dc3e03205..ac5bcf26ad2 100644 --- a/netwerk/cache/src/nsCacheService.cpp +++ b/netwerk/cache/src/nsCacheService.cpp @@ -1030,6 +1030,25 @@ nsresult nsCacheService::EvictUnownedOfflineEntries(nsCacheSession * session) #endif } +nsresult nsCacheService::MergeTemporaryClientID(nsCacheSession * session, + const nsACString & clientID) +{ +#ifdef NECKO_OFFLINE_CACHE + if (session->StoragePolicy() != nsICache::STORE_OFFLINE) + return NS_ERROR_NOT_AVAILABLE; + + if (!gService->mOfflineDevice) { + nsresult rv = gService->CreateOfflineDevice(); + if (NS_FAILED(rv)) return rv; + } + + return gService->mOfflineDevice->MergeTemporaryClientID + (session->ClientID()->get(), PromiseFlatCString(clientID).get()); +#else // !NECKO_OFFLINE_CACHE + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + NS_IMETHODIMP nsCacheService::VisitEntries(nsICacheVisitor *visitor) { NS_ENSURE_ARG_POINTER(visitor); @@ -1083,6 +1102,24 @@ NS_IMETHODIMP nsCacheService::EvictEntries(nsCacheStoragePolicy storagePolicy) return EvictEntriesForClient(nsnull, storagePolicy); } +NS_IMETHODIMP nsCacheService::CreateTemporaryClientID(nsCacheStoragePolicy storagePolicy, + nsACString &clientID) +{ +#ifdef NECKO_OFFLINE_CACHE + // Only the offline cache device supports temporary clients + if (storagePolicy != nsICache::STORE_OFFLINE) + return NS_ERROR_NOT_AVAILABLE; + + if (!gService->mOfflineDevice) { + nsresult rv = gService->CreateOfflineDevice(); + if (NS_FAILED(rv)) return rv; + } + + return gService->mOfflineDevice->CreateTemporaryClientID(clientID); +#else // !NECKO_OFFLINE_CACHE + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} /** * Internal Methods diff --git a/netwerk/cache/src/nsCacheService.h b/netwerk/cache/src/nsCacheService.h index b8e9b278194..287af0a1277 100644 --- a/netwerk/cache/src/nsCacheService.h +++ b/netwerk/cache/src/nsCacheService.h @@ -139,6 +139,9 @@ public: static nsresult EvictUnownedOfflineEntries(nsCacheSession * session); + static nsresult MergeTemporaryClientID(nsCacheSession * session, + const nsACString & fromClientID); + /** * Methods called by nsCacheEntryDescriptor */ diff --git a/netwerk/cache/src/nsCacheSession.cpp b/netwerk/cache/src/nsCacheSession.cpp index 683be5ee7bf..4390106fb2e 100644 --- a/netwerk/cache/src/nsCacheSession.cpp +++ b/netwerk/cache/src/nsCacheSession.cpp @@ -197,3 +197,7 @@ NS_IMETHODIMP nsCacheSession::EvictUnownedEntries() return nsCacheService::EvictUnownedOfflineEntries(this); } +NS_IMETHODIMP nsCacheSession::MergeTemporaryClientID(const nsACString& fromClientID) +{ + return nsCacheService::MergeTemporaryClientID(this, fromClientID); +} diff --git a/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp b/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp index 5a742451883..15f5b265ff3 100644 --- a/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp +++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.cpp @@ -60,6 +60,7 @@ static const char OFFLINE_CACHE_DEVICE_ID[] = { "offline" }; #define LOG(args) CACHE_LOG_DEBUG(args) +static PRUint32 gNextTemporaryClientID = 0; /***************************************************************************** * helpers @@ -118,7 +119,7 @@ class EvictionObserver NS_LITERAL_CSTRING("CREATE TEMP TRIGGER cache_on_delete AFTER DELETE" " ON moz_cache FOR EACH ROW BEGIN SELECT" " cache_eviction_observer(" - " OLD.clientID, OLD.key, OLD.generation);" + " OLD.key, OLD.generation);" " END;")); } @@ -175,19 +176,14 @@ NS_IMPL_ISUPPORTS1(nsOfflineCacheEvictionFunction, mozIStorageFunction) // helper function for directly exposing the same data file binding // path algorithm used in nsOfflineCacheBinding::Create static nsresult -GetCacheDataFile(nsIFile *cacheDir, const char *cid, const char *key, +GetCacheDataFile(nsIFile *cacheDir, const char *key, int generation, nsCOMPtr &file) { cacheDir->Clone(getter_AddRefs(file)); if (!file) return NS_ERROR_OUT_OF_MEMORY; - nsCAutoString fullKey; - fullKey.Append(cid); - fullKey.Append(':'); - fullKey.Append(key); - - PRUint64 hash = DCacheHash(fullKey.get()); + PRUint64 hash = DCacheHash(key); PRUint32 dir1 = (PRUint32) (hash & 0x0F); PRUint32 dir2 = (PRUint32)((hash & 0xF0) >> 4); @@ -212,20 +208,19 @@ nsOfflineCacheEvictionFunction::OnFunctionCall(mozIStorageValueArray *values, ns PRUint32 numEntries; nsresult rv = values->GetNumEntries(&numEntries); NS_ENSURE_SUCCESS(rv, rv); - NS_ASSERTION(numEntries == 3, "unexpected number of arguments"); + NS_ASSERTION(numEntries == 2, "unexpected number of arguments"); PRUint32 valueLen; - const char *cid = values->AsSharedUTF8String(0, &valueLen); - const char *key = values->AsSharedUTF8String(1, &valueLen); - int generation = values->AsInt32(2); + const char *key = values->AsSharedUTF8String(0, &valueLen); + int generation = values->AsInt32(1); nsCOMPtr file; - rv = GetCacheDataFile(mDevice->CacheDirectory(), cid, key, + rv = GetCacheDataFile(mDevice->CacheDirectory(), key, generation, file); if (NS_FAILED(rv)) { - LOG(("GetCacheDataFile [cid=%s key=%s generation=%d] failed [rv=%x]!\n", - cid, key, generation, rv)); + LOG(("GetCacheDataFile [key=%s generation=%d] failed [rv=%x]!\n", + key, generation, rv)); return rv; } @@ -323,7 +318,7 @@ public: NS_DECL_ISUPPORTS static nsOfflineCacheBinding * - Create(nsIFile *cacheDir, const char *key, int generation); + Create(nsIFile *cacheDir, const nsCString *key, int generation); nsCOMPtr mDataFile; int mGeneration; @@ -333,7 +328,7 @@ NS_IMPL_THREADSAFE_ISUPPORTS0(nsOfflineCacheBinding) nsOfflineCacheBinding * nsOfflineCacheBinding::Create(nsIFile *cacheDir, - const char *key, + const nsCString *fullKey, int generation) { nsCOMPtr file; @@ -341,6 +336,11 @@ nsOfflineCacheBinding::Create(nsIFile *cacheDir, if (!file) return nsnull; + nsCAutoString keyBuf; + const char *cid, *key; + if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf)) + return nsnull; + PRUint64 hash = DCacheHash(key); PRUint32 dir1 = (PRUint32) (hash & 0x0F); @@ -444,7 +444,7 @@ CreateCacheEntry(nsOfflineCacheDevice *device, // create a binding object for this entry nsOfflineCacheBinding *binding = nsOfflineCacheBinding::Create(device->CacheDirectory(), - fullKey->get(), + fullKey, rec.generation); if (!binding) { @@ -765,7 +765,7 @@ nsOfflineCacheDevice::Init() new nsOfflineCacheEvictionFunction(this); if (!evictionFunction) return NS_ERROR_OUT_OF_MEMORY; - rv = mDB->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 3, evictionFunction); + rv = mDB->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 2, evictionFunction); NS_ENSURE_SUCCESS(rv, rv); // create all (most) of our statements up front @@ -789,9 +789,11 @@ nsOfflineCacheDevice::Init() StatementSql ( mStatement_AddOwnership, "INSERT INTO moz_cache_owners (ClientID, Domain, URI, Key) VALUES (?, ?, ?, ?);" ), StatementSql ( mStatement_CheckOwnership, "SELECT Key From moz_cache_owners WHERE ClientID = ? AND Domain = ? AND URI = ? AND Key = ?;" ), StatementSql ( mStatement_ListOwned, "SELECT Key FROM moz_cache_owners WHERE ClientID = ? AND Domain = ? AND URI = ?;" ), + StatementSql ( mStatement_ListOwners, "SELECT DISTINCT Domain, URI FROM moz_cache_owners WHERE ClientID = ?;"), StatementSql ( mStatement_ListOwnerDomains, "SELECT DISTINCT Domain FROM moz_cache_owners WHERE ClientID = ?;"), StatementSql ( mStatement_ListOwnerURIs, "SELECT DISTINCT URI FROM moz_cache_owners WHERE ClientID = ? AND Domain = ?;"), - StatementSql ( mStatement_DeleteUnowned, "DELETE FROM moz_cache WHERE rowid IN (SELECT moz_cache.rowid FROM moz_cache LEFT OUTER JOIN moz_cache_owners ON (moz_cache.ClientID = moz_cache_owners.ClientID AND moz_cache.Key = moz_cache_owners.Key) WHERE moz_cache.ClientID = ? AND moz_cache_owners.Domain ISNULL);" ) + StatementSql ( mStatement_DeleteUnowned, "DELETE FROM moz_cache WHERE rowid IN (SELECT moz_cache.rowid FROM moz_cache LEFT OUTER JOIN moz_cache_owners ON (moz_cache.ClientID = moz_cache_owners.ClientID AND moz_cache.Key = moz_cache_owners.Key) WHERE moz_cache.ClientID = ? AND moz_cache_owners.Domain ISNULL);" ), + StatementSql ( mStatement_SwapClientID, "UPDATE OR REPLACE moz_cache SET ClientID = ? WHERE ClientID = ?;") }; for (PRUint32 i=0; iExecuteSimpleSQL( + NS_LITERAL_CSTRING("DELETE FROM moz_cache" + " WHERE (ClientID GLOB \"TempClient*\")")); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; } @@ -972,7 +982,7 @@ nsOfflineCacheDevice::BindEntry(nsCacheEntry *entry) // create binding, pick best generation number nsRefPtr binding = - nsOfflineCacheBinding::Create(mCacheDirectory, entry->Key()->get(), -1); + nsOfflineCacheBinding::Create(mCacheDirectory, entry->Key(), -1); if (!binding) return NS_ERROR_OUT_OF_MEMORY; @@ -1474,6 +1484,84 @@ nsOfflineCacheDevice::EvictUnownedEntries(const char *clientID) return statement->Execute(); } +nsresult +nsOfflineCacheDevice::CreateTemporaryClientID(nsACString &clientID) +{ + nsCAutoString str; + str.AssignLiteral("TempClient"); + str.AppendInt(gNextTemporaryClientID++); + + clientID.Assign(str); + + return NS_OK; +} + +nsresult +nsOfflineCacheDevice::MergeTemporaryClientID(const char *clientID, + const char *fromClientID) +{ + LOG(("nsOfflineCacheDevice::MergeTemporaryClientID [cid=%s, from=%s]\n", + clientID, fromClientID)); + mozStorageTransaction transaction(mDB, PR_FALSE); + + // Move over ownerships + AutoResetStatement listOwnersStatement(mStatement_ListOwners); + nsresult rv = listOwnersStatement->BindUTF8StringParameter( + 0, nsDependentCString(fromClientID)); + NS_ENSURE_SUCCESS(rv, rv); + + // List all the owners in the new session + nsTArray domainArray; + nsTArray uriArray; + + PRBool hasRows; + rv = listOwnersStatement->ExecuteStep(&hasRows); + NS_ENSURE_SUCCESS(rv, rv); + while (hasRows) + { + PRUint32 length; + domainArray.AppendElement( + nsDependentCString(listOwnersStatement->AsSharedUTF8String(0, &length))); + uriArray.AppendElement( + nsDependentCString(listOwnersStatement->AsSharedUTF8String(1, &length))); + + rv = listOwnersStatement->ExecuteStep(&hasRows); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Now move over each ownership set + for (PRUint32 i = 0; i < domainArray.Length(); i++) { + PRUint32 count; + char **keys; + rv = GetOwnedKeys(fromClientID, domainArray[i], uriArray[i], + &count, &keys); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetOwnedKeys(clientID, domainArray[i], uriArray[i], + count, const_cast(keys)); + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, keys); + NS_ENSURE_SUCCESS(rv, rv); + + // Now clear out the temporary session's copy + rv = SetOwnedKeys(fromClientID, domainArray[i], uriArray[i], 0, 0); + NS_ENSURE_SUCCESS(rv, rv); + } + + EvictionObserver evictionObserver(mDB); + + AutoResetStatement swapStatement(mStatement_SwapClientID); + rv = swapStatement->BindUTF8StringParameter( + 0, nsDependentCString(clientID)); + rv |= swapStatement->BindUTF8StringParameter( + 1, nsDependentCString(fromClientID)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = swapStatement->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + return transaction.Commit(); +} + /** * Preference accessors */ diff --git a/netwerk/cache/src/nsDiskCacheDeviceSQL.h b/netwerk/cache/src/nsDiskCacheDeviceSQL.h index 3f12e5314ab..95614a894cc 100644 --- a/netwerk/cache/src/nsDiskCacheDeviceSQL.h +++ b/netwerk/cache/src/nsDiskCacheDeviceSQL.h @@ -121,6 +121,10 @@ public: const nsACString &ownerDomain); nsresult EvictUnownedEntries(const char *clientID); + nsresult CreateTemporaryClientID(nsACString &clientID); + nsresult MergeTemporaryClientID(const char *clientID, + const char *fromClientID); + /** * Preference accessors @@ -163,8 +167,10 @@ private: nsCOMPtr mStatement_CheckOwnership; nsCOMPtr mStatement_DeleteUnowned; nsCOMPtr mStatement_ListOwned; + nsCOMPtr mStatement_ListOwners; nsCOMPtr mStatement_ListOwnerDomains; nsCOMPtr mStatement_ListOwnerURIs; + nsCOMPtr mStatement_SwapClientID; nsCOMPtr mCacheDirectory; PRUint32 mCacheCapacity; diff --git a/netwerk/protocol/http/src/nsHttpChannel.cpp b/netwerk/protocol/http/src/nsHttpChannel.cpp index 6c3f325ba43..6e2104ccbd3 100644 --- a/netwerk/protocol/http/src/nsHttpChannel.cpp +++ b/netwerk/protocol/http/src/nsHttpChannel.cpp @@ -75,6 +75,7 @@ #include "nsStreamUtils.h" #include "nsIOService.h" #include "nsAuthInformationHolder.h" +#include "nsICacheService.h" // True if the local cache should be bypassed when processing a request. #define BYPASS_LOCAL_CACHE(loadFlags) \ @@ -1405,8 +1406,19 @@ nsHttpChannel::OpenOfflineCacheEntryForWriting() GenerateCacheKey(cacheKey); nsCOMPtr session; - rv = gHttpHandler->GetCacheSession(nsICache::STORE_OFFLINE, - getter_AddRefs(session)); + if (!mOfflineCacheClientID.IsEmpty()) { + nsCOMPtr serv = + do_GetService(NS_CACHESERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = serv->CreateSession(mOfflineCacheClientID.get(), + nsICache::STORE_OFFLINE, + nsICache::STREAM_BASED, + getter_AddRefs(session)); + } else { + rv = gHttpHandler->GetCacheSession(nsICache::STORE_OFFLINE, + getter_AddRefs(session)); + } if (NS_FAILED(rv)) return rv; rv = session->OpenCacheEntry(cacheKey, nsICache::ACCESS_READ_WRITE, @@ -4550,6 +4562,22 @@ nsHttpChannel::SetCacheForOfflineUse(PRBool value) return NS_OK; } +NS_IMETHODIMP +nsHttpChannel::GetOfflineCacheClientID(nsACString &value) +{ + value = mOfflineCacheClientID; + + return NS_OK; +} + +NS_IMETHODIMP +nsHttpChannel::SetOfflineCacheClientID(const nsACString &value) +{ + mOfflineCacheClientID = value; + + return NS_OK; +} + NS_IMETHODIMP nsHttpChannel::GetCacheFile(nsIFile **cacheFile) { diff --git a/netwerk/protocol/http/src/nsHttpChannel.h b/netwerk/protocol/http/src/nsHttpChannel.h index 0c92694810a..676d855aace 100644 --- a/netwerk/protocol/http/src/nsHttpChannel.h +++ b/netwerk/protocol/http/src/nsHttpChannel.h @@ -254,6 +254,7 @@ private: nsCOMPtr mOfflineCacheEntry; nsCacheAccessMode mOfflineCacheAccess; + nsCString mOfflineCacheClientID; // auth specific data nsISupports *mProxyAuthContinuationState; diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.cpp b/uriloader/prefetch/nsOfflineCacheUpdate.cpp index e8391004bc1..2fddf0a827f 100644 --- a/uriloader/prefetch/nsOfflineCacheUpdate.cpp +++ b/uriloader/prefetch/nsOfflineCacheUpdate.cpp @@ -52,6 +52,7 @@ #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "nsStreamUtils.h" +#include "nsThreadUtils.h" #include "prlog.h" static nsOfflineCacheUpdateService *gOfflineCacheUpdateService = nsnull; @@ -85,10 +86,11 @@ private: // nsOfflineCacheUpdateItem::nsISupports //----------------------------------------------------------------------------- -NS_IMPL_ISUPPORTS5(nsOfflineCacheUpdateItem, +NS_IMPL_ISUPPORTS6(nsOfflineCacheUpdateItem, nsIDOMLoadStatus, nsIRequestObserver, nsIStreamListener, + nsIRunnable, nsIInterfaceRequestor, nsIChannelEventSink) @@ -99,9 +101,11 @@ NS_IMPL_ISUPPORTS5(nsOfflineCacheUpdateItem, nsOfflineCacheUpdateItem::nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate, nsIURI *aURI, nsIURI *aReferrerURI, - nsIDOMNode *aSource) + nsIDOMNode *aSource, + const nsACString &aClientID) : mURI(aURI) , mReferrerURI(aReferrerURI) + , mClientID(aClientID) , mUpdate(aUpdate) , mChannel(nsnull) , mState(nsIDOMLoadStatus::UNINITIALIZED) @@ -139,6 +143,11 @@ nsOfflineCacheUpdateItem::OpenChannel() if (cachingChannel) { rv = cachingChannel->SetCacheForOfflineUse(PR_TRUE); NS_ENSURE_SUCCESS(rv, rv); + + if (!mClientID.IsEmpty()) { + rv = cachingChannel->SetOfflineCacheClientID(mClientID); + NS_ENSURE_SUCCESS(rv, rv); + } } rv = mChannel->AsyncOpen(this, nsnull); @@ -206,6 +215,20 @@ nsOfflineCacheUpdateItem::OnStopRequest(nsIRequest *aRequest, mChannel->GetContentLength(&mBytesRead); } + // We need to notify the update that the load is complete, but we + // want to give the channel a chance to close the cache entries. + NS_DispatchToCurrentThread(this); + + return NS_OK; +} + + +//----------------------------------------------------------------------------- +// nsOfflineCacheUpdateItem::nsIRunnable +//----------------------------------------------------------------------------- +NS_IMETHODIMP +nsOfflineCacheUpdateItem::Run() +{ mUpdate->LoadCompleted(); return NS_OK; @@ -245,8 +268,15 @@ nsOfflineCacheUpdateItem::OnChannelRedirect(nsIChannel *aOldChannel, do_QueryInterface(aOldChannel); nsCOMPtr newCachingChannel = do_QueryInterface(aOldChannel); - if (newCachingChannel) - newCachingChannel->SetCacheForOfflineUse(PR_TRUE); + if (newCachingChannel) { + rv = newCachingChannel->SetCacheForOfflineUse(PR_TRUE); + NS_ENSURE_SUCCESS(rv, rv); + if (!mClientID.IsEmpty()) { + rv = newCachingChannel->SetOfflineCacheClientID(mClientID); + NS_ENSURE_SUCCESS(rv, rv); + } + } + PRBool match; rv = newURI->SchemeIs("http", &match); @@ -370,6 +400,8 @@ nsOfflineCacheUpdate::nsOfflineCacheUpdate() : mState(STATE_UNINITIALIZED) , mAddedItems(PR_FALSE) , mPartialUpdate(PR_FALSE) + , mSucceeded(PR_TRUE) + , mCurrentItem(-1) { } @@ -409,9 +441,27 @@ nsOfflineCacheUpdate::Init(PRBool aPartialUpdate, getter_AddRefs(session)); NS_ENSURE_SUCCESS(rv, rv); - mCacheSession = do_QueryInterface(session, &rv); + mMainCacheSession = do_QueryInterface(session, &rv); NS_ENSURE_SUCCESS(rv, rv); + // Partial updates don't use temporary cache sessions + if (aPartialUpdate) { + mCacheSession = mMainCacheSession; + } else { + rv = cacheService->CreateTemporaryClientID(nsICache::STORE_OFFLINE, + mClientID); + NS_ENSURE_SUCCESS(rv, rv); + + rv = cacheService->CreateSession(mClientID.get(), + nsICache::STORE_OFFLINE, + nsICache::STREAM_BASED, + getter_AddRefs(session)); + NS_ENSURE_SUCCESS(rv, rv); + + mCacheSession = do_QueryInterface(session, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + mState = STATE_INITIALIZED; return NS_OK; @@ -424,10 +474,23 @@ nsOfflineCacheUpdate::LoadCompleted() LOG(("nsOfflineCacheUpdate::LoadCompleted [%p]", this)); - NS_ASSERTION(mItems.Length() >= 1, "Unknown load completed"); + nsRefPtr item = mItems[mCurrentItem]; + mCurrentItem++; - nsRefPtr item = mItems[0]; - mItems.RemoveElementAt(0); + PRUint16 status; + rv = item->GetStatus(&status); + + // Check for failures. Only connection or server errors (5XX) will cause + // the update to fail. + if (NS_FAILED(rv) || status == 0 || status >= 500) { + // Only fail updates from this domain. Outside-of-domain updates + // are not guaranteeed to be updated. + nsCAutoString domain; + item->mURI->GetHostPort(domain); + if (domain == mUpdateDomain) { + mSucceeded = PR_FALSE; + } + } rv = NotifyCompleted(item); if (NS_FAILED(rv)) return; @@ -449,6 +512,7 @@ nsOfflineCacheUpdate::Begin() mState = STATE_RUNNING; + mCurrentItem = 0; ProcessNextURI(); return NS_OK; @@ -460,10 +524,12 @@ nsOfflineCacheUpdate::Cancel() LOG(("nsOfflineCacheUpdate::Cancel [%p]", this)); mState = STATE_CANCELLED; + mSucceeded = PR_FALSE; - if (mItems.Length() > 0) { - // First load might be running - mItems[0]->Cancel(); + if (mCurrentItem >= 0 && + mCurrentItem < static_cast(mItems.Length())) { + // Load might be running + mItems[mCurrentItem]->Cancel(); } return NS_OK; @@ -478,8 +544,8 @@ nsOfflineCacheUpdate::AddOwnedItems(const nsACString &aOwnerURI) { PRUint32 count; char **keys; - nsresult rv = mCacheSession->GetOwnedKeys(mUpdateDomain, aOwnerURI, - &count, &keys); + nsresult rv = mMainCacheSession->GetOwnedKeys(mUpdateDomain, aOwnerURI, + &count, &keys); NS_ENSURE_SUCCESS(rv, rv); AutoFreeArray autoFree(count, keys); @@ -488,7 +554,8 @@ nsOfflineCacheUpdate::AddOwnedItems(const nsACString &aOwnerURI) nsCOMPtr uri; if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), keys[i]))) { nsRefPtr item = - new nsOfflineCacheUpdateItem(this, uri, mReferrerURI, nsnull); + new nsOfflineCacheUpdateItem(this, uri, mReferrerURI, + nsnull, mClientID); if (!item) return NS_ERROR_OUT_OF_MEMORY; mItems.AppendElement(item); @@ -504,7 +571,7 @@ nsOfflineCacheUpdate::AddDomainItems() { PRUint32 count; char **uris; - nsresult rv = mCacheSession->GetOwnerURIs(mUpdateDomain, &count, &uris); + nsresult rv = mMainCacheSession->GetOwnerURIs(mUpdateDomain, &count, &uris); NS_ENSURE_SUCCESS(rv, rv); AutoFreeArray autoFree(count, uris); @@ -525,22 +592,23 @@ nsOfflineCacheUpdate::AddDomainItems() nsresult nsOfflineCacheUpdate::ProcessNextURI() { - LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, numItems=%d]", - this, mItems.Length())); + LOG(("nsOfflineCacheUpdate::ProcessNextURI [%p, current=%d, numItems=%d]", + this, mCurrentItem, mItems.Length())); - if (mState == STATE_CANCELLED || mItems.Length() == 0) { + if (mState == STATE_CANCELLED || + mCurrentItem >= static_cast(mItems.Length())) { return Finish(); } #if defined(PR_LOGGING) if (LOG_ENABLED()) { nsCAutoString spec; - mItems[0]->mURI->GetSpec(spec); + mItems[mCurrentItem]->mURI->GetSpec(spec); LOG(("%p: Opening channel for %s", this, spec.get())); } #endif - nsresult rv = mItems[0]->OpenChannel(); + nsresult rv = mItems[mCurrentItem]->OpenChannel(); if (NS_FAILED(rv)) { LoadCompleted(); return rv; @@ -584,6 +652,21 @@ nsOfflineCacheUpdate::Finish() nsOfflineCacheUpdateService *service = nsOfflineCacheUpdateService::GetInstance(); + if (!mPartialUpdate) { + if (mSucceeded) { + nsresult rv = mMainCacheSession->MergeTemporaryClientID(mClientID); + if (NS_FAILED(rv)) + mSucceeded = PR_FALSE; + } + + if (!mSucceeded) { + // Update was not merged, mark all the loads as failures + for (PRUint32 i = 0; i < mItems.Length(); i++) { + mItems[i]->Cancel(); + } + } + } + if (!service) return NS_ERROR_FAILURE; @@ -651,8 +734,9 @@ nsOfflineCacheUpdate::AddURI(nsIURI *aURI, nsIDOMNode *aSource) NS_ENSURE_SUCCESS(rv, rv); } - nsRefPtr item = - new nsOfflineCacheUpdateItem(this, aURI, mReferrerURI, aSource); + nsRefPtr item = + new nsOfflineCacheUpdateItem(this, aURI, mReferrerURI, + aSource, mClientID); if (!item) return NS_ERROR_OUT_OF_MEMORY; mItems.AppendElement(item); diff --git a/uriloader/prefetch/nsOfflineCacheUpdate.h b/uriloader/prefetch/nsOfflineCacheUpdate.h index 8914df2982f..31427bd9c35 100644 --- a/uriloader/prefetch/nsOfflineCacheUpdate.h +++ b/uriloader/prefetch/nsOfflineCacheUpdate.h @@ -55,6 +55,7 @@ #include "nsIOfflineCacheSession.h" #include "nsIPrefetchService.h" #include "nsIRequestObserver.h" +#include "nsIRunnable.h" #include "nsIStreamListener.h" #include "nsIURI.h" #include "nsIWebProgressListener.h" @@ -67,6 +68,7 @@ class nsOfflineCacheUpdate; class nsOfflineCacheUpdateItem : public nsIDOMLoadStatus , public nsIStreamListener + , public nsIRunnable , public nsIInterfaceRequestor , public nsIChannelEventSink { @@ -75,18 +77,21 @@ public: NS_DECL_NSIDOMLOADSTATUS NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIRUNNABLE NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSICHANNELEVENTSINK nsOfflineCacheUpdateItem(nsOfflineCacheUpdate *aUpdate, nsIURI *aURI, nsIURI *aReferrerURI, - nsIDOMNode *aSource); + nsIDOMNode *aSource, + const nsACString &aClientID); ~nsOfflineCacheUpdateItem(); nsCOMPtr mURI; nsCOMPtr mReferrerURI; nsCOMPtr mSource; + nsCString mClientID; nsresult OpenChannel(); nsresult Cancel(); @@ -131,14 +136,19 @@ private: PRBool mAddedItems; PRBool mPartialUpdate; + PRBool mSucceeded; nsCString mUpdateDomain; nsCString mOwnerURI; nsCOMPtr mReferrerURI; + nsCString mClientID; nsCOMPtr mCacheSession; + nsCOMPtr mMainCacheSession; + nsCOMPtr mObserverService; /* Items being updated */ + PRInt32 mCurrentItem; nsTArray > mItems; /* Clients watching this update for changes */