Bug 458861. Validate TrueType headers before activating downloaded font. r=roc, sr...
[wine-gecko.git] / netwerk / cache / src / nsDiskCacheDeviceSQL.cpp
blob98e9596b9cc0bbad51450c7cfa915a675bb7325b
1 /* vim:set ts=2 sw=2 sts=2 et cin: */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla.
17 * The Initial Developer of the Original Code is IBM Corporation.
18 * Portions created by IBM Corporation are Copyright (C) 2004
19 * IBM Corporation. All Rights Reserved.
21 * Contributor(s):
22 * Darin Fisher <darin@meer.net>
23 * Dave Camp <dcamp@mozilla.com>
24 * Honza Bambas <honzab@firemni.cz>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsCache.h"
41 #include "nsDiskCache.h"
42 #include "nsDiskCacheDeviceSQL.h"
43 #include "nsCacheService.h"
45 #include "nsNetCID.h"
46 #include "nsNetUtil.h"
47 #include "nsAutoPtr.h"
48 #include "nsEscape.h"
49 #include "nsIPrefBranch.h"
50 #include "nsIPrefService.h"
51 #include "nsString.h"
52 #include "nsPrintfCString.h"
53 #include "nsCRT.h"
54 #include "nsArrayUtils.h"
55 #include "nsIArray.h"
56 #include "nsIVariant.h"
58 #include "mozIStorageService.h"
59 #include "mozIStorageStatement.h"
60 #include "mozIStorageFunction.h"
61 #include "mozStorageHelper.h"
63 #include "nsICacheVisitor.h"
64 #include "nsISeekableStream.h"
66 static const char OFFLINE_CACHE_DEVICE_ID[] = { "offline" };
67 static NS_DEFINE_CID(kCacheServiceCID, NS_CACHESERVICE_CID);
69 #define LOG(args) CACHE_LOG_DEBUG(args)
71 static PRUint32 gNextTemporaryClientID = 0;
73 /*****************************************************************************
74 * helpers
77 static nsresult
78 EnsureDir(nsIFile *dir)
80 PRBool exists;
81 nsresult rv = dir->Exists(&exists);
82 if (NS_SUCCEEDED(rv) && !exists)
83 rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
84 return rv;
87 static PRBool
88 DecomposeCacheEntryKey(const nsCString *fullKey,
89 const char **cid,
90 const char **key,
91 nsCString &buf)
93 buf = *fullKey;
95 PRInt32 colon = buf.FindChar(':');
96 if (colon == kNotFound)
98 NS_ERROR("Invalid key");
99 return PR_FALSE;
101 buf.SetCharAt('\0', colon);
103 *cid = buf.get();
104 *key = buf.get() + colon + 1;
106 return PR_TRUE;
109 class AutoResetStatement
111 public:
112 AutoResetStatement(mozIStorageStatement *s)
113 : mStatement(s) {}
114 ~AutoResetStatement() { mStatement->Reset(); }
115 mozIStorageStatement *operator->() { return mStatement; }
116 private:
117 mozIStorageStatement *mStatement;
120 class EvictionObserver
122 public:
123 EvictionObserver(mozIStorageConnection *db,
124 nsOfflineCacheEvictionFunction *evictionFunction)
125 : mDB(db), mEvictionFunction(evictionFunction)
127 mDB->ExecuteSimpleSQL(
128 NS_LITERAL_CSTRING("CREATE TEMP TRIGGER cache_on_delete AFTER DELETE"
129 " ON moz_cache FOR EACH ROW BEGIN SELECT"
130 " cache_eviction_observer("
131 " OLD.key, OLD.generation);"
132 " END;"));
133 mEvictionFunction->Reset();
136 ~EvictionObserver()
138 mDB->ExecuteSimpleSQL(
139 NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;"));
140 mEvictionFunction->Reset();
143 void Apply() { return mEvictionFunction->Apply(); }
145 private:
146 mozIStorageConnection *mDB;
147 nsRefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
150 #define DCACHE_HASH_MAX LL_MAXINT
151 #define DCACHE_HASH_BITS 64
154 * nsOfflineCache::Hash(const char * key)
156 * This algorithm of this method implies nsOfflineCacheRecords will be stored
157 * in a certain order on disk. If the algorithm changes, existing cache
158 * map files may become invalid, and therefore the kCurrentVersion needs
159 * to be revised.
161 static PRUint64
162 DCacheHash(const char * key)
164 PRUint64 h = 0;
165 for (const PRUint8* s = (PRUint8*) key; *s != '\0'; ++s)
166 h = (h >> (DCACHE_HASH_BITS - 4)) ^ (h << 4) ^ *s;
167 return (h == 0 ? DCACHE_HASH_MAX : h);
170 /******************************************************************************
171 * nsOfflineCacheEvictionFunction
174 NS_IMPL_ISUPPORTS1(nsOfflineCacheEvictionFunction, mozIStorageFunction)
176 // helper function for directly exposing the same data file binding
177 // path algorithm used in nsOfflineCacheBinding::Create
178 static nsresult
179 GetCacheDataFile(nsIFile *cacheDir, const char *key,
180 int generation, nsCOMPtr<nsIFile> &file)
182 cacheDir->Clone(getter_AddRefs(file));
183 if (!file)
184 return NS_ERROR_OUT_OF_MEMORY;
186 PRUint64 hash = DCacheHash(key);
188 PRUint32 dir1 = (PRUint32) (hash & 0x0F);
189 PRUint32 dir2 = (PRUint32)((hash & 0xF0) >> 4);
191 hash >>= 8;
193 file->AppendNative(nsPrintfCString("%X", dir1));
194 file->AppendNative(nsPrintfCString("%X", dir2));
196 char leaf[64];
197 PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
198 return file->AppendNative(nsDependentCString(leaf));
201 NS_IMETHODIMP
202 nsOfflineCacheEvictionFunction::OnFunctionCall(mozIStorageValueArray *values, nsIVariant **_retval)
204 LOG(("nsOfflineCacheEvictionFunction::OnFunctionCall\n"));
206 *_retval = nsnull;
208 PRUint32 numEntries;
209 nsresult rv = values->GetNumEntries(&numEntries);
210 NS_ENSURE_SUCCESS(rv, rv);
211 NS_ASSERTION(numEntries == 2, "unexpected number of arguments");
213 PRUint32 valueLen;
214 const char *key = values->AsSharedUTF8String(0, &valueLen);
215 int generation = values->AsInt32(1);
217 nsCOMPtr<nsIFile> file;
218 rv = GetCacheDataFile(mDevice->CacheDirectory(), key,
219 generation, file);
220 if (NS_FAILED(rv))
222 LOG(("GetCacheDataFile [key=%s generation=%d] failed [rv=%x]!\n",
223 key, generation, rv));
224 return rv;
227 mItems.AppendObject(file);
229 return NS_OK;
232 void
233 nsOfflineCacheEvictionFunction::Apply()
235 LOG(("nsOfflineCacheEvictionFunction::Apply\n"));
237 for (PRInt32 i = 0; i < mItems.Count(); i++) {
238 #if defined(PR_LOGGING)
239 nsCAutoString path;
240 mItems[i]->GetNativePath(path);
241 LOG((" removing %s\n", path.get()));
242 #endif
244 mItems[i]->Remove(PR_FALSE);
247 Reset();
250 /******************************************************************************
251 * nsOfflineCacheDeviceInfo
254 class nsOfflineCacheDeviceInfo : public nsICacheDeviceInfo
256 public:
257 NS_DECL_ISUPPORTS
258 NS_DECL_NSICACHEDEVICEINFO
260 nsOfflineCacheDeviceInfo(nsOfflineCacheDevice* device)
261 : mDevice(device)
264 private:
265 nsOfflineCacheDevice* mDevice;
268 NS_IMPL_ISUPPORTS1(nsOfflineCacheDeviceInfo, nsICacheDeviceInfo)
270 NS_IMETHODIMP
271 nsOfflineCacheDeviceInfo::GetDescription(char **aDescription)
273 *aDescription = NS_strdup("Offline cache device");
274 return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
277 NS_IMETHODIMP
278 nsOfflineCacheDeviceInfo::GetUsageReport(char ** usageReport)
280 nsCAutoString buffer;
281 buffer.AppendLiteral("\n<tr>\n<td><b>Cache Directory:</b></td>\n<td><tt> ");
283 nsILocalFile *cacheDir = mDevice->CacheDirectory();
284 if (!cacheDir)
285 return NS_OK;
287 nsAutoString path;
288 nsresult rv = cacheDir->GetPath(path);
289 if (NS_SUCCEEDED(rv))
290 AppendUTF16toUTF8(path, buffer);
291 else
292 buffer.AppendLiteral("directory unavailable");
293 buffer.AppendLiteral("</tt></td>\n</tr>\n");
295 *usageReport = ToNewCString(buffer);
296 if (!*usageReport)
297 return NS_ERROR_OUT_OF_MEMORY;
299 return NS_OK;
302 NS_IMETHODIMP
303 nsOfflineCacheDeviceInfo::GetEntryCount(PRUint32 *aEntryCount)
305 *aEntryCount = mDevice->EntryCount();
306 return NS_OK;
309 NS_IMETHODIMP
310 nsOfflineCacheDeviceInfo::GetTotalSize(PRUint32 *aTotalSize)
312 *aTotalSize = mDevice->CacheSize();
313 return NS_OK;
316 NS_IMETHODIMP
317 nsOfflineCacheDeviceInfo::GetMaximumSize(PRUint32 *aMaximumSize)
319 *aMaximumSize = mDevice->CacheCapacity();
320 return NS_OK;
323 /******************************************************************************
324 * nsOfflineCacheBinding
327 class nsOfflineCacheBinding : public nsISupports
329 public:
330 NS_DECL_ISUPPORTS
332 static nsOfflineCacheBinding *
333 Create(nsIFile *cacheDir, const nsCString *key, int generation);
335 nsCOMPtr<nsIFile> mDataFile;
336 int mGeneration;
339 NS_IMPL_THREADSAFE_ISUPPORTS0(nsOfflineCacheBinding)
341 nsOfflineCacheBinding *
342 nsOfflineCacheBinding::Create(nsIFile *cacheDir,
343 const nsCString *fullKey,
344 int generation)
346 nsCOMPtr<nsIFile> file;
347 cacheDir->Clone(getter_AddRefs(file));
348 if (!file)
349 return nsnull;
351 nsCAutoString keyBuf;
352 const char *cid, *key;
353 if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
354 return nsnull;
356 PRUint64 hash = DCacheHash(key);
358 PRUint32 dir1 = (PRUint32) (hash & 0x0F);
359 PRUint32 dir2 = (PRUint32)((hash & 0xF0) >> 4);
361 hash >>= 8;
363 // XXX we might want to create these directories up-front
365 file->AppendNative(nsPrintfCString("%X", dir1));
366 file->Create(nsIFile::DIRECTORY_TYPE, 00700);
368 file->AppendNative(nsPrintfCString("%X", dir2));
369 file->Create(nsIFile::DIRECTORY_TYPE, 00700);
371 nsresult rv;
372 char leaf[64];
374 if (generation == -1)
376 file->AppendNative(NS_LITERAL_CSTRING("placeholder"));
378 for (generation = 0; ; ++generation)
380 PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
382 rv = file->SetNativeLeafName(nsDependentCString(leaf));
383 if (NS_FAILED(rv))
384 return nsnull;
385 rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
386 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
387 return nsnull;
388 if (NS_SUCCEEDED(rv))
389 break;
392 else
394 PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
395 rv = file->AppendNative(nsDependentCString(leaf));
396 if (NS_FAILED(rv))
397 return nsnull;
400 nsOfflineCacheBinding *binding = new nsOfflineCacheBinding;
401 if (!binding)
402 return nsnull;
404 binding->mDataFile.swap(file);
405 binding->mGeneration = generation;
406 return binding;
409 /******************************************************************************
410 * nsOfflineCacheRecord
413 struct nsOfflineCacheRecord
415 const char *clientID;
416 const char *key;
417 const PRUint8 *metaData;
418 PRUint32 metaDataLen;
419 PRInt32 generation;
420 PRInt32 flags;
421 PRInt32 dataSize;
422 PRInt32 fetchCount;
423 PRInt64 lastFetched;
424 PRInt64 lastModified;
425 PRInt64 expirationTime;
428 static nsCacheEntry *
429 CreateCacheEntry(nsOfflineCacheDevice *device,
430 const nsCString *fullKey,
431 const nsOfflineCacheRecord &rec)
433 if (rec.flags != 0)
435 LOG(("refusing to load busy entry\n"));
436 return nsnull;
439 nsCacheEntry *entry;
441 nsresult rv = nsCacheEntry::Create(fullKey->get(), // XXX enable sharing
442 nsICache::STREAM_BASED,
443 nsICache::STORE_OFFLINE,
444 device, &entry);
445 if (NS_FAILED(rv))
446 return nsnull;
448 entry->SetFetchCount((PRUint32) rec.fetchCount);
449 entry->SetLastFetched(SecondsFromPRTime(rec.lastFetched));
450 entry->SetLastModified(SecondsFromPRTime(rec.lastModified));
451 entry->SetExpirationTime(SecondsFromPRTime(rec.expirationTime));
452 entry->SetDataSize((PRUint32) rec.dataSize);
454 entry->UnflattenMetaData((const char *) rec.metaData, rec.metaDataLen);
456 // create a binding object for this entry
457 nsOfflineCacheBinding *binding =
458 nsOfflineCacheBinding::Create(device->CacheDirectory(),
459 fullKey,
460 rec.generation);
461 if (!binding)
463 delete entry;
464 return nsnull;
466 entry->SetData(binding);
468 return entry;
472 /******************************************************************************
473 * nsOfflineCacheEntryInfo
476 class nsOfflineCacheEntryInfo : public nsICacheEntryInfo
478 public:
479 NS_DECL_ISUPPORTS
480 NS_DECL_NSICACHEENTRYINFO
482 nsOfflineCacheRecord *mRec;
485 NS_IMPL_ISUPPORTS1(nsOfflineCacheEntryInfo, nsICacheEntryInfo)
487 NS_IMETHODIMP
488 nsOfflineCacheEntryInfo::GetClientID(char **result)
490 *result = NS_strdup(mRec->clientID);
491 return *result ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
494 NS_IMETHODIMP
495 nsOfflineCacheEntryInfo::GetDeviceID(char ** deviceID)
497 *deviceID = NS_strdup(OFFLINE_CACHE_DEVICE_ID);
498 return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
501 NS_IMETHODIMP
502 nsOfflineCacheEntryInfo::GetKey(nsACString &clientKey)
504 clientKey.Assign(mRec->key);
505 return NS_OK;
508 NS_IMETHODIMP
509 nsOfflineCacheEntryInfo::GetFetchCount(PRInt32 *aFetchCount)
511 *aFetchCount = mRec->fetchCount;
512 return NS_OK;
515 NS_IMETHODIMP
516 nsOfflineCacheEntryInfo::GetLastFetched(PRUint32 *aLastFetched)
518 *aLastFetched = SecondsFromPRTime(mRec->lastFetched);
519 return NS_OK;
522 NS_IMETHODIMP
523 nsOfflineCacheEntryInfo::GetLastModified(PRUint32 *aLastModified)
525 *aLastModified = SecondsFromPRTime(mRec->lastModified);
526 return NS_OK;
529 NS_IMETHODIMP
530 nsOfflineCacheEntryInfo::GetExpirationTime(PRUint32 *aExpirationTime)
532 *aExpirationTime = SecondsFromPRTime(mRec->expirationTime);
533 return NS_OK;
536 NS_IMETHODIMP
537 nsOfflineCacheEntryInfo::IsStreamBased(PRBool *aStreamBased)
539 *aStreamBased = PR_TRUE;
540 return NS_OK;
543 NS_IMETHODIMP
544 nsOfflineCacheEntryInfo::GetDataSize(PRUint32 *aDataSize)
546 *aDataSize = mRec->dataSize;
547 return NS_OK;
551 /******************************************************************************
552 * nsApplicationCacheNamespace
555 NS_IMPL_ISUPPORTS1(nsApplicationCacheNamespace, nsIApplicationCacheNamespace)
557 NS_IMETHODIMP
558 nsApplicationCacheNamespace::Init(PRUint32 itemType,
559 const nsACString &namespaceSpec,
560 const nsACString &data)
562 mItemType = itemType;
563 mNamespaceSpec = namespaceSpec;
564 mData = data;
565 return NS_OK;
568 NS_IMETHODIMP
569 nsApplicationCacheNamespace::GetItemType(PRUint32 *out)
571 *out = mItemType;
572 return NS_OK;
575 NS_IMETHODIMP
576 nsApplicationCacheNamespace::GetNamespaceSpec(nsACString &out)
578 out = mNamespaceSpec;
579 return NS_OK;
582 NS_IMETHODIMP
583 nsApplicationCacheNamespace::GetData(nsACString &out)
585 out = mData;
586 return NS_OK;
589 /******************************************************************************
590 * nsApplicationCache
593 class nsApplicationCache : public nsIApplicationCache
594 , public nsSupportsWeakReference
596 public:
597 NS_DECL_ISUPPORTS
598 NS_DECL_NSIAPPLICATIONCACHE
600 nsApplicationCache(nsOfflineCacheDevice *device,
601 const nsACString &group,
602 const nsACString &clientID);
604 virtual ~nsApplicationCache();
606 void MarkInvalid() { mValid = PR_FALSE; }
608 private:
609 nsRefPtr<nsOfflineCacheDevice> mDevice;
610 nsCString mGroup;
611 nsCString mClientID;
612 PRBool mValid;
615 NS_IMPL_ISUPPORTS2(nsApplicationCache,
616 nsIApplicationCache,
617 nsISupportsWeakReference)
619 nsApplicationCache::nsApplicationCache(nsOfflineCacheDevice *device,
620 const nsACString &group,
621 const nsACString &clientID)
622 : mDevice(device)
623 , mGroup(group)
624 , mClientID(clientID)
625 , mValid(PR_TRUE)
629 nsApplicationCache::~nsApplicationCache()
631 mDevice->mCaches.Remove(mClientID);
633 // If this isn't an active cache anymore, it can be destroyed.
634 if (mValid && !mDevice->IsActiveCache(mGroup, mClientID))
635 Discard();
638 NS_IMETHODIMP
639 nsApplicationCache::GetGroupID(nsACString &out)
641 out = mGroup;
642 return NS_OK;
645 NS_IMETHODIMP
646 nsApplicationCache::GetClientID(nsACString &out)
648 out = mClientID;
649 return NS_OK;
652 NS_IMETHODIMP
653 nsApplicationCache::GetActive(PRBool *out)
655 *out = mDevice->IsActiveCache(mGroup, mClientID);
656 return NS_OK;
659 NS_IMETHODIMP
660 nsApplicationCache::Activate()
662 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
664 mDevice->ActivateCache(mGroup, mClientID);
665 return NS_OK;
668 NS_IMETHODIMP
669 nsApplicationCache::Discard()
671 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
673 mValid = PR_FALSE;
675 if (mDevice->IsActiveCache(mGroup, mClientID))
677 mDevice->DeactivateGroup(mGroup);
680 return mDevice->EvictEntries(mClientID.get());
683 NS_IMETHODIMP
684 nsApplicationCache::MarkEntry(const nsACString &key,
685 PRUint32 typeBits)
687 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
689 return mDevice->MarkEntry(mClientID, key, typeBits);
693 NS_IMETHODIMP
694 nsApplicationCache::UnmarkEntry(const nsACString &key,
695 PRUint32 typeBits)
697 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
699 return mDevice->UnmarkEntry(mClientID, key, typeBits);
702 NS_IMETHODIMP
703 nsApplicationCache::GetTypes(const nsACString &key,
704 PRUint32 *typeBits)
706 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
708 return mDevice->GetTypes(mClientID, key, typeBits);
711 NS_IMETHODIMP
712 nsApplicationCache::GatherEntries(PRUint32 typeBits,
713 PRUint32 * count,
714 char *** keys)
716 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
718 return mDevice->GatherEntries(mClientID, typeBits, count, keys);
721 NS_IMETHODIMP
722 nsApplicationCache::AddNamespaces(nsIArray *namespaces)
724 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
726 if (!namespaces)
727 return NS_OK;
729 mozStorageTransaction transaction(mDevice->mDB, PR_FALSE);
731 PRUint32 length;
732 nsresult rv = namespaces->GetLength(&length);
733 NS_ENSURE_SUCCESS(rv, rv);
735 for (PRUint32 i = 0; i < length; i++) {
736 nsCOMPtr<nsIApplicationCacheNamespace> ns =
737 do_QueryElementAt(namespaces, i);
738 if (ns) {
739 rv = mDevice->AddNamespace(mClientID, ns);
740 NS_ENSURE_SUCCESS(rv, rv);
744 rv = transaction.Commit();
745 NS_ENSURE_SUCCESS(rv, rv);
747 return NS_OK;
750 NS_IMETHODIMP
751 nsApplicationCache::GetMatchingNamespace(const nsACString &key,
752 nsIApplicationCacheNamespace **out)
755 NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
757 return mDevice->GetMatchingNamespace(mClientID, key, out);
760 /******************************************************************************
761 * nsOfflineCacheDevice
764 NS_IMPL_ISUPPORTS1(nsOfflineCacheDevice, nsIApplicationCacheService)
766 nsOfflineCacheDevice::nsOfflineCacheDevice()
767 : mDB(nsnull)
768 , mCacheCapacity(0)
769 , mDeltaCounter(0)
773 nsOfflineCacheDevice::~nsOfflineCacheDevice()
775 Shutdown();
778 /* static */
779 PRBool
780 nsOfflineCacheDevice::GetStrictFileOriginPolicy()
782 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
784 PRBool retval;
785 if (prefs && NS_SUCCEEDED(prefs->GetBoolPref("security.fileuri.strict_origin_policy", &retval)))
786 return retval;
788 // As default value use true (be more strict)
789 return PR_TRUE;
792 PRUint32
793 nsOfflineCacheDevice::CacheSize()
795 AutoResetStatement statement(mStatement_CacheSize);
797 PRBool hasRows;
798 nsresult rv = statement->ExecuteStep(&hasRows);
799 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
801 return (PRUint32) statement->AsInt32(0);
804 PRUint32
805 nsOfflineCacheDevice::EntryCount()
807 AutoResetStatement statement(mStatement_EntryCount);
809 PRBool hasRows;
810 nsresult rv = statement->ExecuteStep(&hasRows);
811 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
813 return (PRUint32) statement->AsInt32(0);
816 nsresult
817 nsOfflineCacheDevice::UpdateEntry(nsCacheEntry *entry)
819 // Decompose the key into "ClientID" and "Key"
820 nsCAutoString keyBuf;
821 const char *cid, *key;
822 if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
823 return NS_ERROR_UNEXPECTED;
825 nsCString metaDataBuf;
826 PRUint32 mdSize = entry->MetaDataSize();
827 if (!EnsureStringLength(metaDataBuf, mdSize))
828 return NS_ERROR_OUT_OF_MEMORY;
829 char *md = metaDataBuf.BeginWriting();
830 entry->FlattenMetaData(md, mdSize);
832 nsOfflineCacheRecord rec;
833 rec.metaData = (const PRUint8 *) md;
834 rec.metaDataLen = mdSize;
835 rec.flags = 0; // mark entry as inactive
836 rec.dataSize = entry->DataSize();
837 rec.fetchCount = entry->FetchCount();
838 rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
839 rec.lastModified = PRTimeFromSeconds(entry->LastModified());
840 rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
842 AutoResetStatement statement(mStatement_UpdateEntry);
844 nsresult rv;
845 rv = statement->BindBlobParameter(0, rec.metaData, rec.metaDataLen);
846 rv |= statement->BindInt32Parameter(1, rec.flags);
847 rv |= statement->BindInt32Parameter(2, rec.dataSize);
848 rv |= statement->BindInt32Parameter(3, rec.fetchCount);
849 rv |= statement->BindInt64Parameter(4, rec.lastFetched);
850 rv |= statement->BindInt64Parameter(5, rec.lastModified);
851 rv |= statement->BindInt64Parameter(6, rec.expirationTime);
852 rv |= statement->BindUTF8StringParameter(7, nsDependentCString(cid));
853 rv |= statement->BindUTF8StringParameter(8, nsDependentCString(key));
854 NS_ENSURE_SUCCESS(rv, rv);
856 PRBool hasRows;
857 rv = statement->ExecuteStep(&hasRows);
858 NS_ENSURE_SUCCESS(rv, rv);
860 NS_ASSERTION(!hasRows, "UPDATE should not result in output");
861 return rv;
864 nsresult
865 nsOfflineCacheDevice::UpdateEntrySize(nsCacheEntry *entry, PRUint32 newSize)
867 // Decompose the key into "ClientID" and "Key"
868 nsCAutoString keyBuf;
869 const char *cid, *key;
870 if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
871 return NS_ERROR_UNEXPECTED;
873 AutoResetStatement statement(mStatement_UpdateEntrySize);
875 nsresult rv;
876 rv = statement->BindInt32Parameter(0, newSize);
877 rv |= statement->BindUTF8StringParameter(1, nsDependentCString(cid));
878 rv |= statement->BindUTF8StringParameter(2, nsDependentCString(key));
879 NS_ENSURE_SUCCESS(rv, rv);
881 PRBool hasRows;
882 rv = statement->ExecuteStep(&hasRows);
883 NS_ENSURE_SUCCESS(rv, rv);
885 NS_ASSERTION(!hasRows, "UPDATE should not result in output");
886 return rv;
889 nsresult
890 nsOfflineCacheDevice::DeleteEntry(nsCacheEntry *entry, PRBool deleteData)
892 if (deleteData)
894 nsresult rv = DeleteData(entry);
895 if (NS_FAILED(rv))
896 return rv;
899 // Decompose the key into "ClientID" and "Key"
900 nsCAutoString keyBuf;
901 const char *cid, *key;
902 if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
903 return NS_ERROR_UNEXPECTED;
905 AutoResetStatement statement(mStatement_DeleteEntry);
907 nsresult rv;
908 rv = statement->BindUTF8StringParameter(0, nsDependentCString(cid));
909 rv |= statement->BindUTF8StringParameter(1, nsDependentCString(key));
910 NS_ENSURE_SUCCESS(rv, rv);
912 PRBool hasRows;
913 rv = statement->ExecuteStep(&hasRows);
914 NS_ENSURE_SUCCESS(rv, rv);
916 NS_ASSERTION(!hasRows, "DELETE should not result in output");
917 return rv;
920 nsresult
921 nsOfflineCacheDevice::DeleteData(nsCacheEntry *entry)
923 nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
924 NS_ENSURE_STATE(binding);
926 return binding->mDataFile->Remove(PR_FALSE);
930 * nsCacheDevice implementation
933 /* static */
934 nsOfflineCacheDevice *
935 nsOfflineCacheDevice::GetInstance()
937 nsresult rv;
938 nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID, &rv);
939 NS_ENSURE_SUCCESS(rv, nsnull);
941 nsICacheService *iservice = static_cast<nsICacheService*>(serv.get());
942 nsCacheService *cacheService = static_cast<nsCacheService*>(iservice);
943 rv = cacheService->CreateOfflineDevice();
944 NS_ENSURE_SUCCESS(rv, nsnull);
946 NS_IF_ADDREF(cacheService->mOfflineDevice);
947 return cacheService->mOfflineDevice;
950 nsresult
951 nsOfflineCacheDevice::Init()
953 NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED);
955 // SetCacheParentDirectory must have been called
956 NS_ENSURE_TRUE(mCacheDirectory, NS_ERROR_UNEXPECTED);
958 // make sure the cache directory exists
959 nsresult rv = EnsureDir(mCacheDirectory);
960 NS_ENSURE_SUCCESS(rv, rv);
962 // build path to index file
963 nsCOMPtr<nsIFile> indexFile;
964 rv = mCacheDirectory->Clone(getter_AddRefs(indexFile));
965 NS_ENSURE_SUCCESS(rv, rv);
966 rv = indexFile->AppendNative(NS_LITERAL_CSTRING("index.sqlite"));
967 NS_ENSURE_SUCCESS(rv, rv);
969 nsCOMPtr<mozIStorageService> ss =
970 do_GetService("@mozilla.org/storage/service;1", &rv);
971 NS_ENSURE_SUCCESS(rv, rv);
973 rv = ss->OpenDatabase(indexFile, getter_AddRefs(mDB));
974 NS_ENSURE_SUCCESS(rv, rv);
976 mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
978 // XXX ... other initialization steps
980 // XXX in the future we may wish to verify the schema for moz_cache
981 // perhaps using "PRAGMA table_info" ?
983 // build the table
985 // "Generation" is the data file generation number.
986 // "Flags" is a bit-field indicating the state of the entry.
988 rv = mDB->ExecuteSimpleSQL(
989 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache (\n"
990 " ClientID TEXT,\n"
991 " Key TEXT,\n"
992 " MetaData BLOB,\n"
993 " Generation INTEGER,\n"
994 " Flags INTEGER,\n"
995 " DataSize INTEGER,\n"
996 " FetchCount INTEGER,\n"
997 " LastFetched INTEGER,\n"
998 " LastModified INTEGER,\n"
999 " ExpirationTime INTEGER,\n"
1000 " ItemType INTEGER DEFAULT 0\n"
1001 ");\n"));
1002 NS_ENSURE_SUCCESS(rv, rv);
1004 // Databases from 1.9.0 don't have the ItemType column. Add the column
1005 // here, but don't worry about failures (the column probably already exists)
1006 mDB->ExecuteSimpleSQL(
1007 NS_LITERAL_CSTRING("ALTER TABLE moz_cache ADD ItemType INTEGER DEFAULT 0"));
1009 // Create the table for storing cache groups. All actions on
1010 // moz_cache_groups use the GroupID, so use it as the primary key.
1011 rv = mDB->ExecuteSimpleSQL(
1012 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache_groups (\n"
1013 " GroupID TEXT PRIMARY KEY,\n"
1014 " ActiveClientID TEXT\n"
1015 ");\n"));
1016 NS_ENSURE_SUCCESS(rv, rv);
1018 mDB->ExecuteSimpleSQL(
1019 NS_LITERAL_CSTRING("ALTER TABLE moz_cache_groups "
1020 "ADD ActivateTimeStamp INTEGER DEFAULT 0"));
1022 // ClientID: clientID joining moz_cache and moz_cache_namespaces
1023 // tables.
1024 // Data: Data associated with this namespace (e.g. a fallback URI
1025 // for fallback entries).
1026 // ItemType: the type of namespace.
1027 rv = mDB->ExecuteSimpleSQL(
1028 NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS"
1029 " moz_cache_namespaces (\n"
1030 " ClientID TEXT,\n"
1031 " NameSpace TEXT,\n"
1032 " Data TEXT,\n"
1033 " ItemType INTEGER\n"
1034 ");\n"));
1035 NS_ENSURE_SUCCESS(rv, rv);
1037 // Databases from 1.9.0 have a moz_cache_index that should be dropped
1038 rv = mDB->ExecuteSimpleSQL(
1039 NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_cache_index"));
1040 NS_ENSURE_SUCCESS(rv, rv);
1042 // Key/ClientID pairs should be unique in the database. All queries
1043 // against moz_cache use the Key (which is also the most unique), so
1044 // use it as the primary key for this index.
1045 rv = mDB->ExecuteSimpleSQL(
1046 NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS "
1047 " moz_cache_key_clientid_index"
1048 " ON moz_cache (Key, ClientID);"));
1049 NS_ENSURE_SUCCESS(rv, rv);
1051 // Used for ClientID lookups and to keep ClientID/NameSpace pairs unique.
1052 rv = mDB->ExecuteSimpleSQL(
1053 NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS"
1054 " moz_cache_namespaces_clientid_index"
1055 " ON moz_cache_namespaces (ClientID, NameSpace);"));
1056 NS_ENSURE_SUCCESS(rv, rv);
1058 // Used for namespace lookups.
1059 rv = mDB->ExecuteSimpleSQL(
1060 NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS"
1061 " moz_cache_namespaces_namespace_index"
1062 " ON moz_cache_namespaces (NameSpace);"));
1063 NS_ENSURE_SUCCESS(rv, rv);
1066 mEvictionFunction = new nsOfflineCacheEvictionFunction(this);
1067 if (!mEvictionFunction) return NS_ERROR_OUT_OF_MEMORY;
1069 rv = mDB->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 2, mEvictionFunction);
1070 NS_ENSURE_SUCCESS(rv, rv);
1072 // create all (most) of our statements up front
1073 struct StatementSql {
1074 nsCOMPtr<mozIStorageStatement> &statement;
1075 const char *sql;
1076 StatementSql (nsCOMPtr<mozIStorageStatement> &aStatement, const char *aSql):
1077 statement (aStatement), sql (aSql) {}
1078 } prepared[] = {
1079 StatementSql ( mStatement_CacheSize, "SELECT Sum(DataSize) from moz_cache;" ),
1080 // XXX bug 442810: Restore the ability to monitor individual cache usage
1081 StatementSql ( mStatement_DomainSize, "SELECT 0;"),
1082 StatementSql ( mStatement_EntryCount, "SELECT count(*) from moz_cache;" ),
1083 StatementSql ( mStatement_UpdateEntry, "UPDATE moz_cache SET MetaData = ?, Flags = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ),
1084 StatementSql ( mStatement_UpdateEntrySize, "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ),
1085 StatementSql ( mStatement_UpdateEntryFlags, "UPDATE moz_cache SET Flags = ? WHERE ClientID = ? AND Key = ?;" ),
1086 StatementSql ( mStatement_DeleteEntry, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
1087 StatementSql ( mStatement_FindEntry, "SELECT MetaData, Generation, Flags, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime, ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
1088 StatementSql ( mStatement_BindEntry, "INSERT INTO moz_cache (ClientID, Key, MetaData, Generation, Flags, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime) VALUES(?,?,?,?,?,?,?,?,?,?);" ),
1090 StatementSql ( mStatement_MarkEntry, "UPDATE moz_cache SET ItemType = (ItemType | ?) WHERE ClientID = ? AND Key = ?;" ),
1091 StatementSql ( mStatement_UnmarkEntry, "UPDATE moz_cache SET ItemType = (ItemType & ~?) WHERE ClientID = ? AND Key = ?;" ),
1092 StatementSql ( mStatement_GetTypes, "SELECT ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;"),
1093 StatementSql ( mStatement_CleanupUnmarked, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ? AND ItemType = 0;" ),
1094 StatementSql ( mStatement_GatherEntries, "SELECT Key FROM moz_cache WHERE ClientID = ? AND (ItemType & ?) > 0;" ),
1096 StatementSql ( mStatement_ActivateClient, "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID, ActivateTimeStamp) VALUES (?, ?, ?);" ),
1097 StatementSql ( mStatement_DeactivateGroup, "DELETE FROM moz_cache_groups WHERE GroupID = ?;" ),
1098 StatementSql ( mStatement_FindClient, "SELECT ClientID, ItemType FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;" ),
1100 // Search for namespaces that match the URI. Use the <= operator
1101 // to ensure that we use the index on moz_cache_namespaces.
1102 StatementSql ( mStatement_FindClientByNamespace, "SELECT ns.ClientID, ns.ItemType FROM"
1103 " moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups"
1104 " ON ns.ClientID = groups.ActiveClientID"
1105 " WHERE ns.NameSpace <= ?1 AND ?1 GLOB ns.NameSpace || '*'"
1106 " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"),
1107 StatementSql ( mStatement_FindNamespaceEntry, "SELECT ns.NameSpace, ns.Data, ns.ItemType FROM "
1108 " moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups"
1109 " ON ns.ClientID = groups.ActiveClientID"
1110 " WHERE ClientID = ?1"
1111 " AND NameSpace <= ?2 AND ?2 GLOB NameSpace || '*'"
1112 " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"),
1113 StatementSql ( mStatement_InsertNamespaceEntry, "INSERT INTO moz_cache_namespaces (ClientID, NameSpace, Data, ItemType) VALUES(?, ?, ?, ?);"),
1115 for (PRUint32 i = 0; NS_SUCCEEDED(rv) && i < NS_ARRAY_LENGTH(prepared); ++i)
1117 LOG(("Creating statement: %s\n", prepared[i].sql));
1119 rv = mDB->CreateStatement(nsDependentCString(prepared[i].sql),
1120 getter_AddRefs(prepared[i].statement));
1121 NS_ENSURE_SUCCESS(rv, rv);
1124 // Clear up any dangling active flags
1125 rv = mDB->ExecuteSimpleSQL(
1126 NS_LITERAL_CSTRING("UPDATE moz_cache"
1127 " SET Flags=(Flags & ~1)"
1128 " WHERE (Flags & 1);"));
1129 NS_ENSURE_SUCCESS(rv, rv);
1131 rv = InitActiveCaches();
1132 NS_ENSURE_SUCCESS(rv, rv);
1134 return NS_OK;
1137 nsresult
1138 nsOfflineCacheDevice::InitActiveCaches()
1140 NS_ENSURE_TRUE(mCaches.Init(), NS_ERROR_OUT_OF_MEMORY);
1141 NS_ENSURE_TRUE(mActiveCachesByGroup.Init(), NS_ERROR_OUT_OF_MEMORY);
1143 nsresult rv = mActiveCaches.Init(5);
1144 NS_ENSURE_SUCCESS(rv, rv);
1146 nsCOMPtr<mozIStorageStatement> statement;
1147 rv =
1148 mDB->CreateStatement(NS_LITERAL_CSTRING("SELECT GroupID, ActiveClientID"
1149 " FROM moz_cache_groups"),
1150 getter_AddRefs(statement));
1151 NS_ENSURE_SUCCESS(rv, rv);
1153 PRBool hasRows;
1154 rv = statement->ExecuteStep(&hasRows);
1155 NS_ENSURE_SUCCESS(rv, rv);
1157 while (hasRows)
1159 nsCAutoString group;
1160 statement->GetUTF8String(0, group);
1161 nsCString clientID;
1162 statement->GetUTF8String(1, clientID);
1164 mActiveCaches.Put(clientID);
1165 mActiveCachesByGroup.Put(group, new nsCString(clientID));
1167 rv = statement->ExecuteStep(&hasRows);
1168 NS_ENSURE_SUCCESS(rv, rv);
1171 return NS_OK;
1174 /* static */
1175 PLDHashOperator
1176 nsOfflineCacheDevice::ShutdownApplicationCache(const nsACString &key,
1177 nsIWeakReference *weakRef,
1178 void *ctx)
1180 nsCOMPtr<nsIApplicationCache> obj = do_QueryReferent(weakRef);
1181 if (obj)
1183 nsApplicationCache *appCache = static_cast<nsApplicationCache*>(obj.get());
1184 appCache->MarkInvalid();
1187 return PL_DHASH_NEXT;
1190 nsresult
1191 nsOfflineCacheDevice::Shutdown()
1193 NS_ENSURE_TRUE(mDB, NS_ERROR_NOT_INITIALIZED);
1195 if (mCaches.IsInitialized())
1196 mCaches.EnumerateRead(ShutdownApplicationCache, this);
1198 EvictionObserver evictionObserver(mDB, mEvictionFunction);
1200 // Delete all rows whose clientID is not an active clientID.
1201 nsresult rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1202 "DELETE FROM moz_cache WHERE rowid IN"
1203 " (SELECT moz_cache.rowid FROM"
1204 " moz_cache LEFT OUTER JOIN moz_cache_groups ON"
1205 " (moz_cache.ClientID = moz_cache_groups.ActiveClientID)"
1206 " WHERE moz_cache_groups.GroupID ISNULL)"));
1208 if (NS_FAILED(rv))
1209 NS_WARNING("Failed to clean up unused application caches.");
1210 else
1211 evictionObserver.Apply();
1213 // Delete all namespaces whose clientID is not an active clientID.
1214 rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1215 "DELETE FROM moz_cache_namespaces WHERE rowid IN"
1216 " (SELECT moz_cache_namespaces.rowid FROM"
1217 " moz_cache_namespaces LEFT OUTER JOIN moz_cache_groups ON"
1218 " (moz_cache_namespaces.ClientID = moz_cache_groups.ActiveClientID)"
1219 " WHERE moz_cache_groups.GroupID ISNULL)"));
1221 if (NS_FAILED(rv))
1222 NS_WARNING("Failed to clean up namespaces.");
1224 mDB = 0;
1225 mEvictionFunction = 0;
1227 return NS_OK;
1230 const char *
1231 nsOfflineCacheDevice::GetDeviceID()
1233 return OFFLINE_CACHE_DEVICE_ID;
1236 nsCacheEntry *
1237 nsOfflineCacheDevice::FindEntry(nsCString *fullKey, PRBool *collision)
1239 LOG(("nsOfflineCacheDevice::FindEntry [key=%s]\n", fullKey->get()));
1241 // SELECT * FROM moz_cache WHERE key = ?
1243 // Decompose the key into "ClientID" and "Key"
1244 nsCAutoString keyBuf;
1245 const char *cid, *key;
1246 if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
1247 return nsnull;
1249 AutoResetStatement statement(mStatement_FindEntry);
1251 nsresult rv;
1252 rv = statement->BindUTF8StringParameter(0, nsDependentCString(cid));
1253 rv |= statement->BindUTF8StringParameter(1, nsDependentCString(key));
1254 NS_ENSURE_SUCCESS(rv, nsnull);
1256 PRBool hasRows;
1257 rv = statement->ExecuteStep(&hasRows);
1258 if (NS_FAILED(rv) || !hasRows)
1259 return nsnull; // entry not found
1261 nsOfflineCacheRecord rec;
1262 statement->GetSharedBlob(0, &rec.metaDataLen,
1263 (const PRUint8 **) &rec.metaData);
1264 rec.generation = statement->AsInt32(1);
1265 rec.flags = statement->AsInt32(2);
1266 rec.dataSize = statement->AsInt32(3);
1267 rec.fetchCount = statement->AsInt32(4);
1268 rec.lastFetched = statement->AsInt64(5);
1269 rec.lastModified = statement->AsInt64(6);
1270 rec.expirationTime = statement->AsInt64(7);
1272 LOG(("entry: [%u %d %d %d %d %lld %lld %lld]\n",
1273 rec.metaDataLen,
1274 rec.generation,
1275 rec.flags,
1276 rec.dataSize,
1277 rec.fetchCount,
1278 rec.lastFetched,
1279 rec.lastModified,
1280 rec.expirationTime));
1282 nsCacheEntry *entry = CreateCacheEntry(this, fullKey, rec);
1284 if (entry)
1286 // make sure that the data file exists
1287 nsOfflineCacheBinding *binding = (nsOfflineCacheBinding*)entry->Data();
1288 PRBool isFile;
1289 rv = binding->mDataFile->IsFile(&isFile);
1290 if (NS_FAILED(rv) || !isFile)
1292 DeleteEntry(entry, PR_FALSE);
1293 delete entry;
1294 return nsnull;
1297 statement->Reset();
1299 // mark as active
1300 AutoResetStatement updateStatement(mStatement_UpdateEntryFlags);
1301 rec.flags |= 0x1;
1302 rv |= updateStatement->BindInt32Parameter(0, rec.flags);
1303 rv |= updateStatement->BindUTF8StringParameter(1, nsDependentCString(cid));
1304 rv |= updateStatement->BindUTF8StringParameter(2, nsDependentCString(key));
1305 if (NS_FAILED(rv))
1307 delete entry;
1308 return nsnull;
1311 rv = updateStatement->ExecuteStep(&hasRows);
1312 if (NS_FAILED(rv))
1314 delete entry;
1315 return nsnull;
1318 NS_ASSERTION(!hasRows, "UPDATE should not result in output");
1321 return entry;
1324 nsresult
1325 nsOfflineCacheDevice::DeactivateEntry(nsCacheEntry *entry)
1327 LOG(("nsOfflineCacheDevice::DeactivateEntry [key=%s]\n",
1328 entry->Key()->get()));
1330 // This method is called to inform us that the nsCacheEntry object is going
1331 // away. We should persist anything that needs to be persisted, or if the
1332 // entry is doomed, we can go ahead and clear its storage.
1334 if (entry->IsDoomed())
1336 // remove corresponding row and file if they exist
1338 // the row should have been removed in DoomEntry... we could assert that
1339 // that happened. otherwise, all we have to do here is delete the file
1340 // on disk.
1341 DeleteData(entry);
1343 else
1345 // UPDATE the database row
1347 // XXX Assumption: the row already exists because it was either created
1348 // with a call to BindEntry or it was there when we called FindEntry.
1350 UpdateEntry(entry);
1353 delete entry;
1355 return NS_OK;
1358 nsresult
1359 nsOfflineCacheDevice::BindEntry(nsCacheEntry *entry)
1361 LOG(("nsOfflineCacheDevice::BindEntry [key=%s]\n", entry->Key()->get()));
1363 NS_ENSURE_STATE(!entry->Data());
1365 // This method is called to inform us that we have a new entry. The entry
1366 // may collide with an existing entry in our DB, but if that happens we can
1367 // assume that the entry is not being used.
1369 // INSERT the database row
1371 // XXX Assumption: if the row already exists, then FindEntry would have
1372 // returned it. if that entry was doomed, then DoomEntry would have removed
1373 // it from the table. so, we should always have to insert at this point.
1375 // Decompose the key into "ClientID" and "Key"
1376 nsCAutoString keyBuf;
1377 const char *cid, *key;
1378 if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
1379 return NS_ERROR_UNEXPECTED;
1381 // create binding, pick best generation number
1382 nsRefPtr<nsOfflineCacheBinding> binding =
1383 nsOfflineCacheBinding::Create(mCacheDirectory, entry->Key(), -1);
1384 if (!binding)
1385 return NS_ERROR_OUT_OF_MEMORY;
1387 nsOfflineCacheRecord rec;
1388 rec.clientID = cid;
1389 rec.key = key;
1390 rec.metaData = NULL; // don't write any metadata now.
1391 rec.metaDataLen = 0;
1392 rec.generation = binding->mGeneration;
1393 rec.flags = 0x1; // mark entry as active, we'll reset this in DeactivateEntry
1394 rec.dataSize = 0;
1395 rec.fetchCount = entry->FetchCount();
1396 rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
1397 rec.lastModified = PRTimeFromSeconds(entry->LastModified());
1398 rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
1400 AutoResetStatement statement(mStatement_BindEntry);
1402 nsresult rv;
1403 rv = statement->BindUTF8StringParameter(0, nsDependentCString(rec.clientID));
1404 rv |= statement->BindUTF8StringParameter(1, nsDependentCString(rec.key));
1405 rv |= statement->BindBlobParameter(2, rec.metaData, rec.metaDataLen);
1406 rv |= statement->BindInt32Parameter(3, rec.generation);
1407 rv |= statement->BindInt32Parameter(4, rec.flags);
1408 rv |= statement->BindInt32Parameter(5, rec.dataSize);
1409 rv |= statement->BindInt32Parameter(6, rec.fetchCount);
1410 rv |= statement->BindInt64Parameter(7, rec.lastFetched);
1411 rv |= statement->BindInt64Parameter(8, rec.lastModified);
1412 rv |= statement->BindInt64Parameter(9, rec.expirationTime);
1413 NS_ENSURE_SUCCESS(rv, rv);
1415 PRBool hasRows;
1416 rv = statement->ExecuteStep(&hasRows);
1417 NS_ENSURE_SUCCESS(rv, rv);
1418 NS_ASSERTION(!hasRows, "INSERT should not result in output");
1420 entry->SetData(binding);
1421 return NS_OK;
1424 void
1425 nsOfflineCacheDevice::DoomEntry(nsCacheEntry *entry)
1427 LOG(("nsOfflineCacheDevice::DoomEntry [key=%s]\n", entry->Key()->get()));
1429 // This method is called to inform us that we should mark the entry to be
1430 // deleted when it is no longer in use.
1432 // We can go ahead and delete the corresponding row in our table,
1433 // but we must not delete the file on disk until we are deactivated.
1435 DeleteEntry(entry, PR_FALSE);
1438 nsresult
1439 nsOfflineCacheDevice::OpenInputStreamForEntry(nsCacheEntry *entry,
1440 nsCacheAccessMode mode,
1441 PRUint32 offset,
1442 nsIInputStream **result)
1444 LOG(("nsOfflineCacheDevice::OpenInputStreamForEntry [key=%s]\n",
1445 entry->Key()->get()));
1447 *result = nsnull;
1449 NS_ENSURE_TRUE(offset < entry->DataSize(), NS_ERROR_INVALID_ARG);
1451 // return an input stream to the entry's data file. the stream
1452 // may be read on a background thread.
1454 nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1455 NS_ENSURE_STATE(binding);
1457 nsCOMPtr<nsIInputStream> in;
1458 NS_NewLocalFileInputStream(getter_AddRefs(in), binding->mDataFile, PR_RDONLY);
1459 if (!in)
1460 return NS_ERROR_UNEXPECTED;
1462 // respect |offset| param
1463 if (offset != 0)
1465 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(in);
1466 NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
1468 seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1471 in.swap(*result);
1472 return NS_OK;
1475 nsresult
1476 nsOfflineCacheDevice::OpenOutputStreamForEntry(nsCacheEntry *entry,
1477 nsCacheAccessMode mode,
1478 PRUint32 offset,
1479 nsIOutputStream **result)
1481 LOG(("nsOfflineCacheDevice::OpenOutputStreamForEntry [key=%s]\n",
1482 entry->Key()->get()));
1484 *result = nsnull;
1486 NS_ENSURE_TRUE(offset <= entry->DataSize(), NS_ERROR_INVALID_ARG);
1488 // return an output stream to the entry's data file. we can assume
1489 // that the output stream will only be used on the main thread.
1491 nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1492 NS_ENSURE_STATE(binding);
1494 nsCOMPtr<nsIOutputStream> out;
1495 NS_NewLocalFileOutputStream(getter_AddRefs(out), binding->mDataFile,
1496 PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
1497 00600);
1498 if (!out)
1499 return NS_ERROR_UNEXPECTED;
1501 // respect |offset| param
1502 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(out);
1503 NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
1504 if (offset != 0)
1505 seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1507 // truncate the file at the given offset
1508 seekable->SetEOF();
1510 nsCOMPtr<nsIOutputStream> bufferedOut;
1511 NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out, 16 * 1024);
1512 if (!bufferedOut)
1513 return NS_ERROR_UNEXPECTED;
1515 bufferedOut.swap(*result);
1516 return NS_OK;
1519 nsresult
1520 nsOfflineCacheDevice::GetFileForEntry(nsCacheEntry *entry, nsIFile **result)
1522 LOG(("nsOfflineCacheDevice::GetFileForEntry [key=%s]\n",
1523 entry->Key()->get()));
1525 nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1526 NS_ENSURE_STATE(binding);
1528 NS_IF_ADDREF(*result = binding->mDataFile);
1529 return NS_OK;
1532 nsresult
1533 nsOfflineCacheDevice::OnDataSizeChange(nsCacheEntry *entry, PRInt32 deltaSize)
1535 LOG(("nsOfflineCacheDevice::OnDataSizeChange [key=%s delta=%d]\n",
1536 entry->Key()->get(), deltaSize));
1538 const PRInt32 DELTA_THRESHOLD = 1<<14; // 16k
1540 // called to notify us of an impending change in the total size of the
1541 // specified entry.
1543 PRUint32 oldSize = entry->DataSize();
1544 NS_ASSERTION(deltaSize >= 0 || PRInt32(oldSize) + deltaSize >= 0, "oops");
1545 PRUint32 newSize = PRInt32(oldSize) + deltaSize;
1546 UpdateEntrySize(entry, newSize);
1548 mDeltaCounter += deltaSize; // this may go negative
1550 if (mDeltaCounter >= DELTA_THRESHOLD)
1552 if (CacheSize() > mCacheCapacity) {
1553 // the entry will overrun the cache capacity, doom the entry
1554 // and abort
1555 #ifdef DEBUG
1556 nsresult rv =
1557 #endif
1558 nsCacheService::DoomEntry(entry);
1559 NS_ASSERTION(NS_SUCCEEDED(rv), "DoomEntry() failed.");
1560 return NS_ERROR_ABORT;
1563 mDeltaCounter = 0; // reset counter
1566 return NS_OK;
1569 nsresult
1570 nsOfflineCacheDevice::Visit(nsICacheVisitor *visitor)
1572 NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1574 // called to enumerate the offline cache.
1576 nsCOMPtr<nsICacheDeviceInfo> deviceInfo =
1577 new nsOfflineCacheDeviceInfo(this);
1579 PRBool keepGoing;
1580 nsresult rv = visitor->VisitDevice(OFFLINE_CACHE_DEVICE_ID, deviceInfo,
1581 &keepGoing);
1582 if (NS_FAILED(rv))
1583 return rv;
1585 if (!keepGoing)
1586 return NS_OK;
1588 // SELECT * from moz_cache;
1590 nsOfflineCacheRecord rec;
1591 nsRefPtr<nsOfflineCacheEntryInfo> info = new nsOfflineCacheEntryInfo;
1592 if (!info)
1593 return NS_ERROR_OUT_OF_MEMORY;
1594 info->mRec = &rec;
1596 // XXX may want to list columns explicitly
1597 nsCOMPtr<mozIStorageStatement> statement;
1598 rv = mDB->CreateStatement(
1599 NS_LITERAL_CSTRING("SELECT * FROM moz_cache;"),
1600 getter_AddRefs(statement));
1601 NS_ENSURE_SUCCESS(rv, rv);
1603 PRBool hasRows;
1604 for (;;)
1606 rv = statement->ExecuteStep(&hasRows);
1607 if (NS_FAILED(rv) || !hasRows)
1608 break;
1610 statement->GetSharedUTF8String(0, NULL, &rec.clientID);
1611 statement->GetSharedUTF8String(1, NULL, &rec.key);
1612 statement->GetSharedBlob(2, &rec.metaDataLen,
1613 (const PRUint8 **) &rec.metaData);
1614 rec.generation = statement->AsInt32(3);
1615 rec.flags = statement->AsInt32(4);
1616 rec.dataSize = statement->AsInt32(5);
1617 rec.fetchCount = statement->AsInt32(6);
1618 rec.lastFetched = statement->AsInt64(7);
1619 rec.lastModified = statement->AsInt64(8);
1620 rec.expirationTime = statement->AsInt64(9);
1622 PRBool keepGoing;
1623 rv = visitor->VisitEntry(OFFLINE_CACHE_DEVICE_ID, info, &keepGoing);
1624 if (NS_FAILED(rv) || !keepGoing)
1625 break;
1628 info->mRec = nsnull;
1629 return NS_OK;
1632 nsresult
1633 nsOfflineCacheDevice::EvictEntries(const char *clientID)
1635 LOG(("nsOfflineCacheDevice::EvictEntries [cid=%s]\n",
1636 clientID ? clientID : ""));
1638 // called to evict all entries matching the given clientID.
1640 // need trigger to fire user defined function after a row is deleted
1641 // so we can delete the corresponding data file.
1642 EvictionObserver evictionObserver(mDB, mEvictionFunction);
1644 nsCOMPtr<mozIStorageStatement> statement;
1645 nsresult rv;
1646 if (clientID)
1648 rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=? AND Flags = 0;"),
1649 getter_AddRefs(statement));
1650 NS_ENSURE_SUCCESS(rv, rv);
1652 rv = statement->BindUTF8StringParameter(0, nsDependentCString(clientID));
1653 NS_ENSURE_SUCCESS(rv, rv);
1655 else
1657 rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE Flags = 0;"),
1658 getter_AddRefs(statement));
1659 NS_ENSURE_SUCCESS(rv, rv);
1662 rv = statement->Execute();
1663 NS_ENSURE_SUCCESS(rv, rv);
1665 evictionObserver.Apply();
1667 statement = nsnull;
1668 // Also evict any namespaces associated with this clientID.
1669 if (clientID)
1671 rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces WHERE ClientID=?"),
1672 getter_AddRefs(statement));
1673 NS_ENSURE_SUCCESS(rv, rv);
1675 rv = statement->BindUTF8StringParameter(0, nsDependentCString(clientID));
1676 NS_ENSURE_SUCCESS(rv, rv);
1678 else
1680 rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces;"),
1681 getter_AddRefs(statement));
1682 NS_ENSURE_SUCCESS(rv, rv);
1685 rv = statement->Execute();
1686 NS_ENSURE_SUCCESS(rv, rv);
1688 return NS_OK;
1691 nsresult
1692 nsOfflineCacheDevice::MarkEntry(const nsCString &clientID,
1693 const nsACString &key,
1694 PRUint32 typeBits)
1696 LOG(("nsOfflineCacheDevice::MarkEntry [cid=%s, key=%s, typeBits=%d]\n",
1697 clientID.get(), PromiseFlatCString(key).get(), typeBits));
1699 AutoResetStatement statement(mStatement_MarkEntry);
1700 nsresult rv = statement->BindInt32Parameter(0, typeBits);
1701 NS_ENSURE_SUCCESS(rv, rv);
1702 rv = statement->BindUTF8StringParameter(1, clientID);
1703 NS_ENSURE_SUCCESS(rv, rv);
1704 rv = statement->BindUTF8StringParameter(2, key);
1705 NS_ENSURE_SUCCESS(rv, rv);
1707 rv = statement->Execute();
1708 NS_ENSURE_SUCCESS(rv, rv);
1710 return NS_OK;
1713 nsresult
1714 nsOfflineCacheDevice::UnmarkEntry(const nsCString &clientID,
1715 const nsACString &key,
1716 PRUint32 typeBits)
1718 LOG(("nsOfflineCacheDevice::UnmarkEntry [cid=%s, key=%s, typeBits=%d]\n",
1719 clientID.get(), PromiseFlatCString(key).get(), typeBits));
1721 AutoResetStatement statement(mStatement_UnmarkEntry);
1722 nsresult rv = statement->BindInt32Parameter(0, typeBits);
1723 NS_ENSURE_SUCCESS(rv, rv);
1724 rv = statement->BindUTF8StringParameter(1, clientID);
1725 NS_ENSURE_SUCCESS(rv, rv);
1726 rv = statement->BindUTF8StringParameter(2, key);
1727 NS_ENSURE_SUCCESS(rv, rv);
1729 rv = statement->Execute();
1730 NS_ENSURE_SUCCESS(rv, rv);
1732 // Remove the entry if it is now empty.
1734 EvictionObserver evictionObserver(mDB, mEvictionFunction);
1736 AutoResetStatement cleanupStatement(mStatement_CleanupUnmarked);
1737 rv = cleanupStatement->BindUTF8StringParameter(0, clientID);
1738 NS_ENSURE_SUCCESS(rv, rv);
1739 rv = cleanupStatement->BindUTF8StringParameter(1, key);
1740 NS_ENSURE_SUCCESS(rv, rv);
1742 rv = cleanupStatement->Execute();
1743 NS_ENSURE_SUCCESS(rv, rv);
1745 evictionObserver.Apply();
1747 return NS_OK;
1750 nsresult
1751 nsOfflineCacheDevice::GetMatchingNamespace(const nsCString &clientID,
1752 const nsACString &key,
1753 nsIApplicationCacheNamespace **out)
1755 LOG(("nsOfflineCacheDevice::GetMatchingNamespace [cid=%s, key=%s]\n",
1756 clientID.get(), PromiseFlatCString(key).get()));
1758 nsresult rv;
1760 AutoResetStatement statement(mStatement_FindNamespaceEntry);
1762 rv = statement->BindUTF8StringParameter(0, clientID);
1763 NS_ENSURE_SUCCESS(rv, rv);
1764 rv = statement->BindUTF8StringParameter(1, key);
1765 NS_ENSURE_SUCCESS(rv, rv);
1767 PRBool hasRows;
1768 rv = statement->ExecuteStep(&hasRows);
1769 NS_ENSURE_SUCCESS(rv, rv);
1771 *out = nsnull;
1773 while (hasRows)
1775 nsCString namespaceSpec;
1776 rv = statement->GetUTF8String(0, namespaceSpec);
1777 NS_ENSURE_SUCCESS(rv, rv);
1779 nsCString data;
1780 rv = statement->GetUTF8String(1, data);
1781 NS_ENSURE_SUCCESS(rv, rv);
1783 PRInt32 itemType;
1784 rv = statement->GetInt32(2, &itemType);
1785 NS_ENSURE_SUCCESS(rv, rv);
1787 // XXX: There is currently a proposal on the table to extend matching
1788 // semantics for bypass entries to be a namespace (possibly with extra
1789 // filter data). When/if that happens, this logic will need to change.
1790 if ((itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS) &&
1791 (namespaceSpec != key)) {
1792 rv = statement->ExecuteStep(&hasRows);
1793 NS_ENSURE_SUCCESS(rv, rv);
1795 continue;
1798 nsCOMPtr<nsIApplicationCacheNamespace> ns =
1799 new nsApplicationCacheNamespace();
1800 if (!ns)
1801 return NS_ERROR_OUT_OF_MEMORY;
1803 rv = ns->Init(itemType, namespaceSpec, data);
1804 NS_ENSURE_SUCCESS(rv, rv);
1806 ns.swap(*out);
1807 return NS_OK;
1810 return NS_OK;
1813 nsresult
1814 nsOfflineCacheDevice::CacheOpportunistically(const nsCString &clientID,
1815 const nsACString &key)
1817 // XXX: We should also be propagating this cache entry to other matching
1818 // caches. See bug 444807.
1820 return MarkEntry(clientID, key, nsIApplicationCache::ITEM_OPPORTUNISTIC);
1823 nsresult
1824 nsOfflineCacheDevice::GetTypes(const nsCString &clientID,
1825 const nsACString &key,
1826 PRUint32 *typeBits)
1828 LOG(("nsOfflineCacheDevice::GetTypes [cid=%s, key=%s]\n",
1829 clientID.get(), PromiseFlatCString(key).get()));
1831 AutoResetStatement statement(mStatement_GetTypes);
1832 nsresult rv = statement->BindUTF8StringParameter(0, clientID);
1833 NS_ENSURE_SUCCESS(rv, rv);
1834 rv = statement->BindUTF8StringParameter(1, key);
1835 NS_ENSURE_SUCCESS(rv, rv);
1837 PRBool hasRows;
1838 rv = statement->ExecuteStep(&hasRows);
1839 NS_ENSURE_SUCCESS(rv, rv);
1841 if (!hasRows)
1842 return NS_ERROR_CACHE_KEY_NOT_FOUND;
1844 *typeBits = statement->AsInt32(0);
1846 return NS_OK;
1849 nsresult
1850 nsOfflineCacheDevice::GatherEntries(const nsCString &clientID,
1851 PRUint32 typeBits,
1852 PRUint32 *count,
1853 char ***keys)
1855 LOG(("nsOfflineCacheDevice::GatherEntries [cid=%s, typeBits=%X]\n",
1856 clientID.get(), typeBits));
1858 AutoResetStatement statement(mStatement_GatherEntries);
1859 nsresult rv = statement->BindUTF8StringParameter(0, clientID);
1860 NS_ENSURE_SUCCESS(rv, rv);
1862 rv = statement->BindInt32Parameter(1, typeBits);
1863 NS_ENSURE_SUCCESS(rv, rv);
1865 return RunSimpleQuery(mStatement_GatherEntries, 0, count, keys);
1868 nsresult
1869 nsOfflineCacheDevice::AddNamespace(const nsCString &clientID,
1870 nsIApplicationCacheNamespace *ns)
1872 nsCString namespaceSpec;
1873 nsresult rv = ns->GetNamespaceSpec(namespaceSpec);
1874 NS_ENSURE_SUCCESS(rv, rv);
1876 nsCString data;
1877 rv = ns->GetData(data);
1878 NS_ENSURE_SUCCESS(rv, rv);
1880 PRUint32 itemType;
1881 rv = ns->GetItemType(&itemType);
1882 NS_ENSURE_SUCCESS(rv, rv);
1884 LOG(("nsOfflineCacheDevice::AddNamespace [cid=%s, ns=%s, type=%d]",
1885 PromiseFlatCString(clientID).get(),
1886 namespaceSpec.get(), data.get(), itemType));
1888 AutoResetStatement statement(mStatement_InsertNamespaceEntry);
1890 rv = statement->BindUTF8StringParameter(0, clientID);
1891 NS_ENSURE_SUCCESS(rv, rv);
1893 rv = statement->BindUTF8StringParameter(1, namespaceSpec);
1894 NS_ENSURE_SUCCESS(rv, rv);
1896 rv = statement->BindUTF8StringParameter(2, data);
1897 NS_ENSURE_SUCCESS(rv, rv);
1899 rv = statement->BindInt32Parameter(3, itemType);
1900 NS_ENSURE_SUCCESS(rv, rv);
1902 rv = statement->Execute();
1903 NS_ENSURE_SUCCESS(rv, rv);
1905 return NS_OK;
1908 nsresult
1909 nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement * statement,
1910 PRUint32 resultIndex,
1911 PRUint32 * count,
1912 char *** values)
1914 PRBool hasRows;
1915 nsresult rv = statement->ExecuteStep(&hasRows);
1916 NS_ENSURE_SUCCESS(rv, rv);
1918 nsTArray<nsCString> valArray;
1919 while (hasRows)
1921 PRUint32 length;
1922 valArray.AppendElement(
1923 nsDependentCString(statement->AsSharedUTF8String(resultIndex, &length)));
1925 rv = statement->ExecuteStep(&hasRows);
1926 NS_ENSURE_SUCCESS(rv, rv);
1929 *count = valArray.Length();
1930 char **ret = static_cast<char **>(NS_Alloc(*count * sizeof(char*)));
1931 if (!ret) return NS_ERROR_OUT_OF_MEMORY;
1933 for (PRUint32 i = 0; i < *count; i++) {
1934 ret[i] = NS_strdup(valArray[i].get());
1935 if (!ret[i]) {
1936 NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret);
1937 return NS_ERROR_OUT_OF_MEMORY;
1941 *values = ret;
1943 return NS_OK;
1946 NS_IMETHODIMP
1947 nsOfflineCacheDevice::CreateApplicationCache(const nsACString &group,
1948 nsIApplicationCache **out)
1950 *out = nsnull;
1952 nsCString clientID;
1953 // Some characters are special in the clientID. Escape the groupID
1954 // before putting it in to the client key.
1955 if (!NS_Escape(nsCString(group), clientID, url_Path)) {
1956 return NS_ERROR_OUT_OF_MEMORY;
1959 PRTime now = PR_Now();
1961 // Include the timestamp to guarantee uniqueness across runs, and
1962 // the gNextTemporaryClientID for uniqueness within a second.
1963 clientID.Append(nsPrintfCString(64, "|%016lld|%d",
1964 now / PR_USEC_PER_SEC,
1965 gNextTemporaryClientID++));
1967 nsCOMPtr<nsIApplicationCache> cache = new nsApplicationCache(this,
1968 group,
1969 clientID);
1970 if (!cache)
1971 return NS_ERROR_OUT_OF_MEMORY;
1973 nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(cache);
1974 if (!weak)
1975 return NS_ERROR_OUT_OF_MEMORY;
1977 mCaches.Put(clientID, weak);
1979 cache.swap(*out);
1981 return NS_OK;
1984 NS_IMETHODIMP
1985 nsOfflineCacheDevice::GetApplicationCache(const nsACString &clientID,
1986 nsIApplicationCache **out)
1988 *out = nsnull;
1990 nsCOMPtr<nsIApplicationCache> cache;
1992 nsWeakPtr weak;
1993 if (mCaches.Get(clientID, getter_AddRefs(weak)))
1994 cache = do_QueryReferent(weak);
1996 if (!cache)
1998 nsCString group;
1999 nsresult rv = GetGroupForCache(clientID, group);
2000 NS_ENSURE_SUCCESS(rv, rv);
2002 if (group.IsEmpty()) {
2003 return NS_OK;
2006 cache = new nsApplicationCache(this, group, clientID);
2007 weak = do_GetWeakReference(cache);
2008 if (!weak)
2009 return NS_ERROR_OUT_OF_MEMORY;
2011 mCaches.Put(clientID, weak);
2014 cache.swap(*out);
2016 return NS_OK;
2019 NS_IMETHODIMP
2020 nsOfflineCacheDevice::GetActiveCache(const nsACString &group,
2021 nsIApplicationCache **out)
2023 *out = nsnull;
2025 nsCString *clientID;
2026 if (mActiveCachesByGroup.Get(group, &clientID))
2027 return GetApplicationCache(*clientID, out);
2029 return NS_OK;
2032 PRBool
2033 nsOfflineCacheDevice::CanUseCache(nsIURI *keyURI, const nsCString &clientID)
2035 if (mActiveCaches.Contains(clientID)) {
2036 nsCAutoString groupID;
2037 nsresult rv = GetGroupForCache(clientID, groupID);
2038 NS_ENSURE_SUCCESS(rv, PR_FALSE);
2040 nsCOMPtr<nsIURI> groupURI;
2041 rv = NS_NewURI(getter_AddRefs(groupURI), groupID);
2042 if (NS_SUCCEEDED(rv)) {
2043 // When we are choosing an initial cache to load the top
2044 // level document from, the URL of that document must have
2045 // the same origin as the manifest, according to the spec.
2046 // The following check is here because explicit, fallback
2047 // and dynamic entries might have origin different from the
2048 // manifest origin.
2049 if (NS_SecurityCompareURIs(keyURI, groupURI,
2050 GetStrictFileOriginPolicy()))
2051 return PR_TRUE;
2055 return PR_FALSE;
2059 NS_IMETHODIMP
2060 nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key,
2061 nsIApplicationCache **out)
2063 *out = nsnull;
2065 nsCOMPtr<nsIURI> keyURI;
2066 nsresult rv = NS_NewURI(getter_AddRefs(keyURI), key);
2067 NS_ENSURE_SUCCESS(rv, rv);
2069 // First try to find a matching cache entry.
2070 AutoResetStatement statement(mStatement_FindClient);
2071 rv = statement->BindUTF8StringParameter(0, key);
2072 NS_ENSURE_SUCCESS(rv, rv);
2074 PRBool hasRows;
2075 rv = statement->ExecuteStep(&hasRows);
2076 NS_ENSURE_SUCCESS(rv, rv);
2078 while (hasRows) {
2079 PRInt32 itemType;
2080 rv = statement->GetInt32(1, &itemType);
2081 NS_ENSURE_SUCCESS(rv, rv);
2083 if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) {
2084 nsCAutoString clientID;
2085 rv = statement->GetUTF8String(0, clientID);
2086 NS_ENSURE_SUCCESS(rv, rv);
2088 if (CanUseCache(keyURI, clientID)) {
2089 return GetApplicationCache(clientID, out);
2093 rv = statement->ExecuteStep(&hasRows);
2094 NS_ENSURE_SUCCESS(rv, rv);
2097 // OK, we didn't find an exact match. Search for a client with a
2098 // matching namespace.
2100 AutoResetStatement nsstatement(mStatement_FindClientByNamespace);
2102 rv = nsstatement->BindUTF8StringParameter(0, key);
2103 NS_ENSURE_SUCCESS(rv, rv);
2105 rv = nsstatement->ExecuteStep(&hasRows);
2106 NS_ENSURE_SUCCESS(rv, rv);
2108 while (hasRows)
2110 PRInt32 itemType;
2111 rv = nsstatement->GetInt32(1, &itemType);
2112 NS_ENSURE_SUCCESS(rv, rv);
2114 // Don't associate with a cache based solely on a whitelist entry
2115 if (!(itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) {
2116 nsCAutoString clientID;
2117 rv = nsstatement->GetUTF8String(0, clientID);
2118 NS_ENSURE_SUCCESS(rv, rv);
2120 if (CanUseCache(keyURI, clientID)) {
2121 return GetApplicationCache(clientID, out);
2125 rv = nsstatement->ExecuteStep(&hasRows);
2126 NS_ENSURE_SUCCESS(rv, rv);
2129 return NS_OK;
2132 NS_IMETHODIMP
2133 nsOfflineCacheDevice::CacheOpportunistically(nsIApplicationCache* cache,
2134 const nsACString &key)
2136 NS_ENSURE_ARG_POINTER(cache);
2138 nsresult rv;
2140 nsCAutoString clientID;
2141 rv = cache->GetClientID(clientID);
2142 NS_ENSURE_SUCCESS(rv, rv);
2144 return CacheOpportunistically(clientID, key);
2147 nsresult
2148 nsOfflineCacheDevice::ActivateCache(const nsCSubstring &group,
2149 const nsCSubstring &clientID)
2151 AutoResetStatement statement(mStatement_ActivateClient);
2152 nsresult rv = statement->BindUTF8StringParameter(0, group);
2153 NS_ENSURE_SUCCESS(rv, rv);
2154 rv = statement->BindUTF8StringParameter(1, clientID);
2155 NS_ENSURE_SUCCESS(rv, rv);
2156 rv = statement->BindInt32Parameter(2, SecondsFromPRTime(PR_Now()));
2157 NS_ENSURE_SUCCESS(rv, rv);
2159 rv = statement->Execute();
2160 NS_ENSURE_SUCCESS(rv, rv);
2162 nsCString *active;
2163 if (mActiveCachesByGroup.Get(group, &active))
2165 mActiveCaches.Remove(*active);
2166 mActiveCachesByGroup.Remove(group);
2167 active = nsnull;
2170 if (!clientID.IsEmpty())
2172 mActiveCaches.Put(clientID);
2173 mActiveCachesByGroup.Put(group, new nsCString(clientID));
2176 return NS_OK;
2179 PRBool
2180 nsOfflineCacheDevice::IsActiveCache(const nsCSubstring &group,
2181 const nsCSubstring &clientID)
2183 nsCString *active = nsnull;
2184 return mActiveCachesByGroup.Get(group, &active) && *active == clientID;
2187 nsresult
2188 nsOfflineCacheDevice::DeactivateGroup(const nsCSubstring &group)
2190 nsCString *active = nsnull;
2192 AutoResetStatement statement(mStatement_DeactivateGroup);
2193 nsresult rv = statement->BindUTF8StringParameter(
2194 0, group);
2195 NS_ENSURE_SUCCESS(rv, rv);
2197 rv = statement->Execute();
2198 NS_ENSURE_SUCCESS(rv, rv);
2200 if (mActiveCachesByGroup.Get(group, &active))
2202 mActiveCaches.Remove(*active);
2203 mActiveCachesByGroup.Remove(group);
2204 active = nsnull;
2207 return NS_OK;
2210 nsresult
2211 nsOfflineCacheDevice::GetGroupForCache(const nsACString &clientID,
2212 nsCString &out)
2214 out.Assign(clientID);
2215 out.Truncate(out.FindChar('|'));
2216 NS_UnescapeURL(out);
2218 return NS_OK;
2222 * Preference accessors
2225 void
2226 nsOfflineCacheDevice::SetCacheParentDirectory(nsILocalFile *parentDir)
2228 if (Initialized())
2230 NS_ERROR("cannot switch cache directory once initialized");
2231 return;
2234 if (!parentDir)
2236 mCacheDirectory = nsnull;
2237 return;
2240 // ensure parent directory exists
2241 nsresult rv = EnsureDir(parentDir);
2242 if (NS_FAILED(rv))
2244 NS_WARNING("unable to create parent directory");
2245 return;
2248 // cache dir may not exist, but that's ok
2249 nsCOMPtr<nsIFile> dir;
2250 rv = parentDir->Clone(getter_AddRefs(dir));
2251 if (NS_FAILED(rv))
2252 return;
2253 rv = dir->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
2254 if (NS_FAILED(rv))
2255 return;
2257 mCacheDirectory = do_QueryInterface(dir);
2260 void
2261 nsOfflineCacheDevice::SetCapacity(PRUint32 capacity)
2263 mCacheCapacity = capacity * 1024;