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
4 : *
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/
9 : *
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.
14 : *
15 : * The Original Code is Mozilla.
16 : *
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.
20 : *
21 : * Contributor(s):
22 : * Darin Fisher <darin@meer.net>
23 : * Dave Camp <dcamp@mozilla.com>
24 : * Honza Bambas <honzab@firemni.cz>
25 : *
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.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "mozilla/Util.h"
41 :
42 : #include "nsCache.h"
43 : #include "nsDiskCache.h"
44 : #include "nsDiskCacheDeviceSQL.h"
45 : #include "nsCacheService.h"
46 : #include "nsApplicationCache.h"
47 :
48 : #include "nsNetCID.h"
49 : #include "nsNetUtil.h"
50 : #include "nsAutoPtr.h"
51 : #include "nsEscape.h"
52 : #include "nsIPrefBranch.h"
53 : #include "nsIPrefService.h"
54 : #include "nsString.h"
55 : #include "nsPrintfCString.h"
56 : #include "nsCRT.h"
57 : #include "nsArrayUtils.h"
58 : #include "nsIArray.h"
59 : #include "nsIVariant.h"
60 : #include "nsThreadUtils.h"
61 :
62 : #include "mozIStorageService.h"
63 : #include "mozIStorageStatement.h"
64 : #include "mozIStorageFunction.h"
65 : #include "mozStorageHelper.h"
66 :
67 : #include "nsICacheVisitor.h"
68 : #include "nsISeekableStream.h"
69 :
70 : #include "mozilla/FunctionTimer.h"
71 : #include "mozilla/Telemetry.h"
72 :
73 : using namespace mozilla;
74 :
75 : static const char OFFLINE_CACHE_DEVICE_ID[] = { "offline" };
76 : static NS_DEFINE_CID(kCacheServiceCID, NS_CACHESERVICE_CID);
77 :
78 : #define LOG(args) CACHE_LOG_DEBUG(args)
79 :
80 : static PRUint32 gNextTemporaryClientID = 0;
81 :
82 : /*****************************************************************************
83 : * helpers
84 : */
85 :
86 : static nsresult
87 30 : EnsureDir(nsIFile *dir)
88 : {
89 : bool exists;
90 30 : nsresult rv = dir->Exists(&exists);
91 30 : if (NS_SUCCEEDED(rv) && !exists)
92 15 : rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700);
93 30 : return rv;
94 : }
95 :
96 : static bool
97 186 : DecomposeCacheEntryKey(const nsCString *fullKey,
98 : const char **cid,
99 : const char **key,
100 : nsCString &buf)
101 : {
102 186 : buf = *fullKey;
103 :
104 186 : PRInt32 colon = buf.FindChar(':');
105 186 : if (colon == kNotFound)
106 : {
107 0 : NS_ERROR("Invalid key");
108 0 : return false;
109 : }
110 186 : buf.SetCharAt('\0', colon);
111 :
112 186 : *cid = buf.get();
113 186 : *key = buf.get() + colon + 1;
114 :
115 186 : return true;
116 : }
117 :
118 : class AutoResetStatement
119 : {
120 : public:
121 297 : AutoResetStatement(mozIStorageStatement *s)
122 297 : : mStatement(s) {}
123 297 : ~AutoResetStatement() { mStatement->Reset(); }
124 1429 : mozIStorageStatement *operator->() { return mStatement; }
125 : private:
126 : mozIStorageStatement *mStatement;
127 : };
128 :
129 : class EvictionObserver
130 : {
131 : public:
132 16 : EvictionObserver(mozIStorageConnection *db,
133 : nsOfflineCacheEvictionFunction *evictionFunction)
134 16 : : mDB(db), mEvictionFunction(evictionFunction)
135 : {
136 : mDB->ExecuteSimpleSQL(
137 16 : NS_LITERAL_CSTRING("CREATE TEMP TRIGGER cache_on_delete AFTER DELETE"
138 : " ON moz_cache FOR EACH ROW BEGIN SELECT"
139 : " cache_eviction_observer("
140 : " OLD.key, OLD.generation);"
141 16 : " END;"));
142 16 : mEvictionFunction->Reset();
143 16 : }
144 :
145 16 : ~EvictionObserver()
146 16 : {
147 : mDB->ExecuteSimpleSQL(
148 16 : NS_LITERAL_CSTRING("DROP TRIGGER cache_on_delete;"));
149 16 : mEvictionFunction->Reset();
150 16 : }
151 :
152 16 : void Apply() { return mEvictionFunction->Apply(); }
153 :
154 : private:
155 : mozIStorageConnection *mDB;
156 : nsRefPtr<nsOfflineCacheEvictionFunction> mEvictionFunction;
157 : };
158 :
159 : #define DCACHE_HASH_MAX LL_MAXINT
160 : #define DCACHE_HASH_BITS 64
161 :
162 : /**
163 : * nsOfflineCache::Hash(const char * key)
164 : *
165 : * This algorithm of this method implies nsOfflineCacheRecords will be stored
166 : * in a certain order on disk. If the algorithm changes, existing cache
167 : * map files may become invalid, and therefore the kCurrentVersion needs
168 : * to be revised.
169 : */
170 : static PRUint64
171 32 : DCacheHash(const char * key)
172 : {
173 : // initval 0x7416f295 was chosen randomly
174 32 : return (PRUint64(nsDiskCache::Hash(key, 0)) << 32) | nsDiskCache::Hash(key, 0x7416f295);
175 : }
176 :
177 : /******************************************************************************
178 : * nsOfflineCacheEvictionFunction
179 : */
180 :
181 167 : NS_IMPL_THREADSAFE_ISUPPORTS1(nsOfflineCacheEvictionFunction, mozIStorageFunction)
182 :
183 : // helper function for directly exposing the same data file binding
184 : // path algorithm used in nsOfflineCacheBinding::Create
185 : static nsresult
186 1 : GetCacheDataFile(nsIFile *cacheDir, const char *key,
187 : int generation, nsCOMPtr<nsIFile> &file)
188 : {
189 1 : cacheDir->Clone(getter_AddRefs(file));
190 1 : if (!file)
191 0 : return NS_ERROR_OUT_OF_MEMORY;
192 :
193 1 : PRUint64 hash = DCacheHash(key);
194 :
195 1 : PRUint32 dir1 = (PRUint32) (hash & 0x0F);
196 1 : PRUint32 dir2 = (PRUint32)((hash & 0xF0) >> 4);
197 :
198 1 : hash >>= 8;
199 :
200 1 : file->AppendNative(nsPrintfCString("%X", dir1));
201 1 : file->AppendNative(nsPrintfCString("%X", dir2));
202 :
203 : char leaf[64];
204 1 : PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
205 1 : return file->AppendNative(nsDependentCString(leaf));
206 : }
207 :
208 : NS_IMETHODIMP
209 1 : nsOfflineCacheEvictionFunction::OnFunctionCall(mozIStorageValueArray *values, nsIVariant **_retval)
210 : {
211 1 : LOG(("nsOfflineCacheEvictionFunction::OnFunctionCall\n"));
212 :
213 1 : *_retval = nsnull;
214 :
215 : PRUint32 numEntries;
216 1 : nsresult rv = values->GetNumEntries(&numEntries);
217 1 : NS_ENSURE_SUCCESS(rv, rv);
218 1 : NS_ASSERTION(numEntries == 2, "unexpected number of arguments");
219 :
220 : PRUint32 valueLen;
221 1 : const char *key = values->AsSharedUTF8String(0, &valueLen);
222 1 : int generation = values->AsInt32(1);
223 :
224 2 : nsCOMPtr<nsIFile> file;
225 1 : rv = GetCacheDataFile(mDevice->CacheDirectory(), key,
226 1 : generation, file);
227 1 : if (NS_FAILED(rv))
228 : {
229 0 : LOG(("GetCacheDataFile [key=%s generation=%d] failed [rv=%x]!\n",
230 : key, generation, rv));
231 0 : return rv;
232 : }
233 :
234 1 : mItems.AppendObject(file);
235 :
236 1 : return NS_OK;
237 : }
238 :
239 : void
240 16 : nsOfflineCacheEvictionFunction::Apply()
241 : {
242 16 : LOG(("nsOfflineCacheEvictionFunction::Apply\n"));
243 :
244 17 : for (PRInt32 i = 0; i < mItems.Count(); i++) {
245 : #if defined(PR_LOGGING)
246 2 : nsCAutoString path;
247 1 : mItems[i]->GetNativePath(path);
248 1 : LOG((" removing %s\n", path.get()));
249 : #endif
250 :
251 1 : mItems[i]->Remove(false);
252 : }
253 :
254 16 : Reset();
255 16 : }
256 :
257 : /******************************************************************************
258 : * nsOfflineCacheDeviceInfo
259 : */
260 :
261 : class nsOfflineCacheDeviceInfo : public nsICacheDeviceInfo
262 : {
263 : public:
264 : NS_DECL_ISUPPORTS
265 : NS_DECL_NSICACHEDEVICEINFO
266 :
267 4 : nsOfflineCacheDeviceInfo(nsOfflineCacheDevice* device)
268 4 : : mDevice(device)
269 4 : {}
270 :
271 : private:
272 : nsOfflineCacheDevice* mDevice;
273 : };
274 :
275 72 : NS_IMPL_ISUPPORTS1(nsOfflineCacheDeviceInfo, nsICacheDeviceInfo)
276 :
277 : NS_IMETHODIMP
278 0 : nsOfflineCacheDeviceInfo::GetDescription(char **aDescription)
279 : {
280 0 : *aDescription = NS_strdup("Offline cache device");
281 0 : return *aDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
282 : }
283 :
284 : NS_IMETHODIMP
285 0 : nsOfflineCacheDeviceInfo::GetUsageReport(char ** usageReport)
286 : {
287 0 : nsCAutoString buffer;
288 : buffer.AssignLiteral(" <tr>\n"
289 : " <th>Cache Directory:</th>\n"
290 0 : " <td>");
291 0 : nsILocalFile *cacheDir = mDevice->CacheDirectory();
292 0 : if (!cacheDir)
293 0 : return NS_OK;
294 :
295 0 : nsAutoString path;
296 0 : nsresult rv = cacheDir->GetPath(path);
297 0 : if (NS_SUCCEEDED(rv))
298 0 : AppendUTF16toUTF8(path, buffer);
299 : else
300 0 : buffer.AppendLiteral("directory unavailable");
301 :
302 : buffer.AppendLiteral("</td>\n"
303 0 : " </tr>\n");
304 :
305 0 : *usageReport = ToNewCString(buffer);
306 0 : if (!*usageReport)
307 0 : return NS_ERROR_OUT_OF_MEMORY;
308 :
309 0 : return NS_OK;
310 : }
311 :
312 : NS_IMETHODIMP
313 0 : nsOfflineCacheDeviceInfo::GetEntryCount(PRUint32 *aEntryCount)
314 : {
315 0 : *aEntryCount = mDevice->EntryCount();
316 0 : return NS_OK;
317 : }
318 :
319 : NS_IMETHODIMP
320 0 : nsOfflineCacheDeviceInfo::GetTotalSize(PRUint32 *aTotalSize)
321 : {
322 0 : *aTotalSize = mDevice->CacheSize();
323 0 : return NS_OK;
324 : }
325 :
326 : NS_IMETHODIMP
327 0 : nsOfflineCacheDeviceInfo::GetMaximumSize(PRUint32 *aMaximumSize)
328 : {
329 0 : *aMaximumSize = mDevice->CacheCapacity();
330 0 : return NS_OK;
331 : }
332 :
333 : /******************************************************************************
334 : * nsOfflineCacheBinding
335 : */
336 :
337 : class nsOfflineCacheBinding : public nsISupports
338 62 : {
339 : public:
340 : NS_DECL_ISUPPORTS
341 :
342 : static nsOfflineCacheBinding *
343 : Create(nsIFile *cacheDir, const nsCString *key, int generation);
344 :
345 : nsCOMPtr<nsIFile> mDataFile;
346 : int mGeneration;
347 : };
348 :
349 143 : NS_IMPL_THREADSAFE_ISUPPORTS0(nsOfflineCacheBinding)
350 :
351 : nsOfflineCacheBinding *
352 31 : nsOfflineCacheBinding::Create(nsIFile *cacheDir,
353 : const nsCString *fullKey,
354 : int generation)
355 : {
356 62 : nsCOMPtr<nsIFile> file;
357 31 : cacheDir->Clone(getter_AddRefs(file));
358 31 : if (!file)
359 0 : return nsnull;
360 :
361 62 : nsCAutoString keyBuf;
362 : const char *cid, *key;
363 31 : if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
364 0 : return nsnull;
365 :
366 31 : PRUint64 hash = DCacheHash(key);
367 :
368 31 : PRUint32 dir1 = (PRUint32) (hash & 0x0F);
369 31 : PRUint32 dir2 = (PRUint32)((hash & 0xF0) >> 4);
370 :
371 31 : hash >>= 8;
372 :
373 : // XXX we might want to create these directories up-front
374 :
375 31 : file->AppendNative(nsPrintfCString("%X", dir1));
376 31 : file->Create(nsIFile::DIRECTORY_TYPE, 00700);
377 :
378 31 : file->AppendNative(nsPrintfCString("%X", dir2));
379 31 : file->Create(nsIFile::DIRECTORY_TYPE, 00700);
380 :
381 : nsresult rv;
382 : char leaf[64];
383 :
384 31 : if (generation == -1)
385 : {
386 25 : file->AppendNative(NS_LITERAL_CSTRING("placeholder"));
387 :
388 25 : for (generation = 0; ; ++generation)
389 : {
390 25 : PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
391 :
392 25 : rv = file->SetNativeLeafName(nsDependentCString(leaf));
393 25 : if (NS_FAILED(rv))
394 0 : return nsnull;
395 25 : rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
396 25 : if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)
397 0 : return nsnull;
398 25 : if (NS_SUCCEEDED(rv))
399 : break;
400 : }
401 : }
402 : else
403 : {
404 6 : PR_snprintf(leaf, sizeof(leaf), "%014llX-%X", hash, generation);
405 6 : rv = file->AppendNative(nsDependentCString(leaf));
406 6 : if (NS_FAILED(rv))
407 0 : return nsnull;
408 : }
409 :
410 31 : nsOfflineCacheBinding *binding = new nsOfflineCacheBinding;
411 31 : if (!binding)
412 0 : return nsnull;
413 :
414 31 : binding->mDataFile.swap(file);
415 31 : binding->mGeneration = generation;
416 31 : return binding;
417 : }
418 :
419 : /******************************************************************************
420 : * nsOfflineCacheRecord
421 : */
422 :
423 : struct nsOfflineCacheRecord
424 : {
425 : const char *clientID;
426 : const char *key;
427 : const PRUint8 *metaData;
428 : PRUint32 metaDataLen;
429 : PRInt32 generation;
430 : PRInt32 flags;
431 : PRInt32 dataSize;
432 : PRInt32 fetchCount;
433 : PRInt64 lastFetched;
434 : PRInt64 lastModified;
435 : PRInt64 expirationTime;
436 : };
437 :
438 : static nsCacheEntry *
439 6 : CreateCacheEntry(nsOfflineCacheDevice *device,
440 : const nsCString *fullKey,
441 : const nsOfflineCacheRecord &rec)
442 : {
443 6 : if (rec.flags != 0)
444 : {
445 0 : LOG(("refusing to load busy entry\n"));
446 0 : return nsnull;
447 : }
448 :
449 : nsCacheEntry *entry;
450 :
451 : nsresult rv = nsCacheEntry::Create(fullKey->get(), // XXX enable sharing
452 : nsICache::STREAM_BASED,
453 : nsICache::STORE_OFFLINE,
454 6 : device, &entry);
455 6 : if (NS_FAILED(rv))
456 0 : return nsnull;
457 :
458 6 : entry->SetFetchCount((PRUint32) rec.fetchCount);
459 6 : entry->SetLastFetched(SecondsFromPRTime(rec.lastFetched));
460 6 : entry->SetLastModified(SecondsFromPRTime(rec.lastModified));
461 6 : entry->SetExpirationTime(SecondsFromPRTime(rec.expirationTime));
462 6 : entry->SetDataSize((PRUint32) rec.dataSize);
463 :
464 6 : entry->UnflattenMetaData((const char *) rec.metaData, rec.metaDataLen);
465 :
466 : // create a binding object for this entry
467 : nsOfflineCacheBinding *binding =
468 6 : nsOfflineCacheBinding::Create(device->CacheDirectory(),
469 : fullKey,
470 12 : rec.generation);
471 6 : if (!binding)
472 : {
473 0 : delete entry;
474 0 : return nsnull;
475 : }
476 6 : entry->SetData(binding);
477 :
478 6 : return entry;
479 : }
480 :
481 :
482 : /******************************************************************************
483 : * nsOfflineCacheEntryInfo
484 : */
485 :
486 : class nsOfflineCacheEntryInfo : public nsICacheEntryInfo
487 0 : {
488 : public:
489 : NS_DECL_ISUPPORTS
490 : NS_DECL_NSICACHEENTRYINFO
491 :
492 : nsOfflineCacheRecord *mRec;
493 : };
494 :
495 0 : NS_IMPL_ISUPPORTS1(nsOfflineCacheEntryInfo, nsICacheEntryInfo)
496 :
497 : NS_IMETHODIMP
498 0 : nsOfflineCacheEntryInfo::GetClientID(char **result)
499 : {
500 0 : *result = NS_strdup(mRec->clientID);
501 0 : return *result ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
502 : }
503 :
504 : NS_IMETHODIMP
505 0 : nsOfflineCacheEntryInfo::GetDeviceID(char ** deviceID)
506 : {
507 0 : *deviceID = NS_strdup(OFFLINE_CACHE_DEVICE_ID);
508 0 : return *deviceID ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
509 : }
510 :
511 : NS_IMETHODIMP
512 0 : nsOfflineCacheEntryInfo::GetKey(nsACString &clientKey)
513 : {
514 0 : clientKey.Assign(mRec->key);
515 0 : return NS_OK;
516 : }
517 :
518 : NS_IMETHODIMP
519 0 : nsOfflineCacheEntryInfo::GetFetchCount(PRInt32 *aFetchCount)
520 : {
521 0 : *aFetchCount = mRec->fetchCount;
522 0 : return NS_OK;
523 : }
524 :
525 : NS_IMETHODIMP
526 0 : nsOfflineCacheEntryInfo::GetLastFetched(PRUint32 *aLastFetched)
527 : {
528 0 : *aLastFetched = SecondsFromPRTime(mRec->lastFetched);
529 0 : return NS_OK;
530 : }
531 :
532 : NS_IMETHODIMP
533 0 : nsOfflineCacheEntryInfo::GetLastModified(PRUint32 *aLastModified)
534 : {
535 0 : *aLastModified = SecondsFromPRTime(mRec->lastModified);
536 0 : return NS_OK;
537 : }
538 :
539 : NS_IMETHODIMP
540 0 : nsOfflineCacheEntryInfo::GetExpirationTime(PRUint32 *aExpirationTime)
541 : {
542 0 : *aExpirationTime = SecondsFromPRTime(mRec->expirationTime);
543 0 : return NS_OK;
544 : }
545 :
546 : NS_IMETHODIMP
547 0 : nsOfflineCacheEntryInfo::IsStreamBased(bool *aStreamBased)
548 : {
549 0 : *aStreamBased = true;
550 0 : return NS_OK;
551 : }
552 :
553 : NS_IMETHODIMP
554 0 : nsOfflineCacheEntryInfo::GetDataSize(PRUint32 *aDataSize)
555 : {
556 0 : *aDataSize = mRec->dataSize;
557 0 : return NS_OK;
558 : }
559 :
560 :
561 : /******************************************************************************
562 : * nsApplicationCacheNamespace
563 : */
564 :
565 208 : NS_IMPL_ISUPPORTS1(nsApplicationCacheNamespace, nsIApplicationCacheNamespace)
566 :
567 : NS_IMETHODIMP
568 16 : nsApplicationCacheNamespace::Init(PRUint32 itemType,
569 : const nsACString &namespaceSpec,
570 : const nsACString &data)
571 : {
572 16 : mItemType = itemType;
573 16 : mNamespaceSpec = namespaceSpec;
574 16 : mData = data;
575 16 : return NS_OK;
576 : }
577 :
578 : NS_IMETHODIMP
579 16 : nsApplicationCacheNamespace::GetItemType(PRUint32 *out)
580 : {
581 16 : *out = mItemType;
582 16 : return NS_OK;
583 : }
584 :
585 : NS_IMETHODIMP
586 8 : nsApplicationCacheNamespace::GetNamespaceSpec(nsACString &out)
587 : {
588 8 : out = mNamespaceSpec;
589 8 : return NS_OK;
590 : }
591 :
592 : NS_IMETHODIMP
593 16 : nsApplicationCacheNamespace::GetData(nsACString &out)
594 : {
595 16 : out = mData;
596 16 : return NS_OK;
597 : }
598 :
599 : /******************************************************************************
600 : * nsApplicationCache
601 : */
602 :
603 262 : NS_IMPL_ISUPPORTS2(nsApplicationCache,
604 : nsIApplicationCache,
605 : nsISupportsWeakReference)
606 :
607 0 : nsApplicationCache::nsApplicationCache()
608 : : mDevice(nsnull)
609 0 : , mValid(true)
610 : {
611 0 : }
612 :
613 10 : nsApplicationCache::nsApplicationCache(nsOfflineCacheDevice *device,
614 : const nsACString &group,
615 : const nsACString &clientID)
616 : : mDevice(device)
617 : , mGroup(group)
618 : , mClientID(clientID)
619 10 : , mValid(true)
620 : {
621 10 : }
622 :
623 30 : nsApplicationCache::~nsApplicationCache()
624 : {
625 10 : if (!mDevice)
626 : return;
627 :
628 10 : mDevice->mCaches.Remove(mClientID);
629 :
630 : // If this isn't an active cache anymore, it can be destroyed.
631 10 : if (mValid && !mDevice->IsActiveCache(mGroup, mClientID))
632 0 : Discard();
633 40 : }
634 :
635 : void
636 8 : nsApplicationCache::MarkInvalid()
637 : {
638 8 : mValid = false;
639 8 : }
640 :
641 : NS_IMETHODIMP
642 0 : nsApplicationCache::InitAsHandle(const nsACString &groupId,
643 : const nsACString &clientId)
644 : {
645 0 : NS_ENSURE_FALSE(mDevice, NS_ERROR_ALREADY_INITIALIZED);
646 0 : NS_ENSURE_TRUE(mGroup.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED);
647 :
648 0 : mGroup = groupId;
649 0 : mClientID = clientId;
650 0 : return NS_OK;
651 : }
652 :
653 : NS_IMETHODIMP
654 0 : nsApplicationCache::GetGroupID(nsACString &out)
655 : {
656 0 : out = mGroup;
657 0 : return NS_OK;
658 : }
659 :
660 : NS_IMETHODIMP
661 20 : nsApplicationCache::GetClientID(nsACString &out)
662 : {
663 20 : out = mClientID;
664 20 : return NS_OK;
665 : }
666 :
667 : NS_IMETHODIMP
668 0 : nsApplicationCache::GetActive(bool *out)
669 : {
670 0 : NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
671 :
672 0 : *out = mDevice->IsActiveCache(mGroup, mClientID);
673 0 : return NS_OK;
674 : }
675 :
676 : NS_IMETHODIMP
677 8 : nsApplicationCache::Activate()
678 : {
679 8 : NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
680 8 : NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
681 :
682 8 : mDevice->ActivateCache(mGroup, mClientID);
683 8 : return NS_OK;
684 : }
685 :
686 : NS_IMETHODIMP
687 0 : nsApplicationCache::Discard()
688 : {
689 0 : NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
690 0 : NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
691 :
692 0 : mValid = false;
693 :
694 0 : if (mDevice->IsActiveCache(mGroup, mClientID))
695 : {
696 0 : mDevice->DeactivateGroup(mGroup);
697 : }
698 :
699 0 : return mDevice->EvictEntries(mClientID.get());
700 : }
701 :
702 : NS_IMETHODIMP
703 24 : nsApplicationCache::MarkEntry(const nsACString &key,
704 : PRUint32 typeBits)
705 : {
706 24 : NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
707 24 : NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
708 :
709 24 : return mDevice->MarkEntry(mClientID, key, typeBits);
710 : }
711 :
712 :
713 : NS_IMETHODIMP
714 0 : nsApplicationCache::UnmarkEntry(const nsACString &key,
715 : PRUint32 typeBits)
716 : {
717 0 : NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
718 0 : NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
719 :
720 0 : return mDevice->UnmarkEntry(mClientID, key, typeBits);
721 : }
722 :
723 : NS_IMETHODIMP
724 9 : nsApplicationCache::GetTypes(const nsACString &key,
725 : PRUint32 *typeBits)
726 : {
727 9 : NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
728 9 : NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
729 :
730 9 : return mDevice->GetTypes(mClientID, key, typeBits);
731 : }
732 :
733 : NS_IMETHODIMP
734 0 : nsApplicationCache::GatherEntries(PRUint32 typeBits,
735 : PRUint32 * count,
736 : char *** keys)
737 : {
738 0 : NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
739 0 : NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
740 :
741 0 : return mDevice->GatherEntries(mClientID, typeBits, count, keys);
742 : }
743 :
744 : NS_IMETHODIMP
745 8 : nsApplicationCache::AddNamespaces(nsIArray *namespaces)
746 : {
747 8 : NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
748 8 : NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
749 :
750 8 : if (!namespaces)
751 0 : return NS_OK;
752 :
753 16 : mozStorageTransaction transaction(mDevice->mDB, false);
754 :
755 : PRUint32 length;
756 8 : nsresult rv = namespaces->GetLength(&length);
757 8 : NS_ENSURE_SUCCESS(rv, rv);
758 :
759 16 : for (PRUint32 i = 0; i < length; i++) {
760 : nsCOMPtr<nsIApplicationCacheNamespace> ns =
761 16 : do_QueryElementAt(namespaces, i);
762 8 : if (ns) {
763 8 : rv = mDevice->AddNamespace(mClientID, ns);
764 8 : NS_ENSURE_SUCCESS(rv, rv);
765 : }
766 : }
767 :
768 8 : rv = transaction.Commit();
769 8 : NS_ENSURE_SUCCESS(rv, rv);
770 :
771 8 : return NS_OK;
772 : }
773 :
774 : NS_IMETHODIMP
775 8 : nsApplicationCache::GetMatchingNamespace(const nsACString &key,
776 : nsIApplicationCacheNamespace **out)
777 :
778 : {
779 8 : NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
780 8 : NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
781 :
782 8 : return mDevice->GetMatchingNamespace(mClientID, key, out);
783 : }
784 :
785 : NS_IMETHODIMP
786 0 : nsApplicationCache::GetUsage(PRUint32 *usage)
787 : {
788 0 : NS_ENSURE_TRUE(mValid, NS_ERROR_NOT_AVAILABLE);
789 0 : NS_ENSURE_TRUE(mDevice, NS_ERROR_NOT_AVAILABLE);
790 :
791 0 : return mDevice->GetUsage(mClientID, usage);
792 : }
793 :
794 : /******************************************************************************
795 : * nsCloseDBEvent
796 : *****************************************************************************/
797 :
798 : class nsCloseDBEvent : public nsRunnable {
799 : public:
800 2 : nsCloseDBEvent(mozIStorageConnection *aDB)
801 2 : {
802 2 : mDB = aDB;
803 2 : }
804 :
805 2 : NS_IMETHOD Run()
806 : {
807 2 : mDB->Close();
808 2 : return NS_OK;
809 : }
810 :
811 : protected:
812 8 : virtual ~nsCloseDBEvent() {}
813 :
814 : private:
815 : nsCOMPtr<mozIStorageConnection> mDB;
816 : };
817 :
818 :
819 :
820 : /******************************************************************************
821 : * nsOfflineCacheDevice
822 : */
823 :
824 282 : NS_IMPL_THREADSAFE_ISUPPORTS1(nsOfflineCacheDevice, nsIApplicationCacheService)
825 :
826 15 : nsOfflineCacheDevice::nsOfflineCacheDevice()
827 : : mDB(nsnull)
828 : , mCacheCapacity(0)
829 15 : , mDeltaCounter(0)
830 : {
831 15 : }
832 :
833 : /* static */
834 : bool
835 8 : nsOfflineCacheDevice::GetStrictFileOriginPolicy()
836 : {
837 16 : nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
838 :
839 : bool retval;
840 8 : if (prefs && NS_SUCCEEDED(prefs->GetBoolPref("security.fileuri.strict_origin_policy", &retval)))
841 8 : return retval;
842 :
843 : // As default value use true (be more strict)
844 0 : return true;
845 : }
846 :
847 : PRUint32
848 0 : nsOfflineCacheDevice::CacheSize()
849 : {
850 0 : AutoResetStatement statement(mStatement_CacheSize);
851 :
852 : bool hasRows;
853 0 : nsresult rv = statement->ExecuteStep(&hasRows);
854 0 : NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
855 :
856 0 : return (PRUint32) statement->AsInt32(0);
857 : }
858 :
859 : PRUint32
860 0 : nsOfflineCacheDevice::EntryCount()
861 : {
862 0 : AutoResetStatement statement(mStatement_EntryCount);
863 :
864 : bool hasRows;
865 0 : nsresult rv = statement->ExecuteStep(&hasRows);
866 0 : NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasRows, 0);
867 :
868 0 : return (PRUint32) statement->AsInt32(0);
869 : }
870 :
871 : nsresult
872 31 : nsOfflineCacheDevice::UpdateEntry(nsCacheEntry *entry)
873 : {
874 : // Decompose the key into "ClientID" and "Key"
875 62 : nsCAutoString keyBuf;
876 : const char *cid, *key;
877 31 : if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
878 0 : return NS_ERROR_UNEXPECTED;
879 :
880 62 : nsCString metaDataBuf;
881 31 : PRUint32 mdSize = entry->MetaDataSize();
882 31 : if (!EnsureStringLength(metaDataBuf, mdSize))
883 0 : return NS_ERROR_OUT_OF_MEMORY;
884 31 : char *md = metaDataBuf.BeginWriting();
885 31 : entry->FlattenMetaData(md, mdSize);
886 :
887 : nsOfflineCacheRecord rec;
888 31 : rec.metaData = (const PRUint8 *) md;
889 31 : rec.metaDataLen = mdSize;
890 31 : rec.flags = 0; // mark entry as inactive
891 31 : rec.dataSize = entry->DataSize();
892 31 : rec.fetchCount = entry->FetchCount();
893 31 : rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
894 31 : rec.lastModified = PRTimeFromSeconds(entry->LastModified());
895 31 : rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
896 :
897 62 : AutoResetStatement statement(mStatement_UpdateEntry);
898 :
899 : nsresult rv;
900 31 : rv = statement->BindBlobByIndex(0, rec.metaData, rec.metaDataLen);
901 31 : rv |= statement->BindInt32ByIndex(1, rec.flags);
902 31 : rv |= statement->BindInt32ByIndex(2, rec.dataSize);
903 31 : rv |= statement->BindInt32ByIndex(3, rec.fetchCount);
904 31 : rv |= statement->BindInt64ByIndex(4, rec.lastFetched);
905 31 : rv |= statement->BindInt64ByIndex(5, rec.lastModified);
906 31 : rv |= statement->BindInt64ByIndex(6, rec.expirationTime);
907 31 : rv |= statement->BindUTF8StringByIndex(7, nsDependentCString(cid));
908 31 : rv |= statement->BindUTF8StringByIndex(8, nsDependentCString(key));
909 31 : NS_ENSURE_SUCCESS(rv, rv);
910 :
911 : bool hasRows;
912 31 : rv = statement->ExecuteStep(&hasRows);
913 31 : NS_ENSURE_SUCCESS(rv, rv);
914 :
915 31 : NS_ASSERTION(!hasRows, "UPDATE should not result in output");
916 31 : return rv;
917 : }
918 :
919 : nsresult
920 50 : nsOfflineCacheDevice::UpdateEntrySize(nsCacheEntry *entry, PRUint32 newSize)
921 : {
922 : // Decompose the key into "ClientID" and "Key"
923 100 : nsCAutoString keyBuf;
924 : const char *cid, *key;
925 50 : if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
926 0 : return NS_ERROR_UNEXPECTED;
927 :
928 100 : AutoResetStatement statement(mStatement_UpdateEntrySize);
929 :
930 : nsresult rv;
931 50 : rv = statement->BindInt32ByIndex(0, newSize);
932 50 : rv |= statement->BindUTF8StringByIndex(1, nsDependentCString(cid));
933 50 : rv |= statement->BindUTF8StringByIndex(2, nsDependentCString(key));
934 50 : NS_ENSURE_SUCCESS(rv, rv);
935 :
936 : bool hasRows;
937 50 : rv = statement->ExecuteStep(&hasRows);
938 50 : NS_ENSURE_SUCCESS(rv, rv);
939 :
940 50 : NS_ASSERTION(!hasRows, "UPDATE should not result in output");
941 50 : return rv;
942 : }
943 :
944 : nsresult
945 0 : nsOfflineCacheDevice::DeleteEntry(nsCacheEntry *entry, bool deleteData)
946 : {
947 0 : if (deleteData)
948 : {
949 0 : nsresult rv = DeleteData(entry);
950 0 : if (NS_FAILED(rv))
951 0 : return rv;
952 : }
953 :
954 : // Decompose the key into "ClientID" and "Key"
955 0 : nsCAutoString keyBuf;
956 : const char *cid, *key;
957 0 : if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
958 0 : return NS_ERROR_UNEXPECTED;
959 :
960 0 : AutoResetStatement statement(mStatement_DeleteEntry);
961 :
962 : nsresult rv;
963 0 : rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
964 0 : rv |= statement->BindUTF8StringByIndex(1, nsDependentCString(key));
965 0 : NS_ENSURE_SUCCESS(rv, rv);
966 :
967 : bool hasRows;
968 0 : rv = statement->ExecuteStep(&hasRows);
969 0 : NS_ENSURE_SUCCESS(rv, rv);
970 :
971 0 : NS_ASSERTION(!hasRows, "DELETE should not result in output");
972 0 : return rv;
973 : }
974 :
975 : nsresult
976 0 : nsOfflineCacheDevice::DeleteData(nsCacheEntry *entry)
977 : {
978 0 : nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
979 0 : NS_ENSURE_STATE(binding);
980 :
981 0 : return binding->mDataFile->Remove(false);
982 : }
983 :
984 : /**
985 : * nsCacheDevice implementation
986 : */
987 :
988 : /* static */
989 : nsOfflineCacheDevice *
990 8 : nsOfflineCacheDevice::GetInstance()
991 : {
992 : nsresult rv;
993 16 : nsCOMPtr<nsICacheService> serv = do_GetService(kCacheServiceCID, &rv);
994 8 : NS_ENSURE_SUCCESS(rv, nsnull);
995 :
996 8 : nsICacheService *iservice = static_cast<nsICacheService*>(serv.get());
997 8 : nsCacheService *cacheService = static_cast<nsCacheService*>(iservice);
998 8 : rv = cacheService->CreateOfflineDevice();
999 8 : NS_ENSURE_SUCCESS(rv, nsnull);
1000 :
1001 8 : NS_IF_ADDREF(cacheService->mOfflineDevice);
1002 8 : return cacheService->mOfflineDevice;
1003 : }
1004 :
1005 : // This struct is local to nsOfflineCacheDevice::Init, but ISO C++98 doesn't
1006 : // allow a template (mozilla::ArrayLength) to be instantiated based on a local
1007 : // type. Boo-urns!
1008 : struct StatementSql {
1009 : nsCOMPtr<mozIStorageStatement> &statement;
1010 : const char *sql;
1011 315 : StatementSql (nsCOMPtr<mozIStorageStatement> &aStatement, const char *aSql):
1012 315 : statement (aStatement), sql (aSql) {}
1013 : };
1014 :
1015 : nsresult
1016 15 : nsOfflineCacheDevice::Init()
1017 : {
1018 : NS_TIME_FUNCTION;
1019 :
1020 15 : NS_ENSURE_TRUE(!mDB, NS_ERROR_ALREADY_INITIALIZED);
1021 :
1022 : // SetCacheParentDirectory must have been called
1023 15 : NS_ENSURE_TRUE(mCacheDirectory, NS_ERROR_UNEXPECTED);
1024 :
1025 : // make sure the cache directory exists
1026 15 : nsresult rv = EnsureDir(mCacheDirectory);
1027 15 : NS_ENSURE_SUCCESS(rv, rv);
1028 :
1029 : // build path to index file
1030 30 : nsCOMPtr<nsIFile> indexFile;
1031 15 : rv = mCacheDirectory->Clone(getter_AddRefs(indexFile));
1032 15 : NS_ENSURE_SUCCESS(rv, rv);
1033 15 : rv = indexFile->AppendNative(NS_LITERAL_CSTRING("index.sqlite"));
1034 15 : NS_ENSURE_SUCCESS(rv, rv);
1035 :
1036 : nsCOMPtr<mozIStorageService> ss =
1037 30 : do_GetService("@mozilla.org/storage/service;1", &rv);
1038 15 : NS_ENSURE_SUCCESS(rv, rv);
1039 :
1040 15 : rv = ss->OpenDatabase(indexFile, getter_AddRefs(mDB));
1041 15 : NS_ENSURE_SUCCESS(rv, rv);
1042 :
1043 15 : mInitThread = do_GetCurrentThread();
1044 :
1045 15 : mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
1046 :
1047 : // XXX ... other initialization steps
1048 :
1049 : // XXX in the future we may wish to verify the schema for moz_cache
1050 : // perhaps using "PRAGMA table_info" ?
1051 :
1052 : // build the table
1053 : //
1054 : // "Generation" is the data file generation number.
1055 : // "Flags" is a bit-field indicating the state of the entry.
1056 : //
1057 15 : rv = mDB->ExecuteSimpleSQL(
1058 15 : NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache (\n"
1059 : " ClientID TEXT,\n"
1060 : " Key TEXT,\n"
1061 : " MetaData BLOB,\n"
1062 : " Generation INTEGER,\n"
1063 : " Flags INTEGER,\n"
1064 : " DataSize INTEGER,\n"
1065 : " FetchCount INTEGER,\n"
1066 : " LastFetched INTEGER,\n"
1067 : " LastModified INTEGER,\n"
1068 : " ExpirationTime INTEGER,\n"
1069 : " ItemType INTEGER DEFAULT 0\n"
1070 15 : ");\n"));
1071 15 : NS_ENSURE_SUCCESS(rv, rv);
1072 :
1073 : // Databases from 1.9.0 don't have the ItemType column. Add the column
1074 : // here, but don't worry about failures (the column probably already exists)
1075 15 : mDB->ExecuteSimpleSQL(
1076 15 : NS_LITERAL_CSTRING("ALTER TABLE moz_cache ADD ItemType INTEGER DEFAULT 0"));
1077 :
1078 : // Create the table for storing cache groups. All actions on
1079 : // moz_cache_groups use the GroupID, so use it as the primary key.
1080 15 : rv = mDB->ExecuteSimpleSQL(
1081 15 : NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_cache_groups (\n"
1082 : " GroupID TEXT PRIMARY KEY,\n"
1083 : " ActiveClientID TEXT\n"
1084 15 : ");\n"));
1085 15 : NS_ENSURE_SUCCESS(rv, rv);
1086 :
1087 15 : mDB->ExecuteSimpleSQL(
1088 15 : NS_LITERAL_CSTRING("ALTER TABLE moz_cache_groups "
1089 15 : "ADD ActivateTimeStamp INTEGER DEFAULT 0"));
1090 :
1091 : // ClientID: clientID joining moz_cache and moz_cache_namespaces
1092 : // tables.
1093 : // Data: Data associated with this namespace (e.g. a fallback URI
1094 : // for fallback entries).
1095 : // ItemType: the type of namespace.
1096 15 : rv = mDB->ExecuteSimpleSQL(
1097 15 : NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS"
1098 : " moz_cache_namespaces (\n"
1099 : " ClientID TEXT,\n"
1100 : " NameSpace TEXT,\n"
1101 : " Data TEXT,\n"
1102 : " ItemType INTEGER\n"
1103 15 : ");\n"));
1104 15 : NS_ENSURE_SUCCESS(rv, rv);
1105 :
1106 : // Databases from 1.9.0 have a moz_cache_index that should be dropped
1107 15 : rv = mDB->ExecuteSimpleSQL(
1108 15 : NS_LITERAL_CSTRING("DROP INDEX IF EXISTS moz_cache_index"));
1109 15 : NS_ENSURE_SUCCESS(rv, rv);
1110 :
1111 : // Key/ClientID pairs should be unique in the database. All queries
1112 : // against moz_cache use the Key (which is also the most unique), so
1113 : // use it as the primary key for this index.
1114 15 : rv = mDB->ExecuteSimpleSQL(
1115 15 : NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS "
1116 : " moz_cache_key_clientid_index"
1117 15 : " ON moz_cache (Key, ClientID);"));
1118 15 : NS_ENSURE_SUCCESS(rv, rv);
1119 :
1120 : // Used for ClientID lookups and to keep ClientID/NameSpace pairs unique.
1121 15 : rv = mDB->ExecuteSimpleSQL(
1122 15 : NS_LITERAL_CSTRING("CREATE UNIQUE INDEX IF NOT EXISTS"
1123 : " moz_cache_namespaces_clientid_index"
1124 15 : " ON moz_cache_namespaces (ClientID, NameSpace);"));
1125 15 : NS_ENSURE_SUCCESS(rv, rv);
1126 :
1127 : // Used for namespace lookups.
1128 15 : rv = mDB->ExecuteSimpleSQL(
1129 15 : NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS"
1130 : " moz_cache_namespaces_namespace_index"
1131 15 : " ON moz_cache_namespaces (NameSpace);"));
1132 15 : NS_ENSURE_SUCCESS(rv, rv);
1133 :
1134 :
1135 15 : mEvictionFunction = new nsOfflineCacheEvictionFunction(this);
1136 15 : if (!mEvictionFunction) return NS_ERROR_OUT_OF_MEMORY;
1137 :
1138 15 : rv = mDB->CreateFunction(NS_LITERAL_CSTRING("cache_eviction_observer"), 2, mEvictionFunction);
1139 15 : NS_ENSURE_SUCCESS(rv, rv);
1140 :
1141 : // create all (most) of our statements up front
1142 : StatementSql prepared[] = {
1143 : StatementSql ( mStatement_CacheSize, "SELECT Sum(DataSize) from moz_cache;" ),
1144 : StatementSql ( mStatement_ApplicationCacheSize, "SELECT Sum(DataSize) from moz_cache WHERE ClientID = ?;" ),
1145 : StatementSql ( mStatement_EntryCount, "SELECT count(*) from moz_cache;" ),
1146 : StatementSql ( mStatement_UpdateEntry, "UPDATE moz_cache SET MetaData = ?, Flags = ?, DataSize = ?, FetchCount = ?, LastFetched = ?, LastModified = ?, ExpirationTime = ? WHERE ClientID = ? AND Key = ?;" ),
1147 : StatementSql ( mStatement_UpdateEntrySize, "UPDATE moz_cache SET DataSize = ? WHERE ClientID = ? AND Key = ?;" ),
1148 : StatementSql ( mStatement_UpdateEntryFlags, "UPDATE moz_cache SET Flags = ? WHERE ClientID = ? AND Key = ?;" ),
1149 : StatementSql ( mStatement_DeleteEntry, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
1150 : StatementSql ( mStatement_FindEntry, "SELECT MetaData, Generation, Flags, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime, ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;" ),
1151 : StatementSql ( mStatement_BindEntry, "INSERT INTO moz_cache (ClientID, Key, MetaData, Generation, Flags, DataSize, FetchCount, LastFetched, LastModified, ExpirationTime) VALUES(?,?,?,?,?,?,?,?,?,?);" ),
1152 :
1153 : StatementSql ( mStatement_MarkEntry, "UPDATE moz_cache SET ItemType = (ItemType | ?) WHERE ClientID = ? AND Key = ?;" ),
1154 : StatementSql ( mStatement_UnmarkEntry, "UPDATE moz_cache SET ItemType = (ItemType & ~?) WHERE ClientID = ? AND Key = ?;" ),
1155 : StatementSql ( mStatement_GetTypes, "SELECT ItemType FROM moz_cache WHERE ClientID = ? AND Key = ?;"),
1156 : StatementSql ( mStatement_CleanupUnmarked, "DELETE FROM moz_cache WHERE ClientID = ? AND Key = ? AND ItemType = 0;" ),
1157 : StatementSql ( mStatement_GatherEntries, "SELECT Key FROM moz_cache WHERE ClientID = ? AND (ItemType & ?) > 0;" ),
1158 :
1159 : StatementSql ( mStatement_ActivateClient, "INSERT OR REPLACE INTO moz_cache_groups (GroupID, ActiveClientID, ActivateTimeStamp) VALUES (?, ?, ?);" ),
1160 : StatementSql ( mStatement_DeactivateGroup, "DELETE FROM moz_cache_groups WHERE GroupID = ?;" ),
1161 : StatementSql ( mStatement_FindClient, "SELECT ClientID, ItemType FROM moz_cache WHERE Key = ? ORDER BY LastFetched DESC, LastModified DESC;" ),
1162 :
1163 : // Search for namespaces that match the URI. Use the <= operator
1164 : // to ensure that we use the index on moz_cache_namespaces.
1165 : StatementSql ( mStatement_FindClientByNamespace, "SELECT ns.ClientID, ns.ItemType FROM"
1166 : " moz_cache_namespaces AS ns JOIN moz_cache_groups AS groups"
1167 : " ON ns.ClientID = groups.ActiveClientID"
1168 : " WHERE ns.NameSpace <= ?1 AND ?1 GLOB ns.NameSpace || '*'"
1169 : " ORDER BY ns.NameSpace DESC, groups.ActivateTimeStamp DESC;"),
1170 : StatementSql ( mStatement_FindNamespaceEntry, "SELECT NameSpace, Data, ItemType FROM moz_cache_namespaces"
1171 : " WHERE ClientID = ?1"
1172 : " AND NameSpace <= ?2 AND ?2 GLOB NameSpace || '*'"
1173 : " ORDER BY NameSpace DESC;"),
1174 : StatementSql ( mStatement_InsertNamespaceEntry, "INSERT INTO moz_cache_namespaces (ClientID, NameSpace, Data, ItemType) VALUES(?, ?, ?, ?);"),
1175 : StatementSql ( mStatement_EnumerateGroups, "SELECT GroupID, ActiveClientID FROM moz_cache_groups;")
1176 15 : };
1177 330 : for (PRUint32 i = 0; NS_SUCCEEDED(rv) && i < ArrayLength(prepared); ++i)
1178 : {
1179 315 : LOG(("Creating statement: %s\n", prepared[i].sql));
1180 :
1181 630 : rv = mDB->CreateStatement(nsDependentCString(prepared[i].sql),
1182 630 : getter_AddRefs(prepared[i].statement));
1183 315 : NS_ENSURE_SUCCESS(rv, rv);
1184 : }
1185 :
1186 : // Clear up any dangling active flags
1187 15 : rv = mDB->ExecuteSimpleSQL(
1188 15 : NS_LITERAL_CSTRING("UPDATE moz_cache"
1189 : " SET Flags=(Flags & ~1)"
1190 15 : " WHERE (Flags & 1);"));
1191 15 : NS_ENSURE_SUCCESS(rv, rv);
1192 :
1193 15 : rv = InitActiveCaches();
1194 15 : NS_ENSURE_SUCCESS(rv, rv);
1195 :
1196 15 : return NS_OK;
1197 : }
1198 :
1199 : nsresult
1200 15 : nsOfflineCacheDevice::InitActiveCaches()
1201 : {
1202 15 : NS_ENSURE_TRUE(mCaches.Init(), NS_ERROR_OUT_OF_MEMORY);
1203 15 : NS_ENSURE_TRUE(mActiveCachesByGroup.Init(), NS_ERROR_OUT_OF_MEMORY);
1204 :
1205 15 : nsresult rv = mActiveCaches.Init(5);
1206 15 : NS_ENSURE_SUCCESS(rv, rv);
1207 :
1208 30 : AutoResetStatement statement(mStatement_EnumerateGroups);
1209 :
1210 : bool hasRows;
1211 15 : rv = statement->ExecuteStep(&hasRows);
1212 15 : NS_ENSURE_SUCCESS(rv, rv);
1213 :
1214 30 : while (hasRows)
1215 : {
1216 0 : nsCAutoString group;
1217 0 : statement->GetUTF8String(0, group);
1218 0 : nsCString clientID;
1219 0 : statement->GetUTF8String(1, clientID);
1220 :
1221 0 : mActiveCaches.PutEntry(clientID);
1222 0 : mActiveCachesByGroup.Put(group, new nsCString(clientID));
1223 :
1224 0 : rv = statement->ExecuteStep(&hasRows);
1225 0 : NS_ENSURE_SUCCESS(rv, rv);
1226 : }
1227 :
1228 15 : return NS_OK;
1229 : }
1230 :
1231 : /* static */
1232 : PLDHashOperator
1233 8 : nsOfflineCacheDevice::ShutdownApplicationCache(const nsACString &key,
1234 : nsIWeakReference *weakRef,
1235 : void *ctx)
1236 : {
1237 16 : nsCOMPtr<nsIApplicationCache> obj = do_QueryReferent(weakRef);
1238 8 : if (obj)
1239 : {
1240 8 : nsApplicationCache *appCache = static_cast<nsApplicationCache*>(obj.get());
1241 8 : appCache->MarkInvalid();
1242 : }
1243 :
1244 8 : return PL_DHASH_NEXT;
1245 : }
1246 :
1247 : nsresult
1248 29 : nsOfflineCacheDevice::Shutdown()
1249 : {
1250 29 : NS_ENSURE_TRUE(mDB, NS_ERROR_NOT_INITIALIZED);
1251 :
1252 15 : if (mCaches.IsInitialized())
1253 15 : mCaches.EnumerateRead(ShutdownApplicationCache, this);
1254 :
1255 : {
1256 30 : EvictionObserver evictionObserver(mDB, mEvictionFunction);
1257 :
1258 : // Delete all rows whose clientID is not an active clientID.
1259 30 : nsresult rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1260 : "DELETE FROM moz_cache WHERE rowid IN"
1261 : " (SELECT moz_cache.rowid FROM"
1262 : " moz_cache LEFT OUTER JOIN moz_cache_groups ON"
1263 : " (moz_cache.ClientID = moz_cache_groups.ActiveClientID)"
1264 15 : " WHERE moz_cache_groups.GroupID ISNULL)"));
1265 :
1266 15 : if (NS_FAILED(rv))
1267 0 : NS_WARNING("Failed to clean up unused application caches.");
1268 : else
1269 15 : evictionObserver.Apply();
1270 :
1271 : // Delete all namespaces whose clientID is not an active clientID.
1272 30 : rv = mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
1273 : "DELETE FROM moz_cache_namespaces WHERE rowid IN"
1274 : " (SELECT moz_cache_namespaces.rowid FROM"
1275 : " moz_cache_namespaces LEFT OUTER JOIN moz_cache_groups ON"
1276 : " (moz_cache_namespaces.ClientID = moz_cache_groups.ActiveClientID)"
1277 15 : " WHERE moz_cache_groups.GroupID ISNULL)"));
1278 :
1279 15 : if (NS_FAILED(rv))
1280 0 : NS_WARNING("Failed to clean up namespaces.");
1281 :
1282 15 : mEvictionFunction = 0;
1283 :
1284 15 : mStatement_CacheSize = nsnull;
1285 15 : mStatement_ApplicationCacheSize = nsnull;
1286 15 : mStatement_EntryCount = nsnull;
1287 15 : mStatement_UpdateEntry = nsnull;
1288 15 : mStatement_UpdateEntrySize = nsnull;
1289 15 : mStatement_UpdateEntryFlags = nsnull;
1290 15 : mStatement_DeleteEntry = nsnull;
1291 15 : mStatement_FindEntry = nsnull;
1292 15 : mStatement_BindEntry = nsnull;
1293 15 : mStatement_ClearDomain = nsnull;
1294 15 : mStatement_MarkEntry = nsnull;
1295 15 : mStatement_UnmarkEntry = nsnull;
1296 15 : mStatement_GetTypes = nsnull;
1297 15 : mStatement_FindNamespaceEntry = nsnull;
1298 15 : mStatement_InsertNamespaceEntry = nsnull;
1299 15 : mStatement_CleanupUnmarked = nsnull;
1300 15 : mStatement_GatherEntries = nsnull;
1301 15 : mStatement_ActivateClient = nsnull;
1302 15 : mStatement_DeactivateGroup = nsnull;
1303 15 : mStatement_FindClient = nsnull;
1304 15 : mStatement_FindClientByNamespace = nsnull;
1305 15 : mStatement_EnumerateGroups = nsnull;
1306 : }
1307 :
1308 : // Close Database on the correct thread
1309 15 : bool isOnCurrentThread = true;
1310 15 : if (mInitThread)
1311 15 : mInitThread->IsOnCurrentThread(&isOnCurrentThread);
1312 :
1313 15 : if (!isOnCurrentThread) {
1314 6 : nsCOMPtr<nsIRunnable> ev = new nsCloseDBEvent(mDB);
1315 :
1316 2 : if (ev) {
1317 2 : mInitThread->Dispatch(ev, NS_DISPATCH_NORMAL);
1318 : }
1319 : }
1320 : else {
1321 13 : mDB->Close();
1322 : }
1323 :
1324 15 : mDB = nsnull;
1325 15 : mInitThread = nsnull;
1326 :
1327 15 : return NS_OK;
1328 : }
1329 :
1330 : const char *
1331 4 : nsOfflineCacheDevice::GetDeviceID()
1332 : {
1333 4 : return OFFLINE_CACHE_DEVICE_ID;
1334 : }
1335 :
1336 : nsCacheEntry *
1337 49 : nsOfflineCacheDevice::FindEntry(nsCString *fullKey, bool *collision)
1338 : {
1339 98 : mozilla::Telemetry::AutoTimer<mozilla::Telemetry::CACHE_OFFLINE_SEARCH> timer;
1340 49 : LOG(("nsOfflineCacheDevice::FindEntry [key=%s]\n", fullKey->get()));
1341 :
1342 : // SELECT * FROM moz_cache WHERE key = ?
1343 :
1344 : // Decompose the key into "ClientID" and "Key"
1345 98 : nsCAutoString keyBuf;
1346 : const char *cid, *key;
1347 49 : if (!DecomposeCacheEntryKey(fullKey, &cid, &key, keyBuf))
1348 0 : return nsnull;
1349 :
1350 98 : AutoResetStatement statement(mStatement_FindEntry);
1351 :
1352 : nsresult rv;
1353 49 : rv = statement->BindUTF8StringByIndex(0, nsDependentCString(cid));
1354 49 : rv |= statement->BindUTF8StringByIndex(1, nsDependentCString(key));
1355 49 : NS_ENSURE_SUCCESS(rv, nsnull);
1356 :
1357 : bool hasRows;
1358 49 : rv = statement->ExecuteStep(&hasRows);
1359 49 : if (NS_FAILED(rv) || !hasRows)
1360 43 : return nsnull; // entry not found
1361 :
1362 : nsOfflineCacheRecord rec;
1363 6 : statement->GetSharedBlob(0, &rec.metaDataLen,
1364 6 : (const PRUint8 **) &rec.metaData);
1365 6 : rec.generation = statement->AsInt32(1);
1366 6 : rec.flags = statement->AsInt32(2);
1367 6 : rec.dataSize = statement->AsInt32(3);
1368 6 : rec.fetchCount = statement->AsInt32(4);
1369 6 : rec.lastFetched = statement->AsInt64(5);
1370 6 : rec.lastModified = statement->AsInt64(6);
1371 6 : rec.expirationTime = statement->AsInt64(7);
1372 :
1373 6 : LOG(("entry: [%u %d %d %d %d %lld %lld %lld]\n",
1374 : rec.metaDataLen,
1375 : rec.generation,
1376 : rec.flags,
1377 : rec.dataSize,
1378 : rec.fetchCount,
1379 : rec.lastFetched,
1380 : rec.lastModified,
1381 : rec.expirationTime));
1382 :
1383 6 : nsCacheEntry *entry = CreateCacheEntry(this, fullKey, rec);
1384 :
1385 6 : if (entry)
1386 : {
1387 : // make sure that the data file exists
1388 6 : nsOfflineCacheBinding *binding = (nsOfflineCacheBinding*)entry->Data();
1389 : bool isFile;
1390 6 : rv = binding->mDataFile->IsFile(&isFile);
1391 6 : if (NS_FAILED(rv) || !isFile)
1392 : {
1393 0 : DeleteEntry(entry, false);
1394 0 : delete entry;
1395 0 : return nsnull;
1396 : }
1397 :
1398 6 : statement->Reset();
1399 :
1400 : // mark as active
1401 12 : AutoResetStatement updateStatement(mStatement_UpdateEntryFlags);
1402 6 : rec.flags |= 0x1;
1403 6 : rv |= updateStatement->BindInt32ByIndex(0, rec.flags);
1404 6 : rv |= updateStatement->BindUTF8StringByIndex(1, nsDependentCString(cid));
1405 6 : rv |= updateStatement->BindUTF8StringByIndex(2, nsDependentCString(key));
1406 6 : if (NS_FAILED(rv))
1407 : {
1408 0 : delete entry;
1409 0 : return nsnull;
1410 : }
1411 :
1412 6 : rv = updateStatement->ExecuteStep(&hasRows);
1413 6 : if (NS_FAILED(rv))
1414 : {
1415 0 : delete entry;
1416 0 : return nsnull;
1417 : }
1418 :
1419 6 : NS_ASSERTION(!hasRows, "UPDATE should not result in output");
1420 : }
1421 :
1422 6 : return entry;
1423 : }
1424 :
1425 : nsresult
1426 31 : nsOfflineCacheDevice::DeactivateEntry(nsCacheEntry *entry)
1427 : {
1428 31 : LOG(("nsOfflineCacheDevice::DeactivateEntry [key=%s]\n",
1429 : entry->Key()->get()));
1430 :
1431 : // This method is called to inform us that the nsCacheEntry object is going
1432 : // away. We should persist anything that needs to be persisted, or if the
1433 : // entry is doomed, we can go ahead and clear its storage.
1434 :
1435 31 : if (entry->IsDoomed())
1436 : {
1437 : // remove corresponding row and file if they exist
1438 :
1439 : // the row should have been removed in DoomEntry... we could assert that
1440 : // that happened. otherwise, all we have to do here is delete the file
1441 : // on disk.
1442 0 : DeleteData(entry);
1443 : }
1444 : else
1445 : {
1446 : // UPDATE the database row
1447 :
1448 : // XXX Assumption: the row already exists because it was either created
1449 : // with a call to BindEntry or it was there when we called FindEntry.
1450 :
1451 31 : UpdateEntry(entry);
1452 : }
1453 :
1454 31 : delete entry;
1455 :
1456 31 : return NS_OK;
1457 : }
1458 :
1459 : nsresult
1460 25 : nsOfflineCacheDevice::BindEntry(nsCacheEntry *entry)
1461 : {
1462 25 : LOG(("nsOfflineCacheDevice::BindEntry [key=%s]\n", entry->Key()->get()));
1463 :
1464 25 : NS_ENSURE_STATE(!entry->Data());
1465 :
1466 : // This method is called to inform us that we have a new entry. The entry
1467 : // may collide with an existing entry in our DB, but if that happens we can
1468 : // assume that the entry is not being used.
1469 :
1470 : // INSERT the database row
1471 :
1472 : // XXX Assumption: if the row already exists, then FindEntry would have
1473 : // returned it. if that entry was doomed, then DoomEntry would have removed
1474 : // it from the table. so, we should always have to insert at this point.
1475 :
1476 : // Decompose the key into "ClientID" and "Key"
1477 50 : nsCAutoString keyBuf;
1478 : const char *cid, *key;
1479 25 : if (!DecomposeCacheEntryKey(entry->Key(), &cid, &key, keyBuf))
1480 0 : return NS_ERROR_UNEXPECTED;
1481 :
1482 : // create binding, pick best generation number
1483 : nsRefPtr<nsOfflineCacheBinding> binding =
1484 50 : nsOfflineCacheBinding::Create(mCacheDirectory, entry->Key(), -1);
1485 25 : if (!binding)
1486 0 : return NS_ERROR_OUT_OF_MEMORY;
1487 :
1488 : nsOfflineCacheRecord rec;
1489 25 : rec.clientID = cid;
1490 25 : rec.key = key;
1491 25 : rec.metaData = NULL; // don't write any metadata now.
1492 25 : rec.metaDataLen = 0;
1493 25 : rec.generation = binding->mGeneration;
1494 25 : rec.flags = 0x1; // mark entry as active, we'll reset this in DeactivateEntry
1495 25 : rec.dataSize = 0;
1496 25 : rec.fetchCount = entry->FetchCount();
1497 25 : rec.lastFetched = PRTimeFromSeconds(entry->LastFetched());
1498 25 : rec.lastModified = PRTimeFromSeconds(entry->LastModified());
1499 25 : rec.expirationTime = PRTimeFromSeconds(entry->ExpirationTime());
1500 :
1501 50 : AutoResetStatement statement(mStatement_BindEntry);
1502 :
1503 : nsresult rv;
1504 25 : rv = statement->BindUTF8StringByIndex(0, nsDependentCString(rec.clientID));
1505 25 : rv |= statement->BindUTF8StringByIndex(1, nsDependentCString(rec.key));
1506 25 : rv |= statement->BindBlobByIndex(2, rec.metaData, rec.metaDataLen);
1507 25 : rv |= statement->BindInt32ByIndex(3, rec.generation);
1508 25 : rv |= statement->BindInt32ByIndex(4, rec.flags);
1509 25 : rv |= statement->BindInt32ByIndex(5, rec.dataSize);
1510 25 : rv |= statement->BindInt32ByIndex(6, rec.fetchCount);
1511 25 : rv |= statement->BindInt64ByIndex(7, rec.lastFetched);
1512 25 : rv |= statement->BindInt64ByIndex(8, rec.lastModified);
1513 25 : rv |= statement->BindInt64ByIndex(9, rec.expirationTime);
1514 25 : NS_ENSURE_SUCCESS(rv, rv);
1515 :
1516 : bool hasRows;
1517 25 : rv = statement->ExecuteStep(&hasRows);
1518 25 : NS_ENSURE_SUCCESS(rv, rv);
1519 25 : NS_ASSERTION(!hasRows, "INSERT should not result in output");
1520 :
1521 25 : entry->SetData(binding);
1522 25 : return NS_OK;
1523 : }
1524 :
1525 : void
1526 0 : nsOfflineCacheDevice::DoomEntry(nsCacheEntry *entry)
1527 : {
1528 0 : LOG(("nsOfflineCacheDevice::DoomEntry [key=%s]\n", entry->Key()->get()));
1529 :
1530 : // This method is called to inform us that we should mark the entry to be
1531 : // deleted when it is no longer in use.
1532 :
1533 : // We can go ahead and delete the corresponding row in our table,
1534 : // but we must not delete the file on disk until we are deactivated.
1535 :
1536 0 : DeleteEntry(entry, false);
1537 0 : }
1538 :
1539 : nsresult
1540 6 : nsOfflineCacheDevice::OpenInputStreamForEntry(nsCacheEntry *entry,
1541 : nsCacheAccessMode mode,
1542 : PRUint32 offset,
1543 : nsIInputStream **result)
1544 : {
1545 6 : LOG(("nsOfflineCacheDevice::OpenInputStreamForEntry [key=%s]\n",
1546 : entry->Key()->get()));
1547 :
1548 6 : *result = nsnull;
1549 :
1550 6 : NS_ENSURE_TRUE(!offset || (offset < entry->DataSize()), NS_ERROR_INVALID_ARG);
1551 :
1552 : // return an input stream to the entry's data file. the stream
1553 : // may be read on a background thread.
1554 :
1555 6 : nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1556 6 : NS_ENSURE_STATE(binding);
1557 :
1558 12 : nsCOMPtr<nsIInputStream> in;
1559 6 : NS_NewLocalFileInputStream(getter_AddRefs(in), binding->mDataFile, PR_RDONLY);
1560 6 : if (!in)
1561 0 : return NS_ERROR_UNEXPECTED;
1562 :
1563 : // respect |offset| param
1564 6 : if (offset != 0)
1565 : {
1566 0 : nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(in);
1567 0 : NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
1568 :
1569 0 : seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1570 : }
1571 :
1572 6 : in.swap(*result);
1573 6 : return NS_OK;
1574 : }
1575 :
1576 : nsresult
1577 25 : nsOfflineCacheDevice::OpenOutputStreamForEntry(nsCacheEntry *entry,
1578 : nsCacheAccessMode mode,
1579 : PRUint32 offset,
1580 : nsIOutputStream **result)
1581 : {
1582 25 : LOG(("nsOfflineCacheDevice::OpenOutputStreamForEntry [key=%s]\n",
1583 : entry->Key()->get()));
1584 :
1585 25 : *result = nsnull;
1586 :
1587 25 : NS_ENSURE_TRUE(offset <= entry->DataSize(), NS_ERROR_INVALID_ARG);
1588 :
1589 : // return an output stream to the entry's data file. we can assume
1590 : // that the output stream will only be used on the main thread.
1591 :
1592 25 : nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1593 25 : NS_ENSURE_STATE(binding);
1594 :
1595 50 : nsCOMPtr<nsIOutputStream> out;
1596 25 : NS_NewLocalFileOutputStream(getter_AddRefs(out), binding->mDataFile,
1597 : PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
1598 25 : 00600);
1599 25 : if (!out)
1600 0 : return NS_ERROR_UNEXPECTED;
1601 :
1602 : // respect |offset| param
1603 50 : nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(out);
1604 25 : NS_ENSURE_TRUE(seekable, NS_ERROR_UNEXPECTED);
1605 25 : if (offset != 0)
1606 0 : seekable->Seek(nsISeekableStream::NS_SEEK_SET, offset);
1607 :
1608 : // truncate the file at the given offset
1609 25 : seekable->SetEOF();
1610 :
1611 50 : nsCOMPtr<nsIOutputStream> bufferedOut;
1612 25 : NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out, 16 * 1024);
1613 25 : if (!bufferedOut)
1614 0 : return NS_ERROR_UNEXPECTED;
1615 :
1616 25 : bufferedOut.swap(*result);
1617 25 : return NS_OK;
1618 : }
1619 :
1620 : nsresult
1621 0 : nsOfflineCacheDevice::GetFileForEntry(nsCacheEntry *entry, nsIFile **result)
1622 : {
1623 0 : LOG(("nsOfflineCacheDevice::GetFileForEntry [key=%s]\n",
1624 : entry->Key()->get()));
1625 :
1626 0 : nsOfflineCacheBinding *binding = (nsOfflineCacheBinding *) entry->Data();
1627 0 : NS_ENSURE_STATE(binding);
1628 :
1629 0 : NS_IF_ADDREF(*result = binding->mDataFile);
1630 0 : return NS_OK;
1631 : }
1632 :
1633 : nsresult
1634 50 : nsOfflineCacheDevice::OnDataSizeChange(nsCacheEntry *entry, PRInt32 deltaSize)
1635 : {
1636 50 : LOG(("nsOfflineCacheDevice::OnDataSizeChange [key=%s delta=%d]\n",
1637 : entry->Key()->get(), deltaSize));
1638 :
1639 50 : const PRInt32 DELTA_THRESHOLD = 1<<14; // 16k
1640 :
1641 : // called to notify us of an impending change in the total size of the
1642 : // specified entry.
1643 :
1644 50 : PRUint32 oldSize = entry->DataSize();
1645 50 : NS_ASSERTION(deltaSize >= 0 || PRInt32(oldSize) + deltaSize >= 0, "oops");
1646 50 : PRUint32 newSize = PRInt32(oldSize) + deltaSize;
1647 50 : UpdateEntrySize(entry, newSize);
1648 :
1649 50 : mDeltaCounter += deltaSize; // this may go negative
1650 :
1651 50 : if (mDeltaCounter >= DELTA_THRESHOLD)
1652 : {
1653 0 : if (CacheSize() > mCacheCapacity) {
1654 : // the entry will overrun the cache capacity, doom the entry
1655 : // and abort
1656 : #ifdef DEBUG
1657 : nsresult rv =
1658 : #endif
1659 0 : nsCacheService::DoomEntry(entry);
1660 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "DoomEntry() failed.");
1661 0 : return NS_ERROR_ABORT;
1662 : }
1663 :
1664 0 : mDeltaCounter = 0; // reset counter
1665 : }
1666 :
1667 50 : return NS_OK;
1668 : }
1669 :
1670 : nsresult
1671 4 : nsOfflineCacheDevice::Visit(nsICacheVisitor *visitor)
1672 : {
1673 4 : NS_ENSURE_TRUE(Initialized(), NS_ERROR_NOT_INITIALIZED);
1674 :
1675 : // called to enumerate the offline cache.
1676 :
1677 : nsCOMPtr<nsICacheDeviceInfo> deviceInfo =
1678 8 : new nsOfflineCacheDeviceInfo(this);
1679 :
1680 : bool keepGoing;
1681 : nsresult rv = visitor->VisitDevice(OFFLINE_CACHE_DEVICE_ID, deviceInfo,
1682 4 : &keepGoing);
1683 4 : if (NS_FAILED(rv))
1684 0 : return rv;
1685 :
1686 4 : if (!keepGoing)
1687 4 : return NS_OK;
1688 :
1689 : // SELECT * from moz_cache;
1690 :
1691 : nsOfflineCacheRecord rec;
1692 0 : nsRefPtr<nsOfflineCacheEntryInfo> info = new nsOfflineCacheEntryInfo;
1693 0 : if (!info)
1694 0 : return NS_ERROR_OUT_OF_MEMORY;
1695 0 : info->mRec = &rec;
1696 :
1697 : // XXX may want to list columns explicitly
1698 0 : nsCOMPtr<mozIStorageStatement> statement;
1699 0 : rv = mDB->CreateStatement(
1700 0 : NS_LITERAL_CSTRING("SELECT * FROM moz_cache;"),
1701 0 : getter_AddRefs(statement));
1702 0 : NS_ENSURE_SUCCESS(rv, rv);
1703 :
1704 : bool hasRows;
1705 0 : for (;;)
1706 : {
1707 0 : rv = statement->ExecuteStep(&hasRows);
1708 0 : if (NS_FAILED(rv) || !hasRows)
1709 0 : break;
1710 :
1711 0 : statement->GetSharedUTF8String(0, NULL, &rec.clientID);
1712 0 : statement->GetSharedUTF8String(1, NULL, &rec.key);
1713 0 : statement->GetSharedBlob(2, &rec.metaDataLen,
1714 0 : (const PRUint8 **) &rec.metaData);
1715 0 : rec.generation = statement->AsInt32(3);
1716 0 : rec.flags = statement->AsInt32(4);
1717 0 : rec.dataSize = statement->AsInt32(5);
1718 0 : rec.fetchCount = statement->AsInt32(6);
1719 0 : rec.lastFetched = statement->AsInt64(7);
1720 0 : rec.lastModified = statement->AsInt64(8);
1721 0 : rec.expirationTime = statement->AsInt64(9);
1722 :
1723 : bool keepGoing;
1724 0 : rv = visitor->VisitEntry(OFFLINE_CACHE_DEVICE_ID, info, &keepGoing);
1725 0 : if (NS_FAILED(rv) || !keepGoing)
1726 0 : break;
1727 : }
1728 :
1729 0 : info->mRec = nsnull;
1730 0 : return NS_OK;
1731 : }
1732 :
1733 : nsresult
1734 1 : nsOfflineCacheDevice::EvictEntries(const char *clientID)
1735 : {
1736 1 : LOG(("nsOfflineCacheDevice::EvictEntries [cid=%s]\n",
1737 : clientID ? clientID : ""));
1738 :
1739 : // called to evict all entries matching the given clientID.
1740 :
1741 : // need trigger to fire user defined function after a row is deleted
1742 : // so we can delete the corresponding data file.
1743 2 : EvictionObserver evictionObserver(mDB, mEvictionFunction);
1744 :
1745 2 : nsCOMPtr<mozIStorageStatement> statement;
1746 : nsresult rv;
1747 1 : if (clientID)
1748 : {
1749 0 : rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE ClientID=? AND Flags = 0;"),
1750 0 : getter_AddRefs(statement));
1751 0 : NS_ENSURE_SUCCESS(rv, rv);
1752 :
1753 0 : rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
1754 0 : NS_ENSURE_SUCCESS(rv, rv);
1755 : }
1756 : else
1757 : {
1758 2 : rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache WHERE Flags = 0;"),
1759 2 : getter_AddRefs(statement));
1760 1 : NS_ENSURE_SUCCESS(rv, rv);
1761 : }
1762 :
1763 1 : rv = statement->Execute();
1764 1 : NS_ENSURE_SUCCESS(rv, rv);
1765 :
1766 1 : evictionObserver.Apply();
1767 :
1768 1 : statement = nsnull;
1769 : // Also evict any namespaces associated with this clientID.
1770 1 : if (clientID)
1771 : {
1772 0 : rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces WHERE ClientID=?"),
1773 0 : getter_AddRefs(statement));
1774 0 : NS_ENSURE_SUCCESS(rv, rv);
1775 :
1776 0 : rv = statement->BindUTF8StringByIndex(0, nsDependentCString(clientID));
1777 0 : NS_ENSURE_SUCCESS(rv, rv);
1778 : }
1779 : else
1780 : {
1781 2 : rv = mDB->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM moz_cache_namespaces;"),
1782 2 : getter_AddRefs(statement));
1783 1 : NS_ENSURE_SUCCESS(rv, rv);
1784 : }
1785 :
1786 1 : rv = statement->Execute();
1787 1 : NS_ENSURE_SUCCESS(rv, rv);
1788 :
1789 1 : return NS_OK;
1790 : }
1791 :
1792 : nsresult
1793 24 : nsOfflineCacheDevice::MarkEntry(const nsCString &clientID,
1794 : const nsACString &key,
1795 : PRUint32 typeBits)
1796 : {
1797 24 : LOG(("nsOfflineCacheDevice::MarkEntry [cid=%s, key=%s, typeBits=%d]\n",
1798 : clientID.get(), PromiseFlatCString(key).get(), typeBits));
1799 :
1800 48 : AutoResetStatement statement(mStatement_MarkEntry);
1801 24 : nsresult rv = statement->BindInt32ByIndex(0, typeBits);
1802 24 : NS_ENSURE_SUCCESS(rv, rv);
1803 24 : rv = statement->BindUTF8StringByIndex(1, clientID);
1804 24 : NS_ENSURE_SUCCESS(rv, rv);
1805 24 : rv = statement->BindUTF8StringByIndex(2, key);
1806 24 : NS_ENSURE_SUCCESS(rv, rv);
1807 :
1808 24 : rv = statement->Execute();
1809 24 : NS_ENSURE_SUCCESS(rv, rv);
1810 :
1811 24 : return NS_OK;
1812 : }
1813 :
1814 : nsresult
1815 0 : nsOfflineCacheDevice::UnmarkEntry(const nsCString &clientID,
1816 : const nsACString &key,
1817 : PRUint32 typeBits)
1818 : {
1819 0 : LOG(("nsOfflineCacheDevice::UnmarkEntry [cid=%s, key=%s, typeBits=%d]\n",
1820 : clientID.get(), PromiseFlatCString(key).get(), typeBits));
1821 :
1822 0 : AutoResetStatement statement(mStatement_UnmarkEntry);
1823 0 : nsresult rv = statement->BindInt32ByIndex(0, typeBits);
1824 0 : NS_ENSURE_SUCCESS(rv, rv);
1825 0 : rv = statement->BindUTF8StringByIndex(1, clientID);
1826 0 : NS_ENSURE_SUCCESS(rv, rv);
1827 0 : rv = statement->BindUTF8StringByIndex(2, key);
1828 0 : NS_ENSURE_SUCCESS(rv, rv);
1829 :
1830 0 : rv = statement->Execute();
1831 0 : NS_ENSURE_SUCCESS(rv, rv);
1832 :
1833 : // Remove the entry if it is now empty.
1834 :
1835 0 : EvictionObserver evictionObserver(mDB, mEvictionFunction);
1836 :
1837 0 : AutoResetStatement cleanupStatement(mStatement_CleanupUnmarked);
1838 0 : rv = cleanupStatement->BindUTF8StringByIndex(0, clientID);
1839 0 : NS_ENSURE_SUCCESS(rv, rv);
1840 0 : rv = cleanupStatement->BindUTF8StringByIndex(1, key);
1841 0 : NS_ENSURE_SUCCESS(rv, rv);
1842 :
1843 0 : rv = cleanupStatement->Execute();
1844 0 : NS_ENSURE_SUCCESS(rv, rv);
1845 :
1846 0 : evictionObserver.Apply();
1847 :
1848 0 : return NS_OK;
1849 : }
1850 :
1851 : nsresult
1852 8 : nsOfflineCacheDevice::GetMatchingNamespace(const nsCString &clientID,
1853 : const nsACString &key,
1854 : nsIApplicationCacheNamespace **out)
1855 : {
1856 8 : LOG(("nsOfflineCacheDevice::GetMatchingNamespace [cid=%s, key=%s]\n",
1857 : clientID.get(), PromiseFlatCString(key).get()));
1858 :
1859 : nsresult rv;
1860 :
1861 16 : AutoResetStatement statement(mStatement_FindNamespaceEntry);
1862 :
1863 8 : rv = statement->BindUTF8StringByIndex(0, clientID);
1864 8 : NS_ENSURE_SUCCESS(rv, rv);
1865 8 : rv = statement->BindUTF8StringByIndex(1, key);
1866 8 : NS_ENSURE_SUCCESS(rv, rv);
1867 :
1868 : bool hasRows;
1869 8 : rv = statement->ExecuteStep(&hasRows);
1870 8 : NS_ENSURE_SUCCESS(rv, rv);
1871 :
1872 8 : *out = nsnull;
1873 :
1874 8 : bool found = false;
1875 16 : nsCString nsSpec;
1876 : PRInt32 nsType;
1877 16 : nsCString nsData;
1878 :
1879 8 : while (hasRows)
1880 : {
1881 : PRInt32 itemType;
1882 8 : rv = statement->GetInt32(2, &itemType);
1883 8 : NS_ENSURE_SUCCESS(rv, rv);
1884 :
1885 8 : if (!found || itemType > nsType)
1886 : {
1887 8 : nsType = itemType;
1888 :
1889 8 : rv = statement->GetUTF8String(0, nsSpec);
1890 8 : NS_ENSURE_SUCCESS(rv, rv);
1891 :
1892 8 : rv = statement->GetUTF8String(1, nsData);
1893 8 : NS_ENSURE_SUCCESS(rv, rv);
1894 :
1895 8 : found = true;
1896 : }
1897 :
1898 8 : rv = statement->ExecuteStep(&hasRows);
1899 8 : NS_ENSURE_SUCCESS(rv, rv);
1900 : }
1901 :
1902 8 : if (found) {
1903 : nsCOMPtr<nsIApplicationCacheNamespace> ns =
1904 16 : new nsApplicationCacheNamespace();
1905 8 : if (!ns)
1906 0 : return NS_ERROR_OUT_OF_MEMORY;
1907 8 : rv = ns->Init(nsType, nsSpec, nsData);
1908 8 : NS_ENSURE_SUCCESS(rv, rv);
1909 :
1910 16 : ns.swap(*out);
1911 : }
1912 :
1913 8 : return NS_OK;
1914 : }
1915 :
1916 : nsresult
1917 0 : nsOfflineCacheDevice::CacheOpportunistically(const nsCString &clientID,
1918 : const nsACString &key)
1919 : {
1920 : // XXX: We should also be propagating this cache entry to other matching
1921 : // caches. See bug 444807.
1922 :
1923 0 : return MarkEntry(clientID, key, nsIApplicationCache::ITEM_OPPORTUNISTIC);
1924 : }
1925 :
1926 : nsresult
1927 9 : nsOfflineCacheDevice::GetTypes(const nsCString &clientID,
1928 : const nsACString &key,
1929 : PRUint32 *typeBits)
1930 : {
1931 9 : LOG(("nsOfflineCacheDevice::GetTypes [cid=%s, key=%s]\n",
1932 : clientID.get(), PromiseFlatCString(key).get()));
1933 :
1934 18 : AutoResetStatement statement(mStatement_GetTypes);
1935 9 : nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
1936 9 : NS_ENSURE_SUCCESS(rv, rv);
1937 9 : rv = statement->BindUTF8StringByIndex(1, key);
1938 9 : NS_ENSURE_SUCCESS(rv, rv);
1939 :
1940 : bool hasRows;
1941 9 : rv = statement->ExecuteStep(&hasRows);
1942 9 : NS_ENSURE_SUCCESS(rv, rv);
1943 :
1944 9 : if (!hasRows)
1945 0 : return NS_ERROR_CACHE_KEY_NOT_FOUND;
1946 :
1947 9 : *typeBits = statement->AsInt32(0);
1948 :
1949 9 : return NS_OK;
1950 : }
1951 :
1952 : nsresult
1953 0 : nsOfflineCacheDevice::GatherEntries(const nsCString &clientID,
1954 : PRUint32 typeBits,
1955 : PRUint32 *count,
1956 : char ***keys)
1957 : {
1958 0 : LOG(("nsOfflineCacheDevice::GatherEntries [cid=%s, typeBits=%X]\n",
1959 : clientID.get(), typeBits));
1960 :
1961 0 : AutoResetStatement statement(mStatement_GatherEntries);
1962 0 : nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
1963 0 : NS_ENSURE_SUCCESS(rv, rv);
1964 :
1965 0 : rv = statement->BindInt32ByIndex(1, typeBits);
1966 0 : NS_ENSURE_SUCCESS(rv, rv);
1967 :
1968 0 : return RunSimpleQuery(mStatement_GatherEntries, 0, count, keys);
1969 : }
1970 :
1971 : nsresult
1972 8 : nsOfflineCacheDevice::AddNamespace(const nsCString &clientID,
1973 : nsIApplicationCacheNamespace *ns)
1974 : {
1975 16 : nsCString namespaceSpec;
1976 8 : nsresult rv = ns->GetNamespaceSpec(namespaceSpec);
1977 8 : NS_ENSURE_SUCCESS(rv, rv);
1978 :
1979 16 : nsCString data;
1980 8 : rv = ns->GetData(data);
1981 8 : NS_ENSURE_SUCCESS(rv, rv);
1982 :
1983 : PRUint32 itemType;
1984 8 : rv = ns->GetItemType(&itemType);
1985 8 : NS_ENSURE_SUCCESS(rv, rv);
1986 :
1987 8 : LOG(("nsOfflineCacheDevice::AddNamespace [cid=%s, ns=%s, data=%s, type=%d]",
1988 : clientID.get(), namespaceSpec.get(), data.get(), itemType));
1989 :
1990 16 : AutoResetStatement statement(mStatement_InsertNamespaceEntry);
1991 :
1992 8 : rv = statement->BindUTF8StringByIndex(0, clientID);
1993 8 : NS_ENSURE_SUCCESS(rv, rv);
1994 :
1995 8 : rv = statement->BindUTF8StringByIndex(1, namespaceSpec);
1996 8 : NS_ENSURE_SUCCESS(rv, rv);
1997 :
1998 8 : rv = statement->BindUTF8StringByIndex(2, data);
1999 8 : NS_ENSURE_SUCCESS(rv, rv);
2000 :
2001 8 : rv = statement->BindInt32ByIndex(3, itemType);
2002 8 : NS_ENSURE_SUCCESS(rv, rv);
2003 :
2004 8 : rv = statement->Execute();
2005 8 : NS_ENSURE_SUCCESS(rv, rv);
2006 :
2007 8 : return NS_OK;
2008 : }
2009 :
2010 : nsresult
2011 0 : nsOfflineCacheDevice::GetUsage(const nsACString &clientID,
2012 : PRUint32 *usage)
2013 : {
2014 0 : LOG(("nsOfflineCacheDevice::GetUsage [cid=%s]\n",
2015 : PromiseFlatCString(clientID).get()));
2016 :
2017 0 : *usage = 0;
2018 :
2019 0 : AutoResetStatement statement(mStatement_ApplicationCacheSize);
2020 :
2021 0 : nsresult rv = statement->BindUTF8StringByIndex(0, clientID);
2022 0 : NS_ENSURE_SUCCESS(rv, rv);
2023 :
2024 : bool hasRows;
2025 0 : rv = statement->ExecuteStep(&hasRows);
2026 0 : NS_ENSURE_SUCCESS(rv, rv);
2027 :
2028 0 : if (!hasRows)
2029 0 : return NS_OK;
2030 :
2031 0 : *usage = static_cast<PRUint32>(statement->AsInt32(0));
2032 :
2033 0 : return NS_OK;
2034 : }
2035 :
2036 : NS_IMETHODIMP
2037 0 : nsOfflineCacheDevice::GetGroups(PRUint32 *count,
2038 : char ***keys)
2039 : {
2040 :
2041 0 : LOG(("nsOfflineCacheDevice::GetGroups"));
2042 :
2043 0 : AutoResetStatement statement(mStatement_EnumerateGroups);
2044 0 : return RunSimpleQuery(mStatement_EnumerateGroups, 0, count, keys);
2045 : }
2046 :
2047 : nsresult
2048 0 : nsOfflineCacheDevice::RunSimpleQuery(mozIStorageStatement * statement,
2049 : PRUint32 resultIndex,
2050 : PRUint32 * count,
2051 : char *** values)
2052 : {
2053 : bool hasRows;
2054 0 : nsresult rv = statement->ExecuteStep(&hasRows);
2055 0 : NS_ENSURE_SUCCESS(rv, rv);
2056 :
2057 0 : nsTArray<nsCString> valArray;
2058 0 : while (hasRows)
2059 : {
2060 : PRUint32 length;
2061 : valArray.AppendElement(
2062 0 : nsDependentCString(statement->AsSharedUTF8String(resultIndex, &length)));
2063 :
2064 0 : rv = statement->ExecuteStep(&hasRows);
2065 0 : NS_ENSURE_SUCCESS(rv, rv);
2066 : }
2067 :
2068 0 : *count = valArray.Length();
2069 0 : char **ret = static_cast<char **>(NS_Alloc(*count * sizeof(char*)));
2070 0 : if (!ret) return NS_ERROR_OUT_OF_MEMORY;
2071 :
2072 0 : for (PRUint32 i = 0; i < *count; i++) {
2073 0 : ret[i] = NS_strdup(valArray[i].get());
2074 0 : if (!ret[i]) {
2075 0 : NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(i, ret);
2076 0 : return NS_ERROR_OUT_OF_MEMORY;
2077 : }
2078 : }
2079 :
2080 0 : *values = ret;
2081 :
2082 0 : return NS_OK;
2083 : }
2084 :
2085 : NS_IMETHODIMP
2086 8 : nsOfflineCacheDevice::CreateApplicationCache(const nsACString &group,
2087 : nsIApplicationCache **out)
2088 : {
2089 8 : *out = nsnull;
2090 :
2091 16 : nsCString clientID;
2092 : // Some characters are special in the clientID. Escape the groupID
2093 : // before putting it in to the client key.
2094 8 : if (!NS_Escape(nsCString(group), clientID, url_Path)) {
2095 0 : return NS_ERROR_OUT_OF_MEMORY;
2096 : }
2097 :
2098 8 : PRTime now = PR_Now();
2099 :
2100 : // Include the timestamp to guarantee uniqueness across runs, and
2101 : // the gNextTemporaryClientID for uniqueness within a second.
2102 : clientID.Append(nsPrintfCString(64, "|%016lld|%d",
2103 : now / PR_USEC_PER_SEC,
2104 8 : gNextTemporaryClientID++));
2105 :
2106 : nsCOMPtr<nsIApplicationCache> cache = new nsApplicationCache(this,
2107 : group,
2108 16 : clientID);
2109 8 : if (!cache)
2110 0 : return NS_ERROR_OUT_OF_MEMORY;
2111 :
2112 16 : nsCOMPtr<nsIWeakReference> weak = do_GetWeakReference(cache);
2113 8 : if (!weak)
2114 0 : return NS_ERROR_OUT_OF_MEMORY;
2115 :
2116 8 : mCaches.Put(clientID, weak);
2117 :
2118 8 : cache.swap(*out);
2119 :
2120 8 : return NS_OK;
2121 : }
2122 :
2123 : NS_IMETHODIMP
2124 8 : nsOfflineCacheDevice::GetApplicationCache(const nsACString &clientID,
2125 : nsIApplicationCache **out)
2126 : {
2127 8 : *out = nsnull;
2128 :
2129 16 : nsCOMPtr<nsIApplicationCache> cache;
2130 :
2131 16 : nsWeakPtr weak;
2132 8 : if (mCaches.Get(clientID, getter_AddRefs(weak)))
2133 6 : cache = do_QueryReferent(weak);
2134 :
2135 8 : if (!cache)
2136 : {
2137 4 : nsCString group;
2138 2 : nsresult rv = GetGroupForCache(clientID, group);
2139 2 : NS_ENSURE_SUCCESS(rv, rv);
2140 :
2141 2 : if (group.IsEmpty()) {
2142 0 : return NS_OK;
2143 : }
2144 :
2145 2 : cache = new nsApplicationCache(this, group, clientID);
2146 2 : weak = do_GetWeakReference(cache);
2147 2 : if (!weak)
2148 0 : return NS_ERROR_OUT_OF_MEMORY;
2149 :
2150 4 : mCaches.Put(clientID, weak);
2151 : }
2152 :
2153 8 : cache.swap(*out);
2154 :
2155 8 : return NS_OK;
2156 : }
2157 :
2158 : NS_IMETHODIMP
2159 8 : nsOfflineCacheDevice::GetActiveCache(const nsACString &group,
2160 : nsIApplicationCache **out)
2161 : {
2162 8 : *out = nsnull;
2163 :
2164 : nsCString *clientID;
2165 8 : if (mActiveCachesByGroup.Get(group, &clientID))
2166 0 : return GetApplicationCache(*clientID, out);
2167 :
2168 8 : return NS_OK;
2169 : }
2170 :
2171 : NS_IMETHODIMP
2172 0 : nsOfflineCacheDevice::DeactivateGroup(const nsACString &group)
2173 : {
2174 0 : nsCString *active = nsnull;
2175 :
2176 0 : AutoResetStatement statement(mStatement_DeactivateGroup);
2177 0 : nsresult rv = statement->BindUTF8StringByIndex(0, group);
2178 0 : NS_ENSURE_SUCCESS(rv, rv);
2179 :
2180 0 : rv = statement->Execute();
2181 0 : NS_ENSURE_SUCCESS(rv, rv);
2182 :
2183 0 : if (mActiveCachesByGroup.Get(group, &active))
2184 : {
2185 0 : mActiveCaches.RemoveEntry(*active);
2186 0 : mActiveCachesByGroup.Remove(group);
2187 0 : active = nsnull;
2188 : }
2189 :
2190 0 : return NS_OK;
2191 : }
2192 :
2193 : bool
2194 8 : nsOfflineCacheDevice::CanUseCache(nsIURI *keyURI, const nsCString &clientID)
2195 : {
2196 8 : if (mActiveCaches.Contains(clientID)) {
2197 16 : nsCAutoString groupID;
2198 8 : nsresult rv = GetGroupForCache(clientID, groupID);
2199 8 : NS_ENSURE_SUCCESS(rv, false);
2200 :
2201 16 : nsCOMPtr<nsIURI> groupURI;
2202 8 : rv = NS_NewURI(getter_AddRefs(groupURI), groupID);
2203 8 : if (NS_SUCCEEDED(rv)) {
2204 : // When we are choosing an initial cache to load the top
2205 : // level document from, the URL of that document must have
2206 : // the same origin as the manifest, according to the spec.
2207 : // The following check is here because explicit, fallback
2208 : // and dynamic entries might have origin different from the
2209 : // manifest origin.
2210 8 : if (NS_SecurityCompareURIs(keyURI, groupURI,
2211 8 : GetStrictFileOriginPolicy()))
2212 8 : return true;
2213 : }
2214 : }
2215 :
2216 0 : return false;
2217 : }
2218 :
2219 :
2220 : NS_IMETHODIMP
2221 32 : nsOfflineCacheDevice::ChooseApplicationCache(const nsACString &key,
2222 : nsIApplicationCache **out)
2223 : {
2224 32 : *out = nsnull;
2225 :
2226 64 : nsCOMPtr<nsIURI> keyURI;
2227 32 : nsresult rv = NS_NewURI(getter_AddRefs(keyURI), key);
2228 32 : NS_ENSURE_SUCCESS(rv, rv);
2229 :
2230 : // First try to find a matching cache entry.
2231 64 : AutoResetStatement statement(mStatement_FindClient);
2232 32 : rv = statement->BindUTF8StringByIndex(0, key);
2233 32 : NS_ENSURE_SUCCESS(rv, rv);
2234 :
2235 : bool hasRows;
2236 32 : rv = statement->ExecuteStep(&hasRows);
2237 32 : NS_ENSURE_SUCCESS(rv, rv);
2238 :
2239 64 : while (hasRows) {
2240 : PRInt32 itemType;
2241 0 : rv = statement->GetInt32(1, &itemType);
2242 0 : NS_ENSURE_SUCCESS(rv, rv);
2243 :
2244 0 : if (!(itemType & nsIApplicationCache::ITEM_FOREIGN)) {
2245 0 : nsCAutoString clientID;
2246 0 : rv = statement->GetUTF8String(0, clientID);
2247 0 : NS_ENSURE_SUCCESS(rv, rv);
2248 :
2249 0 : if (CanUseCache(keyURI, clientID)) {
2250 0 : return GetApplicationCache(clientID, out);
2251 : }
2252 : }
2253 :
2254 0 : rv = statement->ExecuteStep(&hasRows);
2255 0 : NS_ENSURE_SUCCESS(rv, rv);
2256 : }
2257 :
2258 : // OK, we didn't find an exact match. Search for a client with a
2259 : // matching namespace.
2260 :
2261 64 : AutoResetStatement nsstatement(mStatement_FindClientByNamespace);
2262 :
2263 32 : rv = nsstatement->BindUTF8StringByIndex(0, key);
2264 32 : NS_ENSURE_SUCCESS(rv, rv);
2265 :
2266 32 : rv = nsstatement->ExecuteStep(&hasRows);
2267 32 : NS_ENSURE_SUCCESS(rv, rv);
2268 :
2269 64 : while (hasRows)
2270 : {
2271 : PRInt32 itemType;
2272 8 : rv = nsstatement->GetInt32(1, &itemType);
2273 8 : NS_ENSURE_SUCCESS(rv, rv);
2274 :
2275 : // Don't associate with a cache based solely on a whitelist entry
2276 8 : if (!(itemType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) {
2277 16 : nsCAutoString clientID;
2278 8 : rv = nsstatement->GetUTF8String(0, clientID);
2279 8 : NS_ENSURE_SUCCESS(rv, rv);
2280 :
2281 8 : if (CanUseCache(keyURI, clientID)) {
2282 8 : return GetApplicationCache(clientID, out);
2283 : }
2284 : }
2285 :
2286 0 : rv = nsstatement->ExecuteStep(&hasRows);
2287 0 : NS_ENSURE_SUCCESS(rv, rv);
2288 : }
2289 :
2290 24 : return NS_OK;
2291 : }
2292 :
2293 : NS_IMETHODIMP
2294 0 : nsOfflineCacheDevice::CacheOpportunistically(nsIApplicationCache* cache,
2295 : const nsACString &key)
2296 : {
2297 0 : NS_ENSURE_ARG_POINTER(cache);
2298 :
2299 : nsresult rv;
2300 :
2301 0 : nsCAutoString clientID;
2302 0 : rv = cache->GetClientID(clientID);
2303 0 : NS_ENSURE_SUCCESS(rv, rv);
2304 :
2305 0 : return CacheOpportunistically(clientID, key);
2306 : }
2307 :
2308 : nsresult
2309 8 : nsOfflineCacheDevice::ActivateCache(const nsCSubstring &group,
2310 : const nsCSubstring &clientID)
2311 : {
2312 16 : AutoResetStatement statement(mStatement_ActivateClient);
2313 8 : nsresult rv = statement->BindUTF8StringByIndex(0, group);
2314 8 : NS_ENSURE_SUCCESS(rv, rv);
2315 8 : rv = statement->BindUTF8StringByIndex(1, clientID);
2316 8 : NS_ENSURE_SUCCESS(rv, rv);
2317 8 : rv = statement->BindInt32ByIndex(2, SecondsFromPRTime(PR_Now()));
2318 8 : NS_ENSURE_SUCCESS(rv, rv);
2319 :
2320 8 : rv = statement->Execute();
2321 8 : NS_ENSURE_SUCCESS(rv, rv);
2322 :
2323 : nsCString *active;
2324 8 : if (mActiveCachesByGroup.Get(group, &active))
2325 : {
2326 0 : mActiveCaches.RemoveEntry(*active);
2327 0 : mActiveCachesByGroup.Remove(group);
2328 0 : active = nsnull;
2329 : }
2330 :
2331 8 : if (!clientID.IsEmpty())
2332 : {
2333 8 : mActiveCaches.PutEntry(clientID);
2334 8 : mActiveCachesByGroup.Put(group, new nsCString(clientID));
2335 : }
2336 :
2337 8 : return NS_OK;
2338 : }
2339 :
2340 : bool
2341 2 : nsOfflineCacheDevice::IsActiveCache(const nsCSubstring &group,
2342 : const nsCSubstring &clientID)
2343 : {
2344 2 : nsCString *active = nsnull;
2345 2 : return mActiveCachesByGroup.Get(group, &active) && *active == clientID;
2346 : }
2347 :
2348 : nsresult
2349 10 : nsOfflineCacheDevice::GetGroupForCache(const nsACString &clientID,
2350 : nsCString &out)
2351 : {
2352 10 : out.Assign(clientID);
2353 10 : out.Truncate(out.FindChar('|'));
2354 10 : NS_UnescapeURL(out);
2355 :
2356 10 : return NS_OK;
2357 : }
2358 :
2359 : /**
2360 : * Preference accessors
2361 : */
2362 :
2363 : void
2364 15 : nsOfflineCacheDevice::SetCacheParentDirectory(nsILocalFile *parentDir)
2365 : {
2366 15 : if (Initialized())
2367 : {
2368 0 : NS_ERROR("cannot switch cache directory once initialized");
2369 0 : return;
2370 : }
2371 :
2372 15 : if (!parentDir)
2373 : {
2374 0 : mCacheDirectory = nsnull;
2375 0 : return;
2376 : }
2377 :
2378 : // ensure parent directory exists
2379 15 : nsresult rv = EnsureDir(parentDir);
2380 15 : if (NS_FAILED(rv))
2381 : {
2382 0 : NS_WARNING("unable to create parent directory");
2383 0 : return;
2384 : }
2385 :
2386 : // cache dir may not exist, but that's ok
2387 30 : nsCOMPtr<nsIFile> dir;
2388 15 : rv = parentDir->Clone(getter_AddRefs(dir));
2389 15 : if (NS_FAILED(rv))
2390 : return;
2391 15 : rv = dir->AppendNative(NS_LITERAL_CSTRING("OfflineCache"));
2392 15 : if (NS_FAILED(rv))
2393 : return;
2394 :
2395 15 : mCacheDirectory = do_QueryInterface(dir);
2396 : }
2397 :
2398 : void
2399 15 : nsOfflineCacheDevice::SetCapacity(PRUint32 capacity)
2400 : {
2401 15 : mCacheCapacity = capacity * 1024;
2402 15 : }
|