LCOV - code coverage report
Current view: directory - netwerk/cache - nsDiskCacheDeviceSQL.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 1051 711 67.6 %
Date: 2012-06-02 Functions: 126 84 66.7 %

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

Generated by: LCOV version 1.7