netwerk/cache/nsDiskCacheDeviceSQL.cpp

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

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

mercurial