From a1572b7d02ecda7218634ecc5d265d9952f0c902 Mon Sep 17 00:00:00 2001 From: Jan Varga Date: Fri, 16 Dec 2011 08:34:24 +0100 Subject: [PATCH] Bug 661877 - Enable storing files in IndexedDB. r=bent --- browser/base/content/pageinfo/permissions.js | 2 +- content/base/public/nsDOMFile.h | 83 ++- content/base/public/nsIDOMFile.idl | 27 +- content/base/src/nsDOMFile.cpp | 70 ++ db/sqlite3/README.MOZILLA | 2 +- db/sqlite3/src/sqlite.def | 1 + db/sqlite3/src/test_quota.c | 704 ++++++++++++++++-- db/sqlite3/src/test_quota.h | 209 ++++++ dom/base/StructuredCloneTags.h | 1 + dom/base/nsDOMWindowUtils.cpp | 50 ++ dom/indexedDB/AsyncConnectionHelper.cpp | 27 +- dom/indexedDB/AsyncConnectionHelper.h | 5 +- dom/indexedDB/CheckPermissionsHelper.cpp | 3 - dom/indexedDB/FileInfo.cpp | 138 ++++ dom/indexedDB/FileInfo.h | 155 ++++ dom/indexedDB/FileManager.cpp | 309 ++++++++ dom/indexedDB/FileManager.h | 136 ++++ dom/indexedDB/IDBCursor.cpp | 31 +- dom/indexedDB/IDBCursor.h | 6 +- dom/indexedDB/IDBDatabase.cpp | 5 +- dom/indexedDB/IDBDatabase.h | 12 +- dom/indexedDB/IDBIndex.cpp | 67 +- dom/indexedDB/IDBObjectStore.cpp | 627 ++++++++++++---- dom/indexedDB/IDBObjectStore.h | 48 +- dom/indexedDB/IDBTransaction.cpp | 273 ++++++- dom/indexedDB/IDBTransaction.h | 111 ++- dom/indexedDB/IndexedDatabase.h | 26 + dom/indexedDB/IndexedDatabaseManager.cpp | 467 ++++++++++-- dom/indexedDB/IndexedDatabaseManager.h | 57 +- dom/indexedDB/Makefile.in | 5 + dom/indexedDB/OpenDatabaseHelper.cpp | 532 ++++++++----- dom/indexedDB/OpenDatabaseHelper.h | 8 + dom/indexedDB/TransactionThreadPool.h | 2 +- dom/indexedDB/nsIIndexedDatabaseManager.idl | 5 +- dom/indexedDB/test/Makefile.in | 10 + dom/indexedDB/test/file.js | 225 ++++++ dom/indexedDB/test/helpers.js | 32 +- dom/indexedDB/test/test_file_array.html | 84 +++ .../test_file_cross_database_copying.html | 106 +++ dom/indexedDB/test/test_file_delete.html | 134 ++++ dom/indexedDB/test/test_file_os_delete.html | 86 +++ .../test/test_file_put_get_values.html | 101 +++ dom/indexedDB/test/test_file_quota.html | 75 ++ .../test/test_file_resurrection_delete.html | 136 ++++ ...t_file_resurrection_transaction_abort.html | 93 +++ dom/indexedDB/test/test_file_sharing.html | 106 +++ .../test/test_file_transaction_abort.html | 78 ++ dom/interfaces/base/nsIDOMWindowUtils.idl | 17 +- storage/public/mozIStorageConnection.idl | 19 +- .../mozIStorageServiceQuotaManagement.idl | 4 +- storage/src/FileSystemModule.cpp | 336 +++++++++ storage/src/FileSystemModule.h | 53 ++ storage/src/Makefile.in | 1 + storage/src/mozStorageConnection.cpp | 43 ++ storage/src/mozStorageService.cpp | 8 +- .../tests/SimpleTest/specialpowersAPI.js | 21 + 56 files changed, 5423 insertions(+), 549 deletions(-) create mode 100644 db/sqlite3/src/test_quota.h create mode 100644 dom/indexedDB/FileInfo.cpp create mode 100644 dom/indexedDB/FileInfo.h create mode 100644 dom/indexedDB/FileManager.cpp create mode 100644 dom/indexedDB/FileManager.h create mode 100644 dom/indexedDB/test/file.js create mode 100644 dom/indexedDB/test/test_file_array.html create mode 100644 dom/indexedDB/test/test_file_cross_database_copying.html create mode 100644 dom/indexedDB/test/test_file_delete.html create mode 100644 dom/indexedDB/test/test_file_os_delete.html create mode 100644 dom/indexedDB/test/test_file_put_get_values.html create mode 100644 dom/indexedDB/test/test_file_quota.html create mode 100644 dom/indexedDB/test/test_file_resurrection_delete.html create mode 100644 dom/indexedDB/test/test_file_resurrection_transaction_abort.html create mode 100644 dom/indexedDB/test/test_file_sharing.html create mode 100644 dom/indexedDB/test/test_file_transaction_abort.html create mode 100644 storage/src/FileSystemModule.cpp create mode 100644 storage/src/FileSystemModule.h diff --git a/browser/base/content/pageinfo/permissions.js b/browser/base/content/pageinfo/permissions.js index 00d954fb8e5..286c3e1ac54 100644 --- a/browser/base/content/pageinfo/permissions.js +++ b/browser/base/content/pageinfo/permissions.js @@ -226,7 +226,7 @@ function onIndexedDBClear() initIndexedDBRow(); } -function onIndexedDBUsageCallback(uri, usage) +function onIndexedDBUsageCallback(uri, usage, fileUsage) { if (!uri.equals(gPermURI)) { throw new Error("Callback received for bad URI: " + uri); diff --git a/content/base/public/nsDOMFile.h b/content/base/public/nsDOMFile.h index 8c23e19700c..b3203115d9e 100644 --- a/content/base/public/nsDOMFile.h +++ b/content/base/public/nsDOMFile.h @@ -53,6 +53,9 @@ #include "nsIXMLHttpRequest.h" #include "prmem.h" #include "nsAutoPtr.h" +#include "mozilla/dom/indexedDB/FileInfo.h" +#include "mozilla/dom/indexedDB/FileManager.h" +#include "mozilla/dom/indexedDB/IndexedDatabaseManager.h" #include "mozilla/GuardObjects.h" @@ -67,12 +70,13 @@ class nsIBlobBuilder; nsresult NS_NewBlobBuilder(nsISupports* *aSupports); +using namespace mozilla::dom; + class nsDOMFileBase : public nsIDOMFile, public nsIXHRSendable, public nsIMutable { public: - nsDOMFileBase(const nsAString& aName, const nsAString& aContentType, PRUint64 aLength) : mIsFile(true), mImmutable(false), mContentType(aContentType), @@ -119,6 +123,21 @@ protected: return mLength == PR_UINT64_MAX; } + virtual bool IsStoredFile() + { + return false; + } + + virtual bool IsWholeFile() + { + NS_NOTREACHED("Should only be called on dom blobs backed by files!"); + return false; + } + + indexedDB::FileInfo* + GetFileInfoInternal(indexedDB::FileManager* aFileManager, + PRUint32 aStartIndex); + bool mIsFile; bool mImmutable; nsString mContentType; @@ -126,6 +145,9 @@ protected: PRUint64 mStart; PRUint64 mLength; + + // Protected by IndexedDatabaseManager::FileMutex() + nsTArray > mFileInfos; }; class nsDOMFileFile : public nsDOMFileBase, @@ -135,7 +157,7 @@ public: // Create as a file nsDOMFileFile(nsIFile *aFile) : nsDOMFileBase(EmptyString(), EmptyString(), PR_UINT64_MAX), - mFile(aFile), mWholeFile(true) + mFile(aFile), mWholeFile(true), mStoredFile(false) { NS_ASSERTION(mFile, "must have file"); // Lazily get the content type and size @@ -147,16 +169,37 @@ public: nsDOMFileFile(nsIFile *aFile, const nsAString& aContentType, nsISupports *aCacheToken = nsnull) : nsDOMFileBase(aContentType, PR_UINT64_MAX), - mFile(aFile), mWholeFile(true), + mFile(aFile), mWholeFile(true), mStoredFile(false), mCacheToken(aCacheToken) { NS_ASSERTION(mFile, "must have file"); } + // Create as a stored file + nsDOMFileFile(const nsAString& aName, const nsAString& aContentType, + PRUint64 aLength, nsIFile* aFile, + indexedDB::FileInfo* aFileInfo) + : nsDOMFileBase(aName, aContentType, aLength), + mFile(aFile), mWholeFile(true), mStoredFile(true) + { + NS_ASSERTION(mFile, "must have file"); + mFileInfos.AppendElement(aFileInfo); + } + + // Create as a stored blob + nsDOMFileFile(const nsAString& aContentType, PRUint64 aLength, + nsIFile* aFile, indexedDB::FileInfo* aFileInfo) + : nsDOMFileBase(aContentType, aLength), + mFile(aFile), mWholeFile(true), mStoredFile(true) + { + NS_ASSERTION(mFile, "must have file"); + mFileInfos.AppendElement(aFileInfo); + } + // Create as a file to be later initialized nsDOMFileFile() : nsDOMFileBase(EmptyString(), EmptyString(), PR_UINT64_MAX), - mWholeFile(true) + mWholeFile(true), mStoredFile(false) { // Lazily get the content type and size mContentType.SetIsVoid(true); @@ -188,17 +231,47 @@ protected: const nsAString& aContentType) : nsDOMFileBase(aContentType, aOther->mStart + aStart, aLength), mFile(aOther->mFile), mWholeFile(false), - mCacheToken(aOther->mCacheToken) + mStoredFile(aOther->mStoredFile), mCacheToken(aOther->mCacheToken) { NS_ASSERTION(mFile, "must have file"); mImmutable = aOther->mImmutable; + + if (mStoredFile) { + indexedDB::FileInfo* fileInfo; + + if (!indexedDB::IndexedDatabaseManager::IsClosed()) { + indexedDB::IndexedDatabaseManager::FileMutex().Lock(); + } + + NS_ASSERTION(!aOther->mFileInfos.IsEmpty(), + "A stored file must have at least one file info!"); + + fileInfo = aOther->mFileInfos.ElementAt(0); + + if (!indexedDB::IndexedDatabaseManager::IsClosed()) { + indexedDB::IndexedDatabaseManager::FileMutex().Unlock(); + } + + mFileInfos.AppendElement(fileInfo); + } } virtual already_AddRefed CreateSlice(PRUint64 aStart, PRUint64 aLength, const nsAString& aContentType); + virtual bool IsStoredFile() + { + return mStoredFile; + } + + virtual bool IsWholeFile() + { + return mWholeFile; + } + nsCOMPtr mFile; bool mWholeFile; + bool mStoredFile; nsCOMPtr mCacheToken; }; diff --git a/content/base/public/nsIDOMFile.idl b/content/base/public/nsIDOMFile.idl index a5604985bfb..923096f11ec 100644 --- a/content/base/public/nsIDOMFile.idl +++ b/content/base/public/nsIDOMFile.idl @@ -39,15 +39,28 @@ %{C++ #include "jsapi.h" + +namespace mozilla { +namespace dom { +namespace indexedDB { +class FileInfo; +class FileManager; +} +} +} + %} +[ptr] native FileInfo(mozilla::dom::indexedDB::FileInfo); +[ptr] native FileManager(mozilla::dom::indexedDB::FileManager); + interface nsIDOMFileError; interface nsIInputStream; interface nsIURI; interface nsIPrincipal; interface nsIDOMBlob; -[scriptable, builtinclass, uuid(d5237f31-443a-460b-9e42-449a135346f0)] +[scriptable, builtinclass, uuid(f62c6887-e3bc-495a-802c-287e12e969a0)] interface nsIDOMBlob : nsISupports { readonly attribute unsigned long long size; @@ -61,6 +74,18 @@ interface nsIDOMBlob : nsISupports [optional_argc] nsIDOMBlob mozSlice([optional] in long long start, [optional] in long long end, [optional] in DOMString contentType); + + // Get internal id of stored file. Returns -1 if it is not a stored file. + // Intended only for testing. It can be called on any thread. + [notxpcom] long long getFileId(); + + // Called when the blob was successfully stored in a database or when + // the blob is initialized from a database. It can be called on any thread. + [notxpcom] void addFileInfo(in FileInfo aFileInfo); + + // Called before the blob is stored in a database to decide if it can be + // shared or needs to be copied. It can be called on any thread. + [notxpcom] FileInfo getFileInfo(in FileManager aFileManager); }; [scriptable, builtinclass, uuid(b096ef67-7b77-47f8-8e70-5d8ee36416bf)] diff --git a/content/base/src/nsDOMFile.cpp b/content/base/src/nsDOMFile.cpp index 0e41ce08310..b0ddfb3ae0c 100644 --- a/content/base/src/nsDOMFile.cpp +++ b/content/base/src/nsDOMFile.cpp @@ -298,6 +298,76 @@ nsDOMFileBase::GetInternalUrl(nsIPrincipal* aPrincipal, nsAString& aURL) return NS_OK; } +NS_IMETHODIMP_(PRInt64) +nsDOMFileBase::GetFileId() +{ + PRInt64 id = -1; + + if (IsStoredFile() && IsWholeFile()) { + if (!indexedDB::IndexedDatabaseManager::IsClosed()) { + indexedDB::IndexedDatabaseManager::FileMutex().Lock(); + } + + NS_ASSERTION(!mFileInfos.IsEmpty(), + "A stored file must have at least one file info!"); + + nsRefPtr& fileInfo = mFileInfos.ElementAt(0); + if (fileInfo) { + id = fileInfo->Id(); + } + + if (!indexedDB::IndexedDatabaseManager::IsClosed()) { + indexedDB::IndexedDatabaseManager::FileMutex().Unlock(); + } + } + + return id; +} + +NS_IMETHODIMP_(void) +nsDOMFileBase::AddFileInfo(indexedDB::FileInfo* aFileInfo) +{ + if (indexedDB::IndexedDatabaseManager::IsClosed()) { + NS_ERROR("Shouldn't be called after shutdown!"); + return; + } + + nsRefPtr fileInfo = aFileInfo; + + MutexAutoLock lock(indexedDB::IndexedDatabaseManager::FileMutex()); + + NS_ASSERTION(!mFileInfos.Contains(aFileInfo), + "Adding the same file info agan?!"); + + nsRefPtr* element = mFileInfos.AppendElement(); + element->swap(fileInfo); +} + +NS_IMETHODIMP_(indexedDB::FileInfo*) +nsDOMFileBase::GetFileInfo(indexedDB::FileManager* aFileManager) +{ + if (indexedDB::IndexedDatabaseManager::IsClosed()) { + NS_ERROR("Shouldn't be called after shutdown!"); + return nsnull; + } + + // A slice created from a stored file must keep the file info alive. + // However, we don't support sharing of slices yet, so the slice must be + // copied again. That's why we have to ignore the first file info. + PRUint32 startIndex = IsStoredFile() && !IsWholeFile() ? 1 : 0; + + MutexAutoLock lock(indexedDB::IndexedDatabaseManager::FileMutex()); + + for (PRUint32 i = startIndex; i < mFileInfos.Length(); i++) { + nsRefPtr& fileInfo = mFileInfos.ElementAt(i); + if (fileInfo->Manager() == aFileManager) { + return fileInfo; + } + } + + return nsnull; +} + NS_IMETHODIMP nsDOMFileBase::GetSendInfo(nsIInputStream** aBody, nsACString& aContentType, diff --git a/db/sqlite3/README.MOZILLA b/db/sqlite3/README.MOZILLA index 1920349f685..28c020f30b7 100644 --- a/db/sqlite3/README.MOZILLA +++ b/db/sqlite3/README.MOZILLA @@ -11,7 +11,7 @@ To move to a new version: Simply copy the sqlite3.h and sqlite3.c files from the amalgamation of sqlite. -Also copy test_quota.c from the full source package. +Also copy test_quota.h and test_quota.c from the full source package. Be sure to update SQLITE_VERSION accordingly in $(topsrcdir)/configure.in as well as the version number at the top of this file. diff --git a/db/sqlite3/src/sqlite.def b/db/sqlite3/src/sqlite.def index e238b26b036..2dcc0e5ea3b 100644 --- a/db/sqlite3/src/sqlite.def +++ b/db/sqlite3/src/sqlite.def @@ -181,6 +181,7 @@ EXPORTS sqlite3_vfs_unregister sqlite3_vfs_register sqlite3_vmprintf + sqlite3_win32_utf8_to_mbcs #ifdef SQLITE_DEBUG sqlite3_mutex_held sqlite3_mutex_notheld diff --git a/db/sqlite3/src/test_quota.c b/db/sqlite3/src/test_quota.c index 6f770a7c83a..4529bd3028d 100644 --- a/db/sqlite3/src/test_quota.c +++ b/db/sqlite3/src/test_quota.c @@ -27,7 +27,7 @@ ** files within the group is less than the new quota, then the write ** continues as if nothing had happened. */ -#include "sqlite3.h" +#include "test_quota.h" #include #include @@ -45,20 +45,6 @@ #endif /* SQLITE_THREADSAFE==0 */ -/* -** For an build without mutexes, no-op the mutex calls. -*/ -#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0 -#define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) -#define sqlite3_mutex_free(X) -#define sqlite3_mutex_enter(X) -#define sqlite3_mutex_try(X) SQLITE_OK -#define sqlite3_mutex_leave(X) -#define sqlite3_mutex_held(X) ((void)(X),1) -#define sqlite3_mutex_notheld(X) ((void)(X),1) -#endif /* SQLITE_THREADSAFE==0 */ - - /************************ Object Definitions ******************************/ /* Forward declaration of all object types */ @@ -125,6 +111,18 @@ struct quotaConn { /* The underlying VFS sqlite3_file is appended to this object */ }; +/* +** An instance of the following object records the state of an +** open file. This object is opaque to all users - the internal +** structure is only visible to the functions below. +*/ +struct quota_FILE { + FILE *f; /* Open stdio file pointer */ + sqlite3_int64 iOfst; /* Current offset into the file */ + quotaFile *pFile; /* The file record in the quota system */ +}; + + /************************* Global Variables **********************************/ /* ** All global variables used by this file are containing within the following @@ -239,9 +237,11 @@ static void quotaGroupDeref(quotaGroup *pGroup){ ** ** [^...] Matches one character not in the enclosed list. ** +** / Matches "/" or "\\" +** */ static int quotaStrglob(const char *zGlob, const char *z){ - int c, c2; + int c, c2, cx; int invert; int seen; @@ -258,8 +258,9 @@ static int quotaStrglob(const char *zGlob, const char *z){ } return (*z)!=0; } + cx = (c=='/') ? '\\' : c; while( (c2 = (*(z++)))!=0 ){ - while( c2!=c ){ + while( c2!=c && c2!=cx ){ c2 = *(z++); if( c2==0 ) return 0; } @@ -297,6 +298,9 @@ static int quotaStrglob(const char *zGlob, const char *z){ c2 = *(zGlob++); } if( c2==0 || (seen ^ invert)==0 ) return 0; + }else if( c=='/' ){ + if( z[0]!='/' && z[0]!='\\' ) return 0; + z++; }else{ if( c!=(*(z++)) ) return 0; } @@ -327,14 +331,131 @@ static sqlite3_file *quotaSubOpen(sqlite3_file *pConn){ /* Find a file in a quota group and return a pointer to that file. ** Return NULL if the file is not in the group. */ -static quotaFile *quotaFindFile(quotaGroup *pGroup, const char *zName){ +static quotaFile *quotaFindFile( + quotaGroup *pGroup, /* Group in which to look for the file */ + const char *zName, /* Full pathname of the file */ + int createFlag /* Try to create the file if not found */ +){ quotaFile *pFile = pGroup->pFiles; while( pFile && strcmp(pFile->zFilename, zName)!=0 ){ pFile = pFile->pNext; } + if( pFile==0 && createFlag ){ + int nName = strlen(zName); + pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 ); + if( pFile ){ + memset(pFile, 0, sizeof(*pFile)); + pFile->zFilename = (char*)&pFile[1]; + memcpy(pFile->zFilename, zName, nName+1); + pFile->pNext = pGroup->pFiles; + if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext; + pFile->ppPrev = &pGroup->pFiles; + pGroup->pFiles = pFile; + pFile->pGroup = pGroup; + } + } return pFile; } +/* +** Figure out if we are dealing with Unix, Windows, or some other +** operating system. After the following block of preprocess macros, +** all of SQLITE_OS_UNIX, SQLITE_OS_WIN, SQLITE_OS_OS2, and SQLITE_OS_OTHER +** will defined to either 1 or 0. One of the four will be 1. The other +** three will be 0. +*/ +#if defined(SQLITE_OS_OTHER) +# if SQLITE_OS_OTHER==1 +# undef SQLITE_OS_UNIX +# define SQLITE_OS_UNIX 0 +# undef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +# undef SQLITE_OS_OS2 +# define SQLITE_OS_OS2 0 +# else +# undef SQLITE_OS_OTHER +# endif +#endif +#if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER) +# define SQLITE_OS_OTHER 0 +# ifndef SQLITE_OS_WIN +# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) \ + || defined(__MINGW32__) || defined(__BORLANDC__) +# define SQLITE_OS_WIN 1 +# define SQLITE_OS_UNIX 0 +# define SQLITE_OS_OS2 0 +# elif defined(__EMX__) || defined(_OS2) || defined(OS2) \ + || defined(_OS2_) || defined(__OS2__) +# define SQLITE_OS_WIN 0 +# define SQLITE_OS_UNIX 0 +# define SQLITE_OS_OS2 1 +# else +# define SQLITE_OS_WIN 0 +# define SQLITE_OS_UNIX 1 +# define SQLITE_OS_OS2 0 +# endif +# else +# define SQLITE_OS_UNIX 0 +# define SQLITE_OS_OS2 0 +# endif +#else +# ifndef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +# endif +#endif + +#if SQLITE_OS_UNIX +# include +#endif +#if SQLITE_OS_WIN +# include +# include +#endif + +/* +** Translate UTF8 to MBCS for use in fopen() calls. Return a pointer to the +** translated text.. Call quota_mbcs_free() to deallocate any memory +** used to store the returned pointer when done. +*/ +static char *quota_utf8_to_mbcs(const char *zUtf8){ +#if SQLITE_OS_WIN + int n; /* Bytes in zUtf8 */ + int nWide; /* number of UTF-16 characters */ + int nMbcs; /* Bytes of MBCS */ + LPWSTR zTmpWide; /* The UTF16 text */ + char *zMbcs; /* The MBCS text */ + int codepage; /* Code page used by fopen() */ + + n = strlen(zUtf8); + nWide = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, NULL, 0); + if( nWide==0 ) return 0; + zTmpWide = (LPWSTR)sqlite3_malloc( (nWide+1)*sizeof(zTmpWide[0]) ); + if( zTmpWide==0 ) return 0; + MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zTmpWide, nWide); + codepage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; + nMbcs = WideCharToMultiByte(codepage, 0, zTmpWide, nWide, 0, 0, 0, 0); + zMbcs = nMbcs ? (char*)sqlite3_malloc( nMbcs+1 ) : 0; + if( zMbcs ){ + WideCharToMultiByte(codepage, 0, zTmpWide, nWide, zMbcs, nMbcs, 0, 0); + } + sqlite3_free(zTmpWide); + return zMbcs; +#else + return (char*)zUtf8; /* No-op on unix */ +#endif +} + +/* +** Deallocate any memory allocated by quota_utf8_to_mbcs(). +*/ +static void quota_mbcs_free(char *zOld){ +#if SQLITE_OS_WIN + sqlite3_free(zOld); +#else + /* No-op on unix */ +#endif +} + /************************* VFS Method Wrappers *****************************/ /* ** This is the xOpen method used for the "quota" VFS. @@ -378,25 +499,13 @@ static int quotaOpen( pSubOpen = quotaSubOpen(pConn); rc = pOrigVfs->xOpen(pOrigVfs, zName, pSubOpen, flags, pOutFlags); if( rc==SQLITE_OK ){ - pFile = quotaFindFile(pGroup, zName); + pFile = quotaFindFile(pGroup, zName, 1); if( pFile==0 ){ - int nName = strlen(zName); - pFile = (quotaFile *)sqlite3_malloc( sizeof(*pFile) + nName + 1 ); - if( pFile==0 ){ - quotaLeave(); - pSubOpen->pMethods->xClose(pSubOpen); - return SQLITE_NOMEM; - } - memset(pFile, 0, sizeof(*pFile)); - pFile->zFilename = (char*)&pFile[1]; - memcpy(pFile->zFilename, zName, nName+1); - pFile->pNext = pGroup->pFiles; - if( pGroup->pFiles ) pGroup->pFiles->ppPrev = &pFile->pNext; - pFile->ppPrev = &pGroup->pFiles; - pGroup->pFiles = pFile; - pFile->pGroup = pGroup; - pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0; + quotaLeave(); + pSubOpen->pMethods->xClose(pSubOpen); + return SQLITE_NOMEM; } + pFile->deleteOnClose = (flags & SQLITE_OPEN_DELETEONCLOSE)!=0; pFile->nRef++; pQuotaOpen->pFile = pFile; if( pSubOpen->pMethods->iVersion==1 ){ @@ -437,7 +546,7 @@ static int quotaDelete( quotaEnter(); pGroup = quotaGroupFind(zName); if( pGroup ){ - pFile = quotaFindFile(pGroup, zName); + pFile = quotaFindFile(pGroup, zName, 0); if( pFile ){ if( pFile->nRef ){ pFile->deleteOnClose = 1; @@ -469,7 +578,10 @@ static int quotaClose(sqlite3_file *pConn){ pFile->nRef--; if( pFile->nRef==0 ){ quotaGroup *pGroup = pFile->pGroup; - if( pFile->deleteOnClose ) quotaRemoveFile(pFile); + if( pFile->deleteOnClose ){ + gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); + quotaRemoveFile(pFile); + } quotaGroupDeref(pGroup); } quotaLeave(); @@ -603,7 +715,13 @@ static int quotaCheckReservedLock(sqlite3_file *pConn, int *pResOut){ */ static int quotaFileControl(sqlite3_file *pConn, int op, void *pArg){ sqlite3_file *pSubOpen = quotaSubOpen(pConn); - return pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); + int rc = pSubOpen->pMethods->xFileControl(pSubOpen, op, pArg); +#if defined(SQLITE_FCNTL_VFSNAME) + if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){ + *(char**)pArg = sqlite3_mprintf("quota/%z", *(char**)pArg); + } +#endif + return rc; } /* Pass xSectorSize requests through to the original VFS unchanged. @@ -819,8 +937,8 @@ int sqlite3_quota_file(const char *zFilename){ int rc; int outFlags = 0; sqlite3_int64 iSize; - fd = (sqlite3_file*) - sqlite3_malloc(gQuota.sThisVfs.szOsFile + gQuota.sThisVfs.mxPathname+1); + fd = (sqlite3_file*)sqlite3_malloc(gQuota.sThisVfs.szOsFile + + gQuota.sThisVfs.mxPathname+1); if( fd==0 ) return SQLITE_NOMEM; zFull = gQuota.sThisVfs.szOsFile + (char*)fd; rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, @@ -838,7 +956,7 @@ int sqlite3_quota_file(const char *zFilename){ quotaEnter(); pGroup = quotaGroupFind(zFull); if( pGroup ){ - pFile = quotaFindFile(pGroup, zFull); + pFile = quotaFindFile(pGroup, zFull, 0); if( pFile ) quotaRemoveFile(pFile); } quotaLeave(); @@ -847,6 +965,220 @@ int sqlite3_quota_file(const char *zFilename){ return rc; } +/* +** Open a potentially quotaed file for I/O. +*/ +quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode){ + quota_FILE *p = 0; + char *zFull = 0; + char *zFullTranslated; + int rc; + quotaGroup *pGroup; + quotaFile *pFile; + + zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1); + if( zFull==0 ) return 0; + rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, + gQuota.sThisVfs.mxPathname+1, zFull); + if( rc ) goto quota_fopen_error; + p = (quota_FILE*)sqlite3_malloc(sizeof(*p)); + if( p==0 ) goto quota_fopen_error; + memset(p, 0, sizeof(*p)); + zFullTranslated = quota_utf8_to_mbcs(zFull); + if( zFullTranslated==0 ) goto quota_fopen_error; + p->f = fopen(zFullTranslated, zMode); + quota_mbcs_free(zFullTranslated); + if( p->f==0 ) goto quota_fopen_error; + quotaEnter(); + pGroup = quotaGroupFind(zFull); + if( pGroup ){ + pFile = quotaFindFile(pGroup, zFull, 1); + if( pFile==0 ){ + quotaLeave(); + goto quota_fopen_error; + } + pFile->nRef++; + p->pFile = pFile; + } + quotaLeave(); + sqlite3_free(zFull); + return p; + +quota_fopen_error: + sqlite3_free(zFull); + if( p && p->f ) fclose(p->f); + sqlite3_free(p); + return 0; +} + +/* +** Read content from a quota_FILE +*/ +size_t sqlite3_quota_fread( + void *pBuf, /* Store the content here */ + size_t size, /* Size of each element */ + size_t nmemb, /* Number of elements to read */ + quota_FILE *p /* Read from this quota_FILE object */ +){ + return fread(pBuf, size, nmemb, p->f); +} + +/* +** Write content into a quota_FILE. Invoke the quota callback and block +** the write if we exceed quota. +*/ +size_t sqlite3_quota_fwrite( + void *pBuf, /* Take content to write from here */ + size_t size, /* Size of each element */ + size_t nmemb, /* Number of elements */ + quota_FILE *p /* Write to this quota_FILE objecct */ +){ + sqlite3_int64 iOfst; + sqlite3_int64 iEnd; + sqlite3_int64 szNew; + quotaFile *pFile; + + iOfst = ftell(p->f); + iEnd = iOfst + size*nmemb; + pFile = p->pFile; + if( pFile && pFile->iSizepGroup; + quotaEnter(); + szNew = pGroup->iSize - pFile->iSize + iEnd; + if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ + if( pGroup->xCallback ){ + pGroup->xCallback(pFile->zFilename, &pGroup->iLimit, szNew, + pGroup->pArg); + } + if( szNew>pGroup->iLimit && pGroup->iLimit>0 ){ + iEnd = pGroup->iLimit - pGroup->iSize + pFile->iSize; + nmemb = (iEnd - iOfst)/size; + iEnd = iOfst + size*nmemb; + szNew = pGroup->iSize - pFile->iSize + iEnd; + } + } + pGroup->iSize = szNew; + pFile->iSize = iEnd; + quotaLeave(); + } + return fwrite(pBuf, size, nmemb, p->f); +} + +/* +** Close an open quota_FILE stream. +*/ +int sqlite3_quota_fclose(quota_FILE *p){ + int rc; + quotaFile *pFile; + rc = fclose(p->f); + pFile = p->pFile; + if( pFile ){ + quotaEnter(); + pFile->nRef--; + if( pFile->nRef==0 ){ + quotaGroup *pGroup = pFile->pGroup; + if( pFile->deleteOnClose ){ + gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); + quotaRemoveFile(pFile); + } + quotaGroupDeref(pGroup); + } + quotaLeave(); + } + sqlite3_free(p); + return rc; +} + +/* +** Flush memory buffers for a quota_FILE to disk. +*/ +int sqlite3_quota_fflush(quota_FILE *p, int doFsync){ + int rc; + rc = fflush(p->f); + if( rc==0 && doFsync ){ +#if SQLITE_OS_UNIX + rc = fsync(fileno(p->f)); +#endif +#if SQLITE_OS_WIN + rc = _commit(_fileno(p->f)); +#endif + } + return rc!=0; +} + +/* +** Seek on a quota_FILE stream. +*/ +int sqlite3_quota_fseek(quota_FILE *p, long offset, int whence){ + return fseek(p->f, offset, whence); +} + +/* +** rewind a quota_FILE stream. +*/ +void sqlite3_quota_rewind(quota_FILE *p){ + rewind(p->f); +} + +/* +** Tell the current location of a quota_FILE stream. +*/ +long sqlite3_quota_ftell(quota_FILE *p){ + return ftell(p->f); +} + +/* +** Remove a managed file. Update quotas accordingly. +*/ +int sqlite3_quota_remove(const char *zFilename){ + char *zFull; /* Full pathname for zFilename */ + int nFull; /* Number of bytes in zFilename */ + int rc; /* Result code */ + quotaGroup *pGroup; /* Group containing zFilename */ + quotaFile *pFile; /* A file in the group */ + quotaFile *pNextFile; /* next file in the group */ + int diff; /* Difference between filenames */ + char c; /* First character past end of pattern */ + + zFull = (char*)sqlite3_malloc(gQuota.sThisVfs.mxPathname + 1); + if( zFull==0 ) return SQLITE_NOMEM; + rc = gQuota.pOrigVfs->xFullPathname(gQuota.pOrigVfs, zFilename, + gQuota.sThisVfs.mxPathname+1, zFull); + if( rc ){ + sqlite3_free(zFull); + return rc; + } + + /* Figure out the length of the full pathname. If the name ends with + ** / (or \ on windows) then remove the trailing /. + */ + nFull = strlen(zFull); + if( nFull>0 && (zFull[nFull-1]=='/' || zFull[nFull-1]=='\\') ){ + nFull--; + zFull[nFull] = 0; + } + + quotaEnter(); + pGroup = quotaGroupFind(zFull); + if( pGroup ){ + for(pFile=pGroup->pFiles; pFile && rc==SQLITE_OK; pFile=pNextFile){ + pNextFile = pFile->pNext; + diff = memcmp(zFull, pFile->zFilename, nFull); + if( diff==0 && ((c = pFile->zFilename[nFull])==0 || c=='/' || c=='\\') ){ + if( pFile->nRef ){ + pFile->deleteOnClose = 1; + }else{ + rc = gQuota.pOrigVfs->xDelete(gQuota.pOrigVfs, pFile->zFilename, 0); + quotaRemoveFile(pFile); + quotaGroupDeref(pGroup); + } + } + } + } + quotaLeave(); + sqlite3_free(zFull); + return rc; +} /***************************** Test Code ***********************************/ #ifdef SQLITE_TEST @@ -1075,9 +1407,13 @@ static int test_quota_dump( Tcl_ListObjAppendElement(interp, pGroupTerm, Tcl_NewWideIntObj(pGroup->iSize)); for(pFile=pGroup->pFiles; pFile; pFile=pFile->pNext){ + int i; + char zTemp[1000]; pFileTerm = Tcl_NewObj(); + sqlite3_snprintf(sizeof(zTemp), zTemp, "%s", pFile->zFilename); + for(i=0; zTemp[i]; i++){ if( zTemp[i]=='\\' ) zTemp[i] = '/'; } Tcl_ListObjAppendElement(interp, pFileTerm, - Tcl_NewStringObj(pFile->zFilename, -1)); + Tcl_NewStringObj(zTemp, -1)); Tcl_ListObjAppendElement(interp, pFileTerm, Tcl_NewWideIntObj(pFile->iSize)); Tcl_ListObjAppendElement(interp, pFileTerm, @@ -1093,6 +1429,272 @@ static int test_quota_dump( return TCL_OK; } +/* +** tclcmd: sqlite3_quota_fopen FILENAME MODE +*/ +static int test_quota_fopen( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + const char *zFilename; /* File pattern to configure */ + const char *zMode; /* Mode string */ + quota_FILE *p; /* Open string object */ + char zReturn[50]; /* Name of pointer to return */ + + /* Process arguments */ + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "FILENAME MODE"); + return TCL_ERROR; + } + zFilename = Tcl_GetString(objv[1]); + zMode = Tcl_GetString(objv[2]); + p = sqlite3_quota_fopen(zFilename, zMode); + sqlite3_snprintf(sizeof(zReturn), zReturn, "%p", p); + Tcl_SetResult(interp, zReturn, TCL_VOLATILE); + return TCL_OK; +} + +/* Defined in test1.c */ +extern void *sqlite3TestTextToPtr(const char*); + +/* +** tclcmd: sqlite3_quota_fread HANDLE SIZE NELEM +*/ +static int test_quota_fread( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + quota_FILE *p; + char *zBuf; + int sz; + int nElem; + int got; + + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM"); + return TCL_ERROR; + } + p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); + if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR; + if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR; + zBuf = (char*)sqlite3_malloc( sz*nElem + 1 ); + if( zBuf==0 ){ + Tcl_SetResult(interp, "out of memory", TCL_STATIC); + return TCL_ERROR; + } + got = sqlite3_quota_fread(zBuf, sz, nElem, p); + if( got<0 ) got = 0; + zBuf[got*sz] = 0; + Tcl_SetResult(interp, zBuf, TCL_VOLATILE); + sqlite3_free(zBuf); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_fwrite HANDLE SIZE NELEM CONTENT +*/ +static int test_quota_fwrite( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + quota_FILE *p; + char *zBuf; + int sz; + int nElem; + int got; + + if( objc!=5 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE SIZE NELEM CONTENT"); + return TCL_ERROR; + } + p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); + if( Tcl_GetIntFromObj(interp, objv[2], &sz) ) return TCL_ERROR; + if( Tcl_GetIntFromObj(interp, objv[3], &nElem) ) return TCL_ERROR; + zBuf = Tcl_GetString(objv[4]); + got = sqlite3_quota_fwrite(zBuf, sz, nElem, p); + Tcl_SetObjResult(interp, Tcl_NewIntObj(got)); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_fclose HANDLE +*/ +static int test_quota_fclose( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + quota_FILE *p; + int rc; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); + return TCL_ERROR; + } + p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); + rc = sqlite3_quota_fclose(p); + Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_fflush HANDLE ?HARDSYNC? +*/ +static int test_quota_fflush( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + quota_FILE *p; + int rc; + int doSync = 0; + + if( objc!=2 && objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE ?HARDSYNC?"); + return TCL_ERROR; + } + p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); + if( objc==3 ){ + if( Tcl_GetBooleanFromObj(interp, objv[2], &doSync) ) return TCL_ERROR; + } + rc = sqlite3_quota_fflush(p, doSync); + Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_fseek HANDLE OFFSET WHENCE +*/ +static int test_quota_fseek( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + quota_FILE *p; + int ofst; + const char *zWhence; + int whence; + int rc; + + if( objc!=4 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE OFFSET WHENCE"); + return TCL_ERROR; + } + p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); + if( Tcl_GetIntFromObj(interp, objv[2], &ofst) ) return TCL_ERROR; + zWhence = Tcl_GetString(objv[3]); + if( strcmp(zWhence, "SEEK_SET")==0 ){ + whence = SEEK_SET; + }else if( strcmp(zWhence, "SEEK_CUR")==0 ){ + whence = SEEK_CUR; + }else if( strcmp(zWhence, "SEEK_END")==0 ){ + whence = SEEK_END; + }else{ + Tcl_AppendResult(interp, + "WHENCE should be SEEK_SET, SEEK_CUR, or SEEK_END", (char*)0); + return TCL_ERROR; + } + rc = sqlite3_quota_fseek(p, ofst, whence); + Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_rewind HANDLE +*/ +static int test_quota_rewind( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + quota_FILE *p; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); + return TCL_ERROR; + } + p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); + sqlite3_quota_rewind(p); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_ftell HANDLE +*/ +static int test_quota_ftell( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + quota_FILE *p; + sqlite3_int64 x; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "HANDLE"); + return TCL_ERROR; + } + p = sqlite3TestTextToPtr(Tcl_GetString(objv[1])); + x = sqlite3_quota_ftell(p); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(x)); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_remove FILENAME +*/ +static int test_quota_remove( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + const char *zFilename; /* File pattern to configure */ + int rc; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "FILENAME"); + return TCL_ERROR; + } + zFilename = Tcl_GetString(objv[1]); + rc = sqlite3_quota_remove(zFilename); + Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); + return TCL_OK; +} + +/* +** tclcmd: sqlite3_quota_glob PATTERN TEXT +** +** Test the glob pattern matching. Return 1 if TEXT matches PATTERN +** and return 0 if it does not. +*/ +static int test_quota_glob( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + const char *zPattern; /* The glob pattern */ + const char *zText; /* Text to compare agains the pattern */ + int rc; + if( objc!=3 ){ + Tcl_WrongNumArgs(interp, 1, objv, "PATTERN TEXT"); + return TCL_ERROR; + } + zPattern = Tcl_GetString(objv[1]); + zText = Tcl_GetString(objv[2]); + rc = quotaStrglob(zPattern, zText); + Tcl_SetObjResult(interp, Tcl_NewIntObj(rc)); + return TCL_OK; +} + /* ** This routine registers the custom TCL commands defined in this ** module. This should be the only procedure visible from outside @@ -1104,10 +1706,20 @@ int Sqlitequota_Init(Tcl_Interp *interp){ Tcl_ObjCmdProc *xProc; } aCmd[] = { { "sqlite3_quota_initialize", test_quota_initialize }, - { "sqlite3_quota_shutdown", test_quota_shutdown }, - { "sqlite3_quota_set", test_quota_set }, - { "sqlite3_quota_file", test_quota_file }, - { "sqlite3_quota_dump", test_quota_dump }, + { "sqlite3_quota_shutdown", test_quota_shutdown }, + { "sqlite3_quota_set", test_quota_set }, + { "sqlite3_quota_file", test_quota_file }, + { "sqlite3_quota_dump", test_quota_dump }, + { "sqlite3_quota_fopen", test_quota_fopen }, + { "sqlite3_quota_fread", test_quota_fread }, + { "sqlite3_quota_fwrite", test_quota_fwrite }, + { "sqlite3_quota_fclose", test_quota_fclose }, + { "sqlite3_quota_fflush", test_quota_fflush }, + { "sqlite3_quota_fseek", test_quota_fseek }, + { "sqlite3_quota_rewind", test_quota_rewind }, + { "sqlite3_quota_ftell", test_quota_ftell }, + { "sqlite3_quota_remove", test_quota_remove }, + { "sqlite3_quota_glob", test_quota_glob }, }; int i; diff --git a/db/sqlite3/src/test_quota.h b/db/sqlite3/src/test_quota.h new file mode 100644 index 00000000000..a2fddbbc439 --- /dev/null +++ b/db/sqlite3/src/test_quota.h @@ -0,0 +1,209 @@ +/* +** 2011 December 1 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file contains the interface definition for the quota a VFS shim. +** +** This particular shim enforces a quota system on files. One or more +** database files are in a "quota group" that is defined by a GLOB +** pattern. A quota is set for the combined size of all files in the +** the group. A quota of zero means "no limit". If the total size +** of all files in the quota group is greater than the limit, then +** write requests that attempt to enlarge a file fail with SQLITE_FULL. +** +** However, before returning SQLITE_FULL, the write requests invoke +** a callback function that is configurable for each quota group. +** This callback has the opportunity to enlarge the quota. If the +** callback does enlarge the quota such that the total size of all +** files within the group is less than the new quota, then the write +** continues as if nothing had happened. +*/ +#ifndef _QUOTA_H_ +#include "sqlite3.h" +#include + +/* Make this callable from C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Initialize the quota VFS shim. Use the VFS named zOrigVfsName +** as the VFS that does the actual work. Use the default if +** zOrigVfsName==NULL. +** +** The quota VFS shim is named "quota". It will become the default +** VFS if makeDefault is non-zero. +** +** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once +** during start-up. +*/ +int sqlite3_quota_initialize(const char *zOrigVfsName, int makeDefault); + +/* +** Shutdown the quota system. +** +** All SQLite database connections must be closed before calling this +** routine. +** +** THIS ROUTINE IS NOT THREADSAFE. Call this routine exactly once while +** shutting down in order to free all remaining quota groups. +*/ +int sqlite3_quota_shutdown(void); + +/* +** Create or destroy a quota group. +** +** The quota group is defined by the zPattern. When calling this routine +** with a zPattern for a quota group that already exists, this routine +** merely updates the iLimit, xCallback, and pArg values for that quota +** group. If zPattern is new, then a new quota group is created. +** +** The zPattern is always compared against the full pathname of the file. +** Even if APIs are called with relative pathnames, SQLite converts the +** name to a full pathname before comparing it against zPattern. zPattern +** is a glob pattern with the following matching rules: +** +** '*' Matches any sequence of zero or more characters. +** +** '?' Matches exactly one character. +** +** [...] Matches one character from the enclosed list of +** characters. "]" can be part of the list if it is +** the first character. Within the list "X-Y" matches +** characters X or Y or any character in between the +** two. Ex: "[0-9]" matches any digit. +** +** [^...] Matches one character not in the enclosed list. +** +** / Matches either / or \. This allows glob patterns +** containing / to work on both unix and windows. +** +** Note that, unlike unix shell globbing, the directory separator "/" +** can match a wildcard. So, for example, the pattern "/abc/xyz/" "*" +** matches any files anywhere in the directory hierarchy beneath +** /abc/xyz. +** +** The glob algorithm works on bytes. Multi-byte UTF8 characters are +** matched as if each byte were a separate character. +** +** If the iLimit for a quota group is set to zero, then the quota group +** is disabled and will be deleted when the last database connection using +** the quota group is closed. +** +** Calling this routine on a zPattern that does not exist and with a +** zero iLimit is a no-op. +** +** A quota group must exist with a non-zero iLimit prior to opening +** database connections if those connections are to participate in the +** quota group. Creating a quota group does not affect database connections +** that are already open. +** +** The patterns that define the various quota groups should be distinct. +** If the same filename matches more than one quota group pattern, then +** the behavior of this package is undefined. +*/ +int sqlite3_quota_set( + const char *zPattern, /* The filename pattern */ + sqlite3_int64 iLimit, /* New quota to set for this quota group */ + void (*xCallback)( /* Callback invoked when going over quota */ + const char *zFilename, /* Name of file whose size increases */ + sqlite3_int64 *piLimit, /* IN/OUT: The current limit */ + sqlite3_int64 iSize, /* Total size of all files in the group */ + void *pArg /* Client data */ + ), + void *pArg, /* client data passed thru to callback */ + void (*xDestroy)(void*) /* Optional destructor for pArg */ +); + +/* +** Bring the named file under quota management, assuming its name matches +** the glob pattern of some quota group. Or if it is already under +** management, update its size. If zFilename does not match the glob +** pattern of any quota group, this routine is a no-op. +*/ +int sqlite3_quota_file(const char *zFilename); + +/* +** The following object serves the same role as FILE in the standard C +** library. It represents an open connection to a file on disk for I/O. +** +** A single quota_FILE should not be used by two or more threads at the +** same time. Multiple threads can be using different quota_FILE objects +** simultaneously, but not the same quota_FILE object. +*/ +typedef struct quota_FILE quota_FILE; + +/* +** Create a new quota_FILE object used to read and/or write to the +** file zFilename. The zMode parameter is as with standard library zMode. +*/ +quota_FILE *sqlite3_quota_fopen(const char *zFilename, const char *zMode); + +/* +** Perform I/O against a quota_FILE object. When doing writes, the +** quota mechanism may result in a short write, in order to prevent +** the sum of sizes of all files from going over quota. +*/ +size_t sqlite3_quota_fread(void*, size_t, size_t, quota_FILE*); +size_t sqlite3_quota_fwrite(void*, size_t, size_t, quota_FILE*); + +/* +** Flush all written content held in memory buffers out to disk. +** This is the equivalent of fflush() in the standard library. +** +** If the hardSync parameter is true (non-zero) then this routine +** also forces OS buffers to disk - the equivalent of fsync(). +** +** This routine return zero on success and non-zero if something goes +** wrong. +*/ +int sqlite3_quota_fflush(quota_FILE*, int hardSync); + +/* +** Close a quota_FILE object and free all associated resources. The +** file remains under quota management. +*/ +int sqlite3_quota_fclose(quota_FILE*); + +/* +** Move the read/write pointer for a quota_FILE object. Or tell the +** current location of the read/write pointer. +*/ +int sqlite3_quota_fseek(quota_FILE*, long, int); +void sqlite3_quota_rewind(quota_FILE*); +long sqlite3_quota_ftell(quota_FILE*); + +/* +** Delete a file from the disk, if that file is under quota management. +** Adjust quotas accordingly. +** +** If zFilename is the name of a directory that matches one of the +** quota glob patterns, then all files under quota management that +** are contained within that directory are deleted. +** +** A standard SQLite result code is returned (SQLITE_OK, SQLITE_NOMEM, etc.) +** When deleting a directory of files, if the deletion of any one +** file fails (for example due to an I/O error), then this routine +** returns immediately, with the error code, and does not try to +** delete any of the other files in the specified directory. +** +** All files are removed from quota management and deleted from disk. +** However, no attempt is made to remove empty directories. +** +** This routine is a no-op for files that are not under quota management. +*/ +int sqlite3_quota_remove(const char *zFilename); + +#ifdef __cplusplus +} /* end of the 'extern "C"' block */ +#endif +#endif /* _QUOTA_H_ */ diff --git a/dom/base/StructuredCloneTags.h b/dom/base/StructuredCloneTags.h index d866c75f86e..86c1acf712d 100644 --- a/dom/base/StructuredCloneTags.h +++ b/dom/base/StructuredCloneTags.h @@ -45,6 +45,7 @@ namespace dom { enum StructuredCloneTags { SCTAG_BASE = JS_SCTAG_USER_MIN, SCTAG_DOM_BLOB, + SCTAG_DOM_FILE, SCTAG_DOM_FILELIST, SCTAG_DOM_MAX }; diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index b0a9f093df6..d3d96235b7d 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -83,6 +83,8 @@ #include "nsIIOService.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/indexedDB/FileInfo.h" +#include "mozilla/dom/indexedDB/IndexedDatabaseManager.h" #include "sampler.h" using namespace mozilla::dom; @@ -1922,3 +1924,51 @@ nsDOMWindowUtils::CheckAndClearPaintedState(nsIDOMElement* aElement, bool* aResu return NS_OK; } +NS_IMETHODIMP +nsDOMWindowUtils::GetFileId(nsIDOMBlob* aBlob, PRInt64* aResult) +{ + if (!IsUniversalXPConnectCapable()) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + *aResult = aBlob->GetFileId(); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWindowUtils::GetFileReferences(const nsAString& aDatabaseName, + PRInt64 aId, PRInt32* aRefCnt, + PRInt32* aDBRefCnt, PRInt32* aSliceRefCnt, + bool* aResult) +{ + if (!IsUniversalXPConnectCapable()) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + NS_ENSURE_TRUE(mWindow, NS_ERROR_FAILURE); + + nsCString origin; + nsresult rv = indexedDB::IndexedDatabaseManager::GetASCIIOriginFromWindow( + mWindow, origin); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr mgr = + indexedDB::IndexedDatabaseManager::GetOrCreate(); + NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE); + + nsRefPtr fileManager = + mgr->GetOrCreateFileManager(origin, aDatabaseName); + NS_ENSURE_TRUE(fileManager, NS_ERROR_FAILURE); + + nsRefPtr fileInfo = fileManager->GetFileInfo(aId); + if (fileInfo) { + fileInfo->GetReferences(aRefCnt, aDBRefCnt, aSliceRefCnt); + *aRefCnt--; + *aResult = true; + return NS_OK; + } + + *aRefCnt = *aDBRefCnt = *aSliceRefCnt = -1; + *aResult = false; + return NS_OK; +} diff --git a/dom/indexedDB/AsyncConnectionHelper.cpp b/dom/indexedDB/AsyncConnectionHelper.cpp index 7790aaabc36..b86e519ec23 100644 --- a/dom/indexedDB/AsyncConnectionHelper.cpp +++ b/dom/indexedDB/AsyncConnectionHelper.cpp @@ -77,9 +77,9 @@ private: // something fails. inline nsresult -ConvertCloneBuffersToArrayInternal( +ConvertCloneReadInfosToArrayInternal( JSContext* aCx, - nsTArray& aBuffers, + nsTArray& aReadInfos, jsval* aResult) { JSObject* array = JS_NewArrayObject(aCx, 0, nsnull); @@ -88,17 +88,18 @@ ConvertCloneBuffersToArrayInternal( return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } - if (!aBuffers.IsEmpty()) { - if (!JS_SetArrayLength(aCx, array, jsuint(aBuffers.Length()))) { + if (!aReadInfos.IsEmpty()) { + if (!JS_SetArrayLength(aCx, array, jsuint(aReadInfos.Length()))) { NS_WARNING("Failed to set array length!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } - for (uint32 index = 0, count = aBuffers.Length(); index < count; index++) { - JSAutoStructuredCloneBuffer& buffer = aBuffers[index]; + for (uint32 index = 0, count = aReadInfos.Length(); index < count; + index++) { + StructuredCloneReadInfo& readInfo = aReadInfos[index]; jsval val; - if (!IDBObjectStore::DeserializeValue(aCx, buffer, &val)) { + if (!IDBObjectStore::DeserializeValue(aCx, readInfo, &val)) { NS_WARNING("Failed to decode!"); return NS_ERROR_DOM_DATA_CLONE_ERR; } @@ -504,9 +505,9 @@ AsyncConnectionHelper::ReleaseMainThreadObjects() // static nsresult -AsyncConnectionHelper::ConvertCloneBuffersToArray( +AsyncConnectionHelper::ConvertCloneReadInfosToArray( JSContext* aCx, - nsTArray& aBuffers, + nsTArray& aReadInfos, jsval* aResult) { NS_ASSERTION(aCx, "Null context!"); @@ -514,12 +515,12 @@ AsyncConnectionHelper::ConvertCloneBuffersToArray( JSAutoRequest ar(aCx); - nsresult rv = ConvertCloneBuffersToArrayInternal(aCx, aBuffers, aResult); + nsresult rv = ConvertCloneReadInfosToArrayInternal(aCx, aReadInfos, aResult); - for (PRUint32 index = 0; index < aBuffers.Length(); index++) { - aBuffers[index].clear(); + for (PRUint32 index = 0; index < aReadInfos.Length(); index++) { + aReadInfos[index].mCloneBuffer.clear(); } - aBuffers.Clear(); + aReadInfos.Clear(); return rv; } diff --git a/dom/indexedDB/AsyncConnectionHelper.h b/dom/indexedDB/AsyncConnectionHelper.h index 34a4065c31d..e51ce82d180 100644 --- a/dom/indexedDB/AsyncConnectionHelper.h +++ b/dom/indexedDB/AsyncConnectionHelper.h @@ -41,6 +41,7 @@ #define mozilla_dom_indexeddb_asyncconnectionhelper_h__ // Only meant to be included in IndexedDB source files, not exported. +#include "DatabaseInfo.h" #include "IndexedDatabase.h" #include "IDBDatabase.h" #include "IDBRequest.h" @@ -198,9 +199,9 @@ protected: /** * Helper to make a JS array object out of an array of clone buffers. */ - static nsresult ConvertCloneBuffersToArray( + static nsresult ConvertCloneReadInfosToArray( JSContext* aCx, - nsTArray& aBuffers, + nsTArray& aReadInfos, jsval* aResult); protected: diff --git a/dom/indexedDB/CheckPermissionsHelper.cpp b/dom/indexedDB/CheckPermissionsHelper.cpp index cb82d6f1ed2..0c2a2c1a140 100644 --- a/dom/indexedDB/CheckPermissionsHelper.cpp +++ b/dom/indexedDB/CheckPermissionsHelper.cpp @@ -205,9 +205,6 @@ CheckPermissionsHelper::Observe(nsISupports* aSubject, mPromptResult = nsDependentString(aData).ToInteger(&rv); NS_ENSURE_SUCCESS(rv, rv); - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - NS_ASSERTION(mgr, "This should never be null!"); - rv = NS_DispatchToCurrentThread(this); NS_ENSURE_SUCCESS(rv, rv); diff --git a/dom/indexedDB/FileInfo.cpp b/dom/indexedDB/FileInfo.cpp new file mode 100644 index 00000000000..5011137860d --- /dev/null +++ b/dom/indexedDB/FileInfo.cpp @@ -0,0 +1,138 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Indexed Database. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "IndexedDatabaseManager.h" +#include "FileInfo.h" + +USING_INDEXEDDB_NAMESPACE + +// static +FileInfo* +FileInfo::Create(FileManager* aFileManager, PRInt64 aId) +{ + NS_ASSERTION(aId > 0, "Wrong id!"); + + if (aId <= PR_INT16_MAX) { + return new FileInfo16(aFileManager, aId); + } + + if (aId <= PR_INT32_MAX) { + return new FileInfo32(aFileManager, aId); + } + + return new FileInfo64(aFileManager, aId); +} + +void +FileInfo::GetReferences(PRInt32* aRefCnt, PRInt32* aDBRefCnt, + PRInt32* aSliceRefCnt) +{ + if (IndexedDatabaseManager::IsClosed()) { + NS_ERROR("Shouldn't be called after shutdown!"); + + if (aRefCnt) { + *aRefCnt = -1; + } + + if (aDBRefCnt) { + *aDBRefCnt = -1; + } + + if (aSliceRefCnt) { + *aSliceRefCnt = -1; + } + + return; + } + + MutexAutoLock lock(IndexedDatabaseManager::FileMutex()); + + if (aRefCnt) { + *aRefCnt = mRefCnt; + } + + if (aDBRefCnt) { + *aDBRefCnt = mDBRefCnt; + } + + if (aSliceRefCnt) { + *aSliceRefCnt = mSliceRefCnt; + } +} + +void +FileInfo::UpdateReferences(nsAutoRefCnt& aRefCount, PRInt32 aDelta, + bool aClear) +{ + if (IndexedDatabaseManager::IsClosed()) { + NS_ERROR("Shouldn't be called after shutdown!"); + return; + } + + { + MutexAutoLock lock(IndexedDatabaseManager::FileMutex()); + + aRefCount = aClear ? 0 : aRefCount + aDelta; + + if (mRefCnt + mDBRefCnt + mSliceRefCnt > 0) { + return; + } + + mFileManager->mFileInfos.Remove(Id()); + } + + Cleanup(); + + delete this; +} + +void +FileInfo::Cleanup() +{ + if (IndexedDatabaseManager::IsShuttingDown() || + mFileManager->Invalidated()) { + return; + } + + nsRefPtr mgr = IndexedDatabaseManager::Get(); + NS_ASSERTION(mgr, "Shouldn't be null!"); + + if (NS_FAILED(mgr->AsyncDeleteFile(mFileManager, Id()))) { + NS_WARNING("Failed to delete file asynchronously!"); + } +} diff --git a/dom/indexedDB/FileInfo.h b/dom/indexedDB/FileInfo.h new file mode 100644 index 00000000000..432fcb48c30 --- /dev/null +++ b/dom/indexedDB/FileInfo.h @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Indexed Database. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef mozilla_dom_indexeddb_fileinfo_h__ +#define mozilla_dom_indexeddb_fileinfo_h__ + +#include "IndexedDatabase.h" +#include "nsAtomicRefcnt.h" +#include "nsThreadUtils.h" +#include "FileManager.h" +#include "IndexedDatabaseManager.h" + +BEGIN_INDEXEDDB_NAMESPACE + +class FileInfo +{ + friend class FileManager; + +public: + FileInfo(FileManager* aFileManager) + : mFileManager(aFileManager) + { } + + virtual ~FileInfo() + { +#ifdef DEBUG + NS_ASSERTION(NS_IsMainThread(), "File info destroyed on wrong thread!"); +#endif + } + + static + FileInfo* Create(FileManager* aFileManager, PRInt64 aId); + + void AddRef() + { + if (IndexedDatabaseManager::IsClosed()) { + NS_AtomicIncrementRefcnt(mRefCnt); + } + else { + UpdateReferences(mRefCnt, 1); + } + } + + void Release() + { + if (IndexedDatabaseManager::IsClosed()) { + nsrefcnt count = NS_AtomicDecrementRefcnt(mRefCnt); + if (count == 0) { + mRefCnt = 1; + delete this; + } + } + else { + UpdateReferences(mRefCnt, -1); + } + } + + void UpdateDBRefs(PRInt32 aDelta) + { + UpdateReferences(mDBRefCnt, aDelta); + } + + void ClearDBRefs() + { + UpdateReferences(mDBRefCnt, 0, true); + } + + void UpdateSliceRefs(PRInt32 aDelta) + { + UpdateReferences(mSliceRefCnt, aDelta); + } + + void GetReferences(PRInt32* aRefCnt, PRInt32* aDBRefCnt, + PRInt32* aSliceRefCnt); + + FileManager* Manager() const + { + return mFileManager; + } + + virtual PRInt64 Id() const = 0; + +private: + void UpdateReferences(nsAutoRefCnt& aRefCount, PRInt32 aDelta, + bool aClear = false); + void Cleanup(); + + nsAutoRefCnt mRefCnt; + nsAutoRefCnt mDBRefCnt; + nsAutoRefCnt mSliceRefCnt; + + nsRefPtr mFileManager; +}; + +#define FILEINFO_SUBCLASS(_bits) \ +class FileInfo##_bits : public FileInfo \ +{ \ +public: \ + FileInfo##_bits(FileManager* aFileManager, PRInt64 aId) \ + : FileInfo(aFileManager), mId(aId) \ + { } \ + \ + virtual PRInt64 Id() const \ + { \ + return mId; \ + } \ + \ +private: \ + PRInt##_bits mId; \ +}; + +FILEINFO_SUBCLASS(16); +FILEINFO_SUBCLASS(32); +FILEINFO_SUBCLASS(64); + +#undef FILEINFO_SUBCLASS + +END_INDEXEDDB_NAMESPACE + +#endif // mozilla_dom_indexeddb_fileinfo_h__ diff --git a/dom/indexedDB/FileManager.cpp b/dom/indexedDB/FileManager.cpp new file mode 100644 index 00000000000..98a75b23de3 --- /dev/null +++ b/dom/indexedDB/FileManager.cpp @@ -0,0 +1,309 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Indexed Database. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "FileManager.h" + +#include "mozIStorageConnection.h" +#include "mozIStorageServiceQuotaManagement.h" +#include "mozIStorageStatement.h" +#include "nsISimpleEnumerator.h" +#include "mozStorageCID.h" +#include "nsContentUtils.h" + +#include "FileInfo.h" +#include "IndexedDatabaseManager.h" + +USING_INDEXEDDB_NAMESPACE + +namespace { + +PLDHashOperator +EnumerateToTArray(const PRUint64& aKey, + FileInfo* aValue, + void* aUserArg) +{ + NS_ASSERTION(aValue, "Null pointer!"); + NS_ASSERTION(aUserArg, "Null pointer!"); + + nsTArray* array = + static_cast*>(aUserArg); + + array->AppendElement(aValue); + + return PL_DHASH_NEXT; +} + +} // anonymous namespace + +nsresult +FileManager::Init() +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + NS_ENSURE_TRUE(mFileInfos.Init(), NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + +nsresult +FileManager::InitDirectory(nsIFile* aDirectory, + mozIStorageConnection* aConnection) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + bool exists; + nsresult rv = aDirectory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + bool isDirectory; + rv = aDirectory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(isDirectory, NS_ERROR_FAILURE); + } + else { + rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE VIRTUAL TABLE fs USING filesystem;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr stmt; + rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( + "SELECT name, (name IN (SELECT id FROM file)) FROM fs " + "WHERE path = :path" + ), getter_AddRefs(stmt)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString path; + rv = aDirectory->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("path"), path); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr ss = + do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE); + + bool hasResult; + while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { + nsString name; + rv = stmt->GetString(0, name); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt32 flag = stmt->AsInt32(1); + + nsCOMPtr file; + rv = aDirectory->Clone(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = file->Append(name); + NS_ENSURE_SUCCESS(rv, rv); + + if (flag) { + rv = ss->UpdateQuotaInformationForFile(file); + NS_ENSURE_SUCCESS(rv, rv); + } else { + rv = file->Remove(false); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to remove orphaned file!"); + } + } + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE fs;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDirectory->GetPath(mDirectoryPath); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDirectory->GetLeafName(mDirectoryName); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +FileManager::Load(mozIStorageConnection* aConnection) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + nsCOMPtr stmt; + nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( + "SELECT id, refcount " + "FROM file" + ), getter_AddRefs(stmt)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasResult; + while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { + PRInt64 id; + rv = stmt->GetInt64(0, &id); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt32 refcount; + rv = stmt->GetInt32(1, &refcount); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(refcount, "This shouldn't happen!"); + + nsRefPtr fileInfo = FileInfo::Create(this, id); + fileInfo->mDBRefCnt = refcount; + + if (!mFileInfos.Put(id, fileInfo)) { + NS_WARNING("Out of memory?"); + return NS_ERROR_OUT_OF_MEMORY; + } + + mLastFileId = NS_MAX(id, mLastFileId); + } + + mLoaded = true; + + return NS_OK; +} + +nsresult +FileManager::Invalidate() +{ + if (IndexedDatabaseManager::IsClosed()) { + NS_ERROR("Shouldn't be called after shutdown!"); + return NS_ERROR_UNEXPECTED; + } + + nsTArray fileInfos; + { + MutexAutoLock lock(IndexedDatabaseManager::FileMutex()); + + NS_ASSERTION(!mInvalidated, "Invalidate more than once?!"); + mInvalidated = true; + + fileInfos.SetCapacity(mFileInfos.Count()); + mFileInfos.EnumerateRead(EnumerateToTArray, &fileInfos); + } + + for (PRUint32 i = 0; i < fileInfos.Length(); i++) { + FileInfo* fileInfo = fileInfos.ElementAt(i); + fileInfo->ClearDBRefs(); + } + + return NS_OK; +} + +already_AddRefed +FileManager::GetDirectory() +{ + nsCOMPtr directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + NS_ENSURE_TRUE(directory, nsnull); + + nsresult rv = directory->InitWithPath(mDirectoryPath); + NS_ENSURE_SUCCESS(rv, nsnull); + + return directory.forget(); +} + +already_AddRefed +FileManager::GetFileInfo(PRInt64 aId) +{ + if (IndexedDatabaseManager::IsClosed()) { + NS_ERROR("Shouldn't be called after shutdown!"); + return nsnull; + } + + FileInfo* fileInfo = nsnull; + { + MutexAutoLock lock(IndexedDatabaseManager::FileMutex()); + fileInfo = mFileInfos.Get(aId); + } + nsRefPtr result = fileInfo; + return result.forget(); +} + +already_AddRefed +FileManager::GetNewFileInfo() +{ + if (IndexedDatabaseManager::IsClosed()) { + NS_ERROR("Shouldn't be called after shutdown!"); + return nsnull; + } + + nsAutoPtr fileInfo; + + { + MutexAutoLock lock(IndexedDatabaseManager::FileMutex()); + + PRInt64 id = mLastFileId + 1; + + fileInfo = FileInfo::Create(this, id); + + if (!mFileInfos.Put(id, fileInfo)) { + NS_WARNING("Out of memory?"); + return nsnull; + } + + mLastFileId = id; + } + + nsRefPtr result = fileInfo.forget(); + return result.forget(); +} + +already_AddRefed +FileManager::GetFileForId(nsIFile* aDirectory, PRInt64 aId) +{ + NS_ASSERTION(aDirectory, "Null pointer!"); + + nsAutoString id; + id.AppendInt(aId); + + nsCOMPtr file; + nsresult rv = aDirectory->Clone(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, nsnull); + + rv = file->Append(id); + NS_ENSURE_SUCCESS(rv, nsnull); + + return file.forget(); +} diff --git a/dom/indexedDB/FileManager.h b/dom/indexedDB/FileManager.h new file mode 100644 index 00000000000..1e0e0b19b1c --- /dev/null +++ b/dom/indexedDB/FileManager.h @@ -0,0 +1,136 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Indexed Database. + * + * The Initial Developer of the Original Code is + * The Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef mozilla_dom_indexeddb_filemanager_h__ +#define mozilla_dom_indexeddb_filemanager_h__ + +#include "IndexedDatabase.h" +#include "nsIFile.h" +#include "nsILocalFile.h" +#include "nsIDOMFile.h" +#include "nsDataHashtable.h" + +class mozIStorageConnection; + +BEGIN_INDEXEDDB_NAMESPACE + +class FileInfo; + +class FileManager +{ + friend class FileInfo; + +public: + FileManager(const nsACString& aOrigin, + const nsAString& aDatabaseName) + : mOrigin(aOrigin), mDatabaseName(aDatabaseName), mLastFileId(0), + mLoaded(false), mInvalidated(false) + { } + + ~FileManager() + { } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileManager) + + const nsACString& Origin() const + { + return mOrigin; + } + + const nsAString& DatabaseName() const + { + return mDatabaseName; + } + + const nsAString& DirectoryName() const + { + return mDirectoryName; + } + + bool IsDirectoryInited() const + { + return !mDirectoryPath.IsEmpty(); + } + + bool Loaded() const + { + return mLoaded; + } + + bool Invalidated() const + { + return mInvalidated; + } + + nsresult Init(); + + nsresult InitDirectory(nsIFile* aDirectory, + mozIStorageConnection* aConnection); + + nsresult Load(mozIStorageConnection* aConnection); + + nsresult Invalidate(); + + already_AddRefed GetDirectory(); + + already_AddRefed GetFileInfo(PRInt64 aId); + + already_AddRefed GetNewFileInfo(); + + static already_AddRefed GetFileForId(nsIFile* aDirectory, + PRInt64 aId); + +private: + nsCString mOrigin; + nsString mDatabaseName; + + nsString mDirectoryPath; + nsString mDirectoryName; + + PRInt64 mLastFileId; + + // Protected by IndexedDatabaseManager::FileMutex() + nsDataHashtable mFileInfos; + + bool mLoaded; + bool mInvalidated; +}; + +END_INDEXEDDB_NAMESPACE + +#endif // mozilla_dom_indexeddb_filemanager_h__ diff --git a/dom/indexedDB/IDBCursor.cpp b/dom/indexedDB/IDBCursor.cpp index 0dde869ffc6..bc47b73de52 100644 --- a/dom/indexedDB/IDBCursor.cpp +++ b/dom/indexedDB/IDBCursor.cpp @@ -49,7 +49,6 @@ #include "nsThreadUtils.h" #include "AsyncConnectionHelper.h" -#include "DatabaseInfo.h" #include "IDBEvents.h" #include "IDBIndex.h" #include "IDBObjectStore.h" @@ -87,7 +86,7 @@ public: ~ContinueHelper() { - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer); + IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); @@ -112,7 +111,7 @@ protected: PRInt32 mCount; Key mKey; Key mObjectKey; - JSAutoStructuredCloneBuffer mCloneBuffer; + StructuredCloneReadInfo mCloneReadInfo; }; class ContinueObjectStoreHelper : public ContinueHelper @@ -165,7 +164,7 @@ IDBCursor::Create(IDBRequest* aRequest, const nsACString& aContinueQuery, const nsACString& aContinueToQuery, const Key& aKey, - JSAutoStructuredCloneBuffer& aCloneBuffer) + StructuredCloneReadInfo& aCloneReadInfo) { NS_ASSERTION(aObjectStore, "Null pointer!"); NS_ASSERTION(!aKey.IsUnset(), "Bad key!"); @@ -178,7 +177,7 @@ IDBCursor::Create(IDBRequest* aRequest, cursor->mObjectStore = aObjectStore; cursor->mType = OBJECTSTORE; cursor->mKey = aKey; - cursor->mCloneBuffer.swap(aCloneBuffer); + cursor->mCloneReadInfo.Swap(aCloneReadInfo); return cursor.forget(); } @@ -224,7 +223,7 @@ IDBCursor::Create(IDBRequest* aRequest, const nsACString& aContinueToQuery, const Key& aKey, const Key& aObjectKey, - JSAutoStructuredCloneBuffer& aCloneBuffer) + StructuredCloneReadInfo& aCloneReadInfo) { NS_ASSERTION(aIndex, "Null pointer!"); NS_ASSERTION(!aKey.IsUnset(), "Bad key!"); @@ -240,7 +239,7 @@ IDBCursor::Create(IDBRequest* aRequest, cursor->mType = INDEXOBJECT; cursor->mKey = aKey; cursor->mObjectKey = aObjectKey; - cursor->mCloneBuffer.swap(aCloneBuffer); + cursor->mCloneReadInfo.Swap(aCloneReadInfo); return cursor.forget(); } @@ -300,7 +299,7 @@ IDBCursor::~IDBCursor() if (mRooted) { NS_DROP_JS_OBJECTS(this, IDBCursor); } - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer); + IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); } nsresult @@ -529,12 +528,12 @@ IDBCursor::GetValue(JSContext* aCx, mRooted = true; } - if (!IDBObjectStore::DeserializeValue(aCx, mCloneBuffer, &mCachedValue)) { + if (!IDBObjectStore::DeserializeValue(aCx, mCloneReadInfo, &mCachedValue)) { mCachedValue = JSVAL_VOID; return NS_ERROR_DOM_DATA_CLONE_ERR; } - mCloneBuffer.clear(); + mCloneReadInfo.mCloneBuffer.clear(); mHaveCachedValue = true; } @@ -762,8 +761,8 @@ ContinueHelper::GetSuccessResult(JSContext* aCx, mCursor->mObjectKey = mObjectKey; mCursor->mContinueToKey.Unset(); - mCursor->mCloneBuffer.swap(mCloneBuffer); - mCloneBuffer.clear(); + mCursor->mCloneReadInfo.Swap(mCloneReadInfo); + mCloneReadInfo.mCloneBuffer.clear(); nsresult rv = WrapNative(aCx, mCursor, aVal); NS_ENSURE_SUCCESS(rv, rv); @@ -811,8 +810,8 @@ ContinueObjectStoreHelper::GatherResultsFromStatement( nsresult rv = mKey.SetFromStatement(aStatement, 0); NS_ENSURE_SUCCESS(rv, rv); - rv = IDBObjectStore::GetStructuredCloneDataFromStatement(aStatement, 1, - mCloneBuffer); + rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(aStatement, 1, 2, + mDatabase->Manager(), mCloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; @@ -881,8 +880,8 @@ ContinueIndexObjectHelper::GatherResultsFromStatement( rv = mObjectKey.SetFromStatement(aStatement, 1); NS_ENSURE_SUCCESS(rv, rv); - rv = IDBObjectStore::GetStructuredCloneDataFromStatement(aStatement, 2, - mCloneBuffer); + rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(aStatement, 2, 3, + mDatabase->Manager(), mCloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; diff --git a/dom/indexedDB/IDBCursor.h b/dom/indexedDB/IDBCursor.h index f9f7a787cc8..4cc6f74525e 100644 --- a/dom/indexedDB/IDBCursor.h +++ b/dom/indexedDB/IDBCursor.h @@ -88,7 +88,7 @@ public: const nsACString& aContinueQuery, const nsACString& aContinueToQuery, const Key& aKey, - JSAutoStructuredCloneBuffer& aCloneBuffer); + StructuredCloneReadInfo& aCloneReadInfo); // For INDEXKEY cursors. static @@ -115,7 +115,7 @@ public: const nsACString& aContinueToQuery, const Key& aKey, const Key& aObjectKey, - JSAutoStructuredCloneBuffer& aCloneBuffer); + StructuredCloneReadInfo& aCloneReadInfo); enum Type { @@ -169,7 +169,7 @@ protected: Key mKey; Key mObjectKey; - JSAutoStructuredCloneBuffer mCloneBuffer; + StructuredCloneReadInfo mCloneReadInfo; Key mContinueToKey; bool mHaveCachedKey; diff --git a/dom/indexedDB/IDBDatabase.cpp b/dom/indexedDB/IDBDatabase.cpp index 77b0c93c8ea..beb65297cb1 100644 --- a/dom/indexedDB/IDBDatabase.cpp +++ b/dom/indexedDB/IDBDatabase.cpp @@ -43,6 +43,7 @@ #include "mozilla/Mutex.h" #include "mozilla/storage.h" #include "nsDOMClassInfo.h" +#include "nsDOMLists.h" #include "nsEventDispatcher.h" #include "nsJSUtils.h" #include "nsProxyRelease.h" @@ -151,7 +152,8 @@ already_AddRefed IDBDatabase::Create(nsIScriptContext* aScriptContext, nsPIDOMWindow* aOwner, already_AddRefed aDatabaseInfo, - const nsACString& aASCIIOrigin) + const nsACString& aASCIIOrigin, + FileManager* aFileManager) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!aASCIIOrigin.IsEmpty(), "Empty origin!"); @@ -169,6 +171,7 @@ IDBDatabase::Create(nsIScriptContext* aScriptContext, db->mFilePath = databaseInfo->filePath; databaseInfo.swap(db->mDatabaseInfo); db->mASCIIOrigin = aASCIIOrigin; + db->mFileManager = aFileManager; IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); NS_ASSERTION(mgr, "This should never be null!"); diff --git a/dom/indexedDB/IDBDatabase.h b/dom/indexedDB/IDBDatabase.h index 6062e4a52ac..7b9c15d0a76 100644 --- a/dom/indexedDB/IDBDatabase.h +++ b/dom/indexedDB/IDBDatabase.h @@ -41,12 +41,12 @@ #define mozilla_dom_indexeddb_idbdatabase_h__ #include "mozilla/dom/indexedDB/IndexedDatabase.h" +#include "mozilla/dom/indexedDB/FileManager.h" #include "nsIIDBDatabase.h" #include "nsCycleCollectionParticipant.h" #include "nsDOMEventTargetHelper.h" -#include "nsDOMLists.h" #include "nsIDocument.h" class nsIScriptContext; @@ -78,7 +78,8 @@ public: Create(nsIScriptContext* aScriptContext, nsPIDOMWindow* aOwner, already_AddRefed aDatabaseInfo, - const nsACString& aASCIIOrigin); + const nsACString& aASCIIOrigin, + FileManager* aFileManager); // nsIDOMEventTarget virtual nsresult PostHandleEvent(nsEventChainPostVisitor& aVisitor); @@ -141,6 +142,11 @@ public: void EnterSetVersionTransaction(); void ExitSetVersionTransaction(); + FileManager* Manager() const + { + return mFileManager; + } + private: IDBDatabase(); ~IDBDatabase(); @@ -158,6 +164,8 @@ private: bool mClosed; bool mRunningVersionChange; + nsRefPtr mFileManager; + // Only touched on the main thread. nsRefPtr mOnAbortListener; nsRefPtr mOnErrorListener; diff --git a/dom/indexedDB/IDBIndex.cpp b/dom/indexedDB/IDBIndex.cpp index 93a78fce98f..afeb1ee4e62 100644 --- a/dom/indexedDB/IDBIndex.cpp +++ b/dom/indexedDB/IDBIndex.cpp @@ -104,7 +104,7 @@ public: ~GetHelper() { - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer); + IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); @@ -113,12 +113,12 @@ public: void ReleaseMainThreadObjects() { - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer); + IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); GetKeyHelper::ReleaseMainThreadObjects(); } protected: - JSAutoStructuredCloneBuffer mCloneBuffer; + StructuredCloneReadInfo mCloneReadInfo; }; class GetAllKeysHelper : public GetKeyHelper @@ -154,8 +154,9 @@ public: ~GetAllHelper() { - for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) { - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffers[index]); + for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) { + IDBObjectStore::ClearStructuredCloneBuffer( + mCloneReadInfos[index].mCloneBuffer); } } @@ -165,15 +166,16 @@ public: void ReleaseMainThreadObjects() { - for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) { - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffers[index]); + for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) { + IDBObjectStore::ClearStructuredCloneBuffer( + mCloneReadInfos[index].mCloneBuffer); } GetKeyHelper::ReleaseMainThreadObjects(); } protected: const PRUint32 mLimit; - nsTArray mCloneBuffers; + nsTArray mCloneReadInfos; }; class OpenKeyCursorHelper : public AsyncConnectionHelper @@ -227,7 +229,7 @@ public: ~OpenCursorHelper() { - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer); + IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); @@ -250,7 +252,7 @@ private: // Out-params. Key mKey; Key mObjectKey; - JSAutoStructuredCloneBuffer mCloneBuffer; + StructuredCloneReadInfo mCloneReadInfo; nsCString mContinueQuery; nsCString mContinueToQuery; Key mRangeKey; @@ -793,7 +795,7 @@ GetHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */) NS_NAMED_LITERAL_CSTRING(indexId, "index_id"); - nsCString query = NS_LITERAL_CSTRING("SELECT data FROM ") + objectTable + + nsCString query = NS_LITERAL_CSTRING("SELECT data, file_ids FROM ") + objectTable + NS_LITERAL_CSTRING(" INNER JOIN ") + joinTable + NS_LITERAL_CSTRING(" ON ") + objectTable + NS_LITERAL_CSTRING(".id = ") + joinTable + @@ -818,8 +820,8 @@ GetHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */) NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (hasResult) { - rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 0, - mCloneBuffer); + rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 0, 1, + mDatabase->Manager(), mCloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); } @@ -830,9 +832,9 @@ nsresult GetHelper::GetSuccessResult(JSContext* aCx, jsval* aVal) { - bool result = IDBObjectStore::DeserializeValue(aCx, mCloneBuffer, aVal); + bool result = IDBObjectStore::DeserializeValue(aCx, mCloneReadInfo, aVal); - mCloneBuffer.clear(); + mCloneReadInfo.mCloneBuffer.clear(); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); return NS_OK; @@ -1003,7 +1005,7 @@ GetAllHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */) limitClause.AppendInt(mLimit); } - nsCString query = NS_LITERAL_CSTRING("SELECT data FROM ") + dataTableName + + nsCString query = NS_LITERAL_CSTRING("SELECT data, file_ids FROM ") + dataTableName + NS_LITERAL_CSTRING(" INNER JOIN ") + indexTableName + NS_LITERAL_CSTRING(" ON ") + dataTableName + NS_LITERAL_CSTRING(".id = ") + indexTableName + @@ -1025,18 +1027,19 @@ GetAllHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */) NS_ENSURE_SUCCESS(rv, rv); } - mCloneBuffers.SetCapacity(50); + mCloneReadInfos.SetCapacity(50); bool hasResult; while(NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) { - if (mCloneBuffers.Capacity() == mCloneBuffers.Length()) { - mCloneBuffers.SetCapacity(mCloneBuffers.Capacity() * 2); + if (mCloneReadInfos.Capacity() == mCloneReadInfos.Length()) { + mCloneReadInfos.SetCapacity(mCloneReadInfos.Capacity() * 2); } - JSAutoStructuredCloneBuffer* buffer = mCloneBuffers.AppendElement(); - NS_ASSERTION(buffer, "This shouldn't fail!"); + StructuredCloneReadInfo* readInfo = mCloneReadInfos.AppendElement(); + NS_ASSERTION(readInfo, "This shouldn't fail!"); - rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 0, *buffer); + rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 0, 1, + mDatabase->Manager(), *readInfo); NS_ENSURE_SUCCESS(rv, rv); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); @@ -1048,12 +1051,12 @@ nsresult GetAllHelper::GetSuccessResult(JSContext* aCx, jsval* aVal) { - NS_ASSERTION(mCloneBuffers.Length() <= mLimit, "Too many results!"); + NS_ASSERTION(mCloneReadInfos.Length() <= mLimit, "Too many results!"); - nsresult rv = ConvertCloneBuffersToArray(aCx, mCloneBuffers, aVal); + nsresult rv = ConvertCloneReadInfosToArray(aCx, mCloneReadInfos, aVal); - for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) { - mCloneBuffers[index].clear(); + for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) { + mCloneReadInfos[index].mCloneBuffer.clear(); } NS_ENSURE_SUCCESS(rv, rv); @@ -1317,9 +1320,10 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) NS_NAMED_LITERAL_CSTRING(commaspace, ", "); nsCString data = objectTable + NS_LITERAL_CSTRING(".data"); + nsCString fileIds = objectTable + NS_LITERAL_CSTRING(".file_ids"); nsCString firstQuery = NS_LITERAL_CSTRING("SELECT ") + value + commaspace + - keyValue + commaspace + data + + keyValue + commaspace + data + commaspace + fileIds + NS_LITERAL_CSTRING(" FROM ") + indexTable + NS_LITERAL_CSTRING(" INNER JOIN ") + objectTable + NS_LITERAL_CSTRING(" ON ") + indexTable + dot + @@ -1359,13 +1363,14 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) rv = mObjectKey.SetFromStatement(stmt, 1); NS_ENSURE_SUCCESS(rv, rv); - rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 2, - mCloneBuffer); + rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 2, 3, + mDatabase->Manager(), mCloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); // Now we need to make the query to get the next match. nsCAutoString queryStart = NS_LITERAL_CSTRING("SELECT ") + value + commaspace + keyValue + commaspace + data + + commaspace + fileIds + NS_LITERAL_CSTRING(" FROM ") + indexTable + NS_LITERAL_CSTRING(" INNER JOIN ") + objectTable + NS_LITERAL_CSTRING(" ON ") + indexTable + dot + @@ -1458,10 +1463,10 @@ OpenCursorHelper::GetSuccessResult(JSContext* aCx, nsRefPtr cursor = IDBCursor::Create(mRequest, mTransaction, mIndex, mDirection, mRangeKey, mContinueQuery, mContinueToQuery, mKey, mObjectKey, - mCloneBuffer); + mCloneReadInfo); NS_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - NS_ASSERTION(!mCloneBuffer.data(), "Should have swapped!"); + NS_ASSERTION(!mCloneReadInfo.mCloneBuffer.data(), "Should have swapped!"); return WrapNative(aCx, cursor, aVal); } diff --git a/dom/indexedDB/IDBObjectStore.cpp b/dom/indexedDB/IDBObjectStore.cpp index 4242bc046f2..3ded781c39e 100644 --- a/dom/indexedDB/IDBObjectStore.cpp +++ b/dom/indexedDB/IDBObjectStore.cpp @@ -42,15 +42,19 @@ #include "nsIJSContextStack.h" #include "jsclone.h" +#include "mozilla/dom/StructuredCloneTags.h" #include "mozilla/storage.h" #include "nsCharSeparatedTokenizer.h" #include "nsContentUtils.h" #include "nsDOMClassInfo.h" +#include "nsDOMFile.h" +#include "nsDOMLists.h" #include "nsEventDispatcher.h" #include "nsJSUtils.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "snappy/snappy.h" +#include "test_quota.h" #include "AsyncConnectionHelper.h" #include "IDBCursor.h" @@ -60,6 +64,8 @@ #include "IDBTransaction.h" #include "DatabaseInfo.h" +#define FILE_COPY_BUFFER_SIZE 32768 + USING_INDEXEDDB_NAMESPACE namespace { @@ -70,21 +76,20 @@ public: AddHelper(IDBTransaction* aTransaction, IDBRequest* aRequest, IDBObjectStore* aObjectStore, - JSAutoStructuredCloneBuffer& aCloneBuffer, + StructuredCloneWriteInfo& aCloneWriteInfo, const Key& aKey, bool aOverwrite, - nsTArray& aIndexUpdateInfo, - PRUint64 aOffsetToKeyProp) + nsTArray& aIndexUpdateInfo) : AsyncConnectionHelper(aTransaction, aRequest), mObjectStore(aObjectStore), - mKey(aKey), mOverwrite(aOverwrite), mOffsetToKeyProp(aOffsetToKeyProp) + mKey(aKey), mOverwrite(aOverwrite) { - mCloneBuffer.swap(aCloneBuffer); + mCloneWriteInfo.Swap(aCloneWriteInfo); mIndexUpdateInfo.SwapElements(aIndexUpdateInfo); } ~AddHelper() { - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer); + IDBObjectStore::ClearStructuredCloneBuffer(mCloneWriteInfo.mCloneBuffer); } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); @@ -94,7 +99,7 @@ public: void ReleaseMainThreadObjects() { mObjectStore = nsnull; - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer); + IDBObjectStore::ClearStructuredCloneBuffer(mCloneWriteInfo.mCloneBuffer); AsyncConnectionHelper::ReleaseMainThreadObjects(); } @@ -103,11 +108,10 @@ private: nsRefPtr mObjectStore; // These may change in the autoincrement case. - JSAutoStructuredCloneBuffer mCloneBuffer; + StructuredCloneWriteInfo mCloneWriteInfo; Key mKey; const bool mOverwrite; nsTArray mIndexUpdateInfo; - PRUint64 mOffsetToKeyProp; }; class GetHelper : public AsyncConnectionHelper @@ -123,7 +127,7 @@ public: ~GetHelper() { - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer); + IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); @@ -134,7 +138,7 @@ public: { mObjectStore = nsnull; mKeyRange = nsnull; - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer); + IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); AsyncConnectionHelper::ReleaseMainThreadObjects(); } @@ -145,7 +149,7 @@ protected: private: // Out-params. - JSAutoStructuredCloneBuffer mCloneBuffer; + StructuredCloneReadInfo mCloneReadInfo; }; class DeleteHelper : public GetHelper @@ -199,7 +203,7 @@ public: ~OpenCursorHelper() { - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer); + IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); } nsresult DoDatabaseWork(mozIStorageConnection* aConnection); @@ -210,7 +214,7 @@ public: { mObjectStore = nsnull; mKeyRange = nsnull; - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffer); + IDBObjectStore::ClearStructuredCloneBuffer(mCloneReadInfo.mCloneBuffer); AsyncConnectionHelper::ReleaseMainThreadObjects(); } @@ -222,7 +226,7 @@ private: // Out-params. Key mKey; - JSAutoStructuredCloneBuffer mCloneBuffer; + StructuredCloneReadInfo mCloneReadInfo; nsCString mContinueQuery; nsCString mContinueToQuery; Key mRangeKey; @@ -312,8 +316,9 @@ public: ~GetAllHelper() { - for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) { - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffers[index]); + for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) { + IDBObjectStore::ClearStructuredCloneBuffer( + mCloneReadInfos[index].mCloneBuffer); } } @@ -325,8 +330,9 @@ public: { mObjectStore = nsnull; mKeyRange = nsnull; - for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) { - IDBObjectStore::ClearStructuredCloneBuffer(mCloneBuffers[index]); + for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) { + IDBObjectStore::ClearStructuredCloneBuffer( + mCloneReadInfos[index].mCloneBuffer); } AsyncConnectionHelper::ReleaseMainThreadObjects(); } @@ -339,7 +345,7 @@ protected: private: // Out-params. - nsTArray mCloneBuffers; + nsTArray mCloneReadInfos; }; class CountHelper : public AsyncConnectionHelper @@ -492,32 +498,6 @@ JSClass gDummyPropClass = { JSCLASS_NO_OPTIONAL_MEMBERS }; -JSBool -StructuredCloneWriteDummyProp(JSContext* aCx, - JSStructuredCloneWriter* aWriter, - JSObject* aObj, - void* aClosure) -{ - if (JS_GET_CLASS(aCx, aObj) == &gDummyPropClass) { - PRUint64* closure = reinterpret_cast(aClosure); - - NS_ASSERTION(*closure == 0, "We should not have been here before!"); - *closure = js_GetSCOffset(aWriter); - - PRUint64 value = 0; - return JS_WriteBytes(aWriter, &value, sizeof(value)); - } - - // try using the runtime callbacks - const JSStructuredCloneCallbacks* runtimeCallbacks = - aCx->runtime->structuredCloneCallbacks; - if (runtimeCallbacks) { - return runtimeCallbacks->write(aCx, aWriter, aObj, nsnull); - } - - return JS_FALSE; -} - } // anonymous namespace // static @@ -771,23 +751,26 @@ IDBObjectStore::UpdateIndexes(IDBTransaction* aTransaction, // static nsresult -IDBObjectStore::GetStructuredCloneDataFromStatement( +IDBObjectStore::GetStructuredCloneReadInfoFromStatement( mozIStorageStatement* aStatement, - PRUint32 aIndex, - JSAutoStructuredCloneBuffer& aBuffer) + PRUint32 aDataIndex, + PRUint32 aFileIdsIndex, + FileManager* aFileManager, + StructuredCloneReadInfo& aInfo) { #ifdef DEBUG { - PRInt32 valueType; - NS_ASSERTION(NS_SUCCEEDED(aStatement->GetTypeOfIndex(aIndex, &valueType)) && - valueType == mozIStorageStatement::VALUE_TYPE_BLOB, + PRInt32 type; + NS_ASSERTION(NS_SUCCEEDED(aStatement->GetTypeOfIndex(aDataIndex, &type)) && + type == mozIStorageStatement::VALUE_TYPE_BLOB, "Bad value type!"); } #endif const PRUint8* blobData; PRUint32 blobDataLength; - nsresult rv = aStatement->GetSharedBlob(aIndex, &blobDataLength, &blobData); + nsresult rv = aStatement->GetSharedBlob(aDataIndex, &blobDataLength, + &blobData); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); const char* compressed = reinterpret_cast(blobData); @@ -808,10 +791,38 @@ IDBObjectStore::GetStructuredCloneDataFromStatement( return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } - return aBuffer.copy(reinterpret_cast(uncompressed.get()), - uncompressedLength) ? - NS_OK : - NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + JSAutoStructuredCloneBuffer& buffer = aInfo.mCloneBuffer; + if (!buffer.copy(reinterpret_cast(uncompressed.get()), + uncompressedLength)) { + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + bool isNull; + rv = aStatement->GetIsNull(aFileIdsIndex, &isNull); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + if (isNull) { + return NS_OK; + } + + nsString ids; + rv = aStatement->GetString(aFileIdsIndex, ids); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + nsAutoTArray array; + rv = ConvertFileIdsToArray(ids, array); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + for (PRUint32 i = 0; i < array.Length(); i++) { + const PRInt64& id = array.ElementAt(i); + + nsRefPtr fileInfo = aFileManager->GetFileInfo(id); + NS_ASSERTION(fileInfo, "Null file info!"); + + aInfo.mFileInfos.AppendElement(fileInfo); + } + + return NS_OK; } // static @@ -826,32 +837,36 @@ IDBObjectStore::ClearStructuredCloneBuffer(JSAutoStructuredCloneBuffer& aBuffer) // static bool IDBObjectStore::DeserializeValue(JSContext* aCx, - JSAutoStructuredCloneBuffer& aBuffer, - jsval* aValue, - JSStructuredCloneCallbacks* aCallbacks, - void* aClosure) + StructuredCloneReadInfo& aCloneReadInfo, + jsval* aValue) { NS_ASSERTION(NS_IsMainThread(), "Should only be deserializing on the main thread!"); NS_ASSERTION(aCx, "A JSContext is required!"); - if (!aBuffer.data()) { + JSAutoStructuredCloneBuffer& buffer = aCloneReadInfo.mCloneBuffer; + + if (!buffer.data()) { *aValue = JSVAL_VOID; return true; } JSAutoRequest ar(aCx); - return aBuffer.read(aCx, aValue, aCallbacks, aClosure); + JSStructuredCloneCallbacks callbacks = { + IDBObjectStore::StructuredCloneReadCallback, + nsnull, + nsnull + }; + + return buffer.read(aCx, aValue, &callbacks, &aCloneReadInfo); } // static bool IDBObjectStore::SerializeValue(JSContext* aCx, - JSAutoStructuredCloneBuffer& aBuffer, - jsval aValue, - JSStructuredCloneCallbacks* aCallbacks, - void* aClosure) + StructuredCloneWriteInfo& aCloneWriteInfo, + jsval aValue) { NS_ASSERTION(NS_IsMainThread(), "Should only be serializing on the main thread!"); @@ -859,30 +874,264 @@ IDBObjectStore::SerializeValue(JSContext* aCx, JSAutoRequest ar(aCx); - return aBuffer.write(aCx, aValue, aCallbacks, aClosure); + JSStructuredCloneCallbacks callbacks = { + nsnull, + StructuredCloneWriteCallback, + nsnull + }; + + JSAutoStructuredCloneBuffer& buffer = aCloneWriteInfo.mCloneBuffer; + + return buffer.write(aCx, aValue, &callbacks, &aCloneWriteInfo); +} + +static inline PRUint32 +SwapBytes(PRUint32 u) +{ +#ifdef IS_BIG_ENDIAN + return ((u & 0x000000ffU) << 24) | + ((u & 0x0000ff00U) << 8) | + ((u & 0x00ff0000U) >> 8) | + ((u & 0xff000000U) >> 24); +#else + return u; +#endif } static inline jsdouble SwapBytes(PRUint64 u) { #ifdef IS_BIG_ENDIAN - return ((u & 0x00000000000000ffLLU) << 56) | - ((u & 0x000000000000ff00LLU) << 40) | - ((u & 0x0000000000ff0000LLU) << 24) | - ((u & 0x00000000ff000000LLU) << 8) | - ((u & 0x000000ff00000000LLU) >> 8) | - ((u & 0x0000ff0000000000LLU) >> 24) | - ((u & 0x00ff000000000000LLU) >> 40) | - ((u & 0xff00000000000000LLU) >> 56); + return ((u & 0x00000000000000ffLLU) << 56) | + ((u & 0x000000000000ff00LLU) << 40) | + ((u & 0x0000000000ff0000LLU) << 24) | + ((u & 0x00000000ff000000LLU) << 8) | + ((u & 0x000000ff00000000LLU) >> 8) | + ((u & 0x0000ff0000000000LLU) >> 24) | + ((u & 0x00ff000000000000LLU) >> 40) | + ((u & 0xff00000000000000LLU) >> 56); #else - return jsdouble(u); + return jsdouble(u); #endif } +static inline bool +StructuredCloneReadString(JSStructuredCloneReader* aReader, + nsCString& aString) +{ + PRUint32 length; + if (!JS_ReadBytes(aReader, &length, sizeof(PRUint32))) { + NS_WARNING("Failed to read length!"); + return false; + } + length = SwapBytes(length); + + if (!EnsureStringLength(aString, length)) { + NS_WARNING("Out of memory?"); + return false; + } + char* buffer = aString.BeginWriting(); + + if (!JS_ReadBytes(aReader, buffer, length)) { + NS_WARNING("Failed to read type!"); + return false; + } + + return true; +} + +JSObject* +IDBObjectStore::StructuredCloneReadCallback(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32 aTag, + uint32 aData, + void* aClosure) +{ + if (aTag == SCTAG_DOM_BLOB || aTag == SCTAG_DOM_FILE) { + StructuredCloneReadInfo* cloneReadInfo = + reinterpret_cast(aClosure); + + NS_ASSERTION(aData < cloneReadInfo->mFileInfos.Length(), + "Bad blob index!"); + + nsRefPtr fileInfo = cloneReadInfo->mFileInfos[aData]; + nsRefPtr fileManager = fileInfo->Manager(); + nsCOMPtr directory = fileManager->GetDirectory(); + if (!directory) { + return nsnull; + } + + nsCOMPtr nativeFile = + fileManager->GetFileForId(directory, fileInfo->Id()); + if (!nativeFile) { + return nsnull; + } + + PRUint64 size; + if (!JS_ReadBytes(aReader, &size, sizeof(PRUint64))) { + NS_WARNING("Failed to read size!"); + return nsnull; + } + size = SwapBytes(size); + + nsCString type; + if (!StructuredCloneReadString(aReader, type)) { + return nsnull; + } + NS_ConvertUTF8toUTF16 convType(type); + + if (aTag == SCTAG_DOM_BLOB) { + nsCOMPtr blob = new nsDOMFileFile(convType, size, + nativeFile, fileInfo); + + jsval wrappedBlob; + nsresult rv = + nsContentUtils::WrapNative(aCx, JS_GetGlobalForScopeChain(aCx), blob, + &NS_GET_IID(nsIDOMBlob), &wrappedBlob); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to wrap native!"); + return nsnull; + } + + return JSVAL_TO_OBJECT(wrappedBlob); + } + + nsCString name; + if (!StructuredCloneReadString(aReader, name)) { + return nsnull; + } + NS_ConvertUTF8toUTF16 convName(name); + + nsCOMPtr file = new nsDOMFileFile(convName, convType, size, + nativeFile, fileInfo); + + jsval wrappedFile; + nsresult rv = + nsContentUtils::WrapNative(aCx, JS_GetGlobalForScopeChain(aCx), file, + &NS_GET_IID(nsIDOMFile), &wrappedFile); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to wrap native!"); + return nsnull; + } + + return JSVAL_TO_OBJECT(wrappedFile); + } + + const JSStructuredCloneCallbacks* runtimeCallbacks = + aCx->runtime->structuredCloneCallbacks; + + if (runtimeCallbacks) { + return runtimeCallbacks->read(aCx, aReader, aTag, aData, nsnull); + } + + return nsnull; +} + +JSBool +IDBObjectStore::StructuredCloneWriteCallback(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + JSObject* aObj, + void* aClosure) +{ + StructuredCloneWriteInfo* cloneWriteInfo = + reinterpret_cast(aClosure); + + if (JS_GET_CLASS(aCx, aObj) == &gDummyPropClass) { + NS_ASSERTION(cloneWriteInfo->mOffsetToKeyProp == 0, + "We should not have been here before!"); + cloneWriteInfo->mOffsetToKeyProp = js_GetSCOffset(aWriter); + + PRUint64 value = 0; + return JS_WriteBytes(aWriter, &value, sizeof(value)); + } + + nsCOMPtr wrappedNative; + nsContentUtils::XPConnect()-> + GetWrappedNativeOfJSObject(aCx, aObj, getter_AddRefs(wrappedNative)); + + if (wrappedNative) { + nsISupports* supports = wrappedNative->Native(); + + nsCOMPtr blob = do_QueryInterface(supports); + if (blob) { + nsCOMPtr file = do_QueryInterface(blob); + + PRUint64 size; + if (NS_FAILED(blob->GetSize(&size))) { + return false; + } + size = SwapBytes(size); + + nsString type; + if (NS_FAILED(blob->GetType(type))) { + return false; + } + NS_ConvertUTF16toUTF8 convType(type); + PRUint32 convTypeLength = SwapBytes(convType.Length()); + + if (!JS_WriteUint32Pair(aWriter, file ? SCTAG_DOM_FILE : SCTAG_DOM_BLOB, + cloneWriteInfo->mBlobs.Length()) || + !JS_WriteBytes(aWriter, &size, sizeof(PRUint64)) || + !JS_WriteBytes(aWriter, &convTypeLength, sizeof(PRUint32)) || + !JS_WriteBytes(aWriter, convType.get(), convType.Length())) { + return false; + } + + if (file) { + nsString name; + if (NS_FAILED(file->GetName(name))) { + return false; + } + NS_ConvertUTF16toUTF8 convName(name); + PRUint32 convNameLength = SwapBytes(convName.Length()); + + if (!JS_WriteBytes(aWriter, &convNameLength, sizeof(PRUint32)) || + !JS_WriteBytes(aWriter, convName.get(), convName.Length())) { + return false; + } + } + + cloneWriteInfo->mBlobs.AppendElement(blob); + + return true; + } + } + + // try using the runtime callbacks + const JSStructuredCloneCallbacks* runtimeCallbacks = + aCx->runtime->structuredCloneCallbacks; + if (runtimeCallbacks) { + return runtimeCallbacks->write(aCx, aWriter, aObj, nsnull); + } + + return false; +} + nsresult -IDBObjectStore::ModifyValueForNewKey(JSAutoStructuredCloneBuffer& aBuffer, - Key& aKey, - PRUint64 aOffsetToKeyProp) +IDBObjectStore::ConvertFileIdsToArray(const nsAString& aFileIds, + nsTArray& aResult) +{ + nsCharSeparatedTokenizerTemplate tokenizer(aFileIds, ' '); + + while (tokenizer.hasMoreTokens()) { + nsString token(tokenizer.nextToken()); + + NS_ASSERTION(!token.IsEmpty(), "Should be a valid id!"); + + nsresult rv; + PRInt32 id = token.ToInteger(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt64* element = aResult.AppendElement(); + *element = id; + } + + return NS_OK; +} + +nsresult +IDBObjectStore::ModifyValueForNewKey(StructuredCloneWriteInfo& aCloneWriteInfo, + Key& aKey) { NS_ASSERTION(IsAutoIncrement() && aKey.IsInteger(), "Don't call me!"); NS_ASSERTION(!NS_IsMainThread(), "Wrong thread"); @@ -893,9 +1142,12 @@ IDBObjectStore::ModifyValueForNewKey(JSAutoStructuredCloneBuffer& aBuffer, PRUint64 u; } pun; - pun.d = SwapBytes(aKey.ToInteger()); + pun.d = SwapBytes(static_cast(aKey.ToInteger())); - memcpy((char*)aBuffer.data() + aOffsetToKeyProp, &pun.u, sizeof(PRUint64)); + JSAutoStructuredCloneBuffer& buffer = aCloneWriteInfo.mCloneBuffer; + PRUint64 offsetToKeyProp = aCloneWriteInfo.mOffsetToKeyProp; + + memcpy((char*)buffer.data() + offsetToKeyProp, &pun.u, sizeof(PRUint64)); return NS_OK; } @@ -915,10 +1167,9 @@ nsresult IDBObjectStore::GetAddInfo(JSContext* aCx, jsval aValue, jsval aKeyVal, - JSAutoStructuredCloneBuffer& aCloneBuffer, + StructuredCloneWriteInfo& aCloneWriteInfo, Key& aKey, - nsTArray& aUpdateInfoArray, - PRUint64* aOffsetToKeyProp) + nsTArray& aUpdateInfoArray) { nsresult rv; @@ -1066,18 +1317,12 @@ IDBObjectStore::GetAddInfo(JSContext* aCx, } } - JSStructuredCloneCallbacks callbacks = { - nsnull, - StructuredCloneWriteDummyProp, - nsnull - }; - *aOffsetToKeyProp = 0; + aCloneWriteInfo.mOffsetToKeyProp = 0; // We guard on rv being a success because we need to run the property // deletion code below even if we should not be serializing the value if (NS_SUCCEEDED(rv) && - !IDBObjectStore::SerializeValue(aCx, aCloneBuffer, aValue, &callbacks, - aOffsetToKeyProp)) { + !IDBObjectStore::SerializeValue(aCx, aCloneWriteInfo, aValue)) { rv = NS_ERROR_DOM_DATA_CLONE_ERR; } @@ -1118,13 +1363,12 @@ IDBObjectStore::AddOrPut(const jsval& aValue, jsval keyval = (aOptionalArgCount >= 1) ? aKey : JSVAL_VOID; - JSAutoStructuredCloneBuffer cloneBuffer; + StructuredCloneWriteInfo cloneWriteInfo; Key key; nsTArray updateInfo; - PRUint64 offset; - nsresult rv = - GetAddInfo(aCx, aValue, keyval, cloneBuffer, key, updateInfo, &offset); + nsresult rv = GetAddInfo(aCx, aValue, keyval, cloneWriteInfo, key, + updateInfo); if (NS_FAILED(rv)) { return rv; } @@ -1138,8 +1382,8 @@ IDBObjectStore::AddOrPut(const jsval& aValue, NS_ENSURE_TRUE(request, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsRefPtr helper = - new AddHelper(mTransaction, request, this, cloneBuffer, key, aOverwrite, - updateInfo, offset); + new AddHelper(mTransaction, request, this, cloneWriteInfo, key, aOverwrite, + updateInfo); rv = helper->DispatchToTransactionPool(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); @@ -1688,6 +1932,31 @@ IDBObjectStore::Count(const jsval& aKey, return NS_OK; } +inline nsresult +CopyData(nsIInputStream* aStream, quota_FILE* aFile) +{ + do { + char copyBuffer[FILE_COPY_BUFFER_SIZE]; + + PRUint32 numRead; + nsresult rv = aStream->Read(copyBuffer, FILE_COPY_BUFFER_SIZE, &numRead); + NS_ENSURE_SUCCESS(rv, rv); + + if (numRead <= 0) { + break; + } + + size_t numWrite = sqlite3_quota_fwrite(copyBuffer, 1, numRead, aFile); + NS_ENSURE_TRUE(numWrite == numRead, NS_ERROR_FAILURE); + } while (true); + + // Flush and sync + NS_ENSURE_TRUE(sqlite3_quota_fflush(aFile, 1) == 0, + NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + return NS_OK; +} + nsresult AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { @@ -1755,6 +2024,64 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection) } } + nsRefPtr fileManager = mDatabase->Manager(); + nsCOMPtr directory = fileManager->GetDirectory(); + NS_ENSURE_TRUE(directory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + nsAutoString fileIds; + + for (PRUint32 index = 0; index < mCloneWriteInfo.mBlobs.Length(); index++) { + nsCOMPtr& domBlob = mCloneWriteInfo.mBlobs[index]; + + PRInt64 id = -1; + + // Check if it is a blob created from this db or the blob was already + // stored in this db + nsRefPtr fileInfo = domBlob->GetFileInfo(fileManager); + if (fileInfo) { + id = fileInfo->Id(); + } + + if (id == -1) { + fileInfo = fileManager->GetNewFileInfo(); + NS_ENSURE_TRUE(fileInfo, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + id = fileInfo->Id(); + + mTransaction->OnNewFileInfo(fileInfo); + + // Copy it + nsCOMPtr inputStream; + rv = domBlob->GetInternalStream(getter_AddRefs(inputStream)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + nsCOMPtr nativeFile = fileManager->GetFileForId(directory, id); + NS_ENSURE_TRUE(nativeFile, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + nsString nativeFilePath; + rv = nativeFile->GetPath(nativeFilePath); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + quota_FILE* file = + sqlite3_quota_fopen(NS_ConvertUTF16toUTF8(nativeFilePath).get(), "wb"); + NS_ENSURE_TRUE(file, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + rv = CopyData(inputStream, file); + + NS_ENSURE_TRUE(sqlite3_quota_fclose(file) == 0, + NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + domBlob->AddFileInfo(fileInfo); + } + + if (index) { + fileIds.Append(NS_LITERAL_STRING(" ")); + } + fileIds.AppendInt(id); + } + // Now we add it to the database (or update, depending on our variables). stmt = mTransaction->AddStatement(true, mayOverwrite, autoIncrement); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); @@ -1793,8 +2120,8 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection) else { // Compress the bytes before adding into the database. const char* uncompressed = - reinterpret_cast(mCloneBuffer.data()); - size_t uncompressedLength = mCloneBuffer.nbytes(); + reinterpret_cast(mCloneWriteInfo.mCloneBuffer.data()); + size_t uncompressedLength = mCloneWriteInfo.mCloneBuffer.nbytes(); size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength); compressed = new char[compressedLength]; @@ -1809,6 +2136,13 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection) NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } + if (fileIds.IsEmpty()) { + rv = stmt->BindNullByName(NS_LITERAL_CSTRING("file_ids")); + } else { + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("file_ids"), fileIds); + } + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + rv = stmt->Execute(); if (NS_FAILED(rv)) { if (mayOverwrite && rv == NS_ERROR_STORAGE_CONSTRAINT) { @@ -1835,6 +2169,13 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection) rv = stmt->BindBlobByName(data, dataBuffer, dataBufferLength); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + if (fileIds.IsEmpty()) { + rv = stmt->BindNullByName(NS_LITERAL_CSTRING("file_ids")); + } else { + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("file_ids"), fileIds); + } + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + rv = stmt->Execute(); } @@ -1867,8 +2208,7 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection) // Special case where someone put an object into an autoIncrement'ing // objectStore with no key in its keyPath set. We needed to figure out // which row id we would get above before we could set that properly. - rv = mObjectStore->ModifyValueForNewKey(mCloneBuffer, mKey, - mOffsetToKeyProp); + rv = mObjectStore->ModifyValueForNewKey(mCloneWriteInfo, mKey); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); scoper.Abandon(); @@ -1889,8 +2229,8 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection) NS_ASSERTION(!dataBuffer && !dataBufferLength, "These should be unset!"); const char* uncompressed = - reinterpret_cast(mCloneBuffer.data()); - size_t uncompressedLength = mCloneBuffer.nbytes(); + reinterpret_cast(mCloneWriteInfo.mCloneBuffer.data()); + size_t uncompressedLength = mCloneWriteInfo.mCloneBuffer.nbytes(); size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength); @@ -1905,6 +2245,13 @@ AddHelper::DoDatabaseWork(mozIStorageConnection* aConnection) rv = stmt->BindBlobByName(data, dataBuffer, dataBufferLength); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + if (fileIds.IsEmpty()) { + rv = stmt->BindNullByName(NS_LITERAL_CSTRING("file_ids")); + } else { + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("file_ids"), fileIds); + } + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + rv = stmt->Execute(); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); } @@ -1931,7 +2278,7 @@ AddHelper::GetSuccessResult(JSContext* aCx, { NS_ASSERTION(!mKey.IsUnset(), "Badness!"); - mCloneBuffer.clear(); + mCloneWriteInfo.mCloneBuffer.clear(); return mKey.ToJSVal(aCx, aVal); } @@ -1959,7 +2306,7 @@ GetHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */) NS_NAMED_LITERAL_CSTRING(osid, "osid"); - nsCString query = NS_LITERAL_CSTRING("SELECT data FROM ") + table + + nsCString query = NS_LITERAL_CSTRING("SELECT data, file_ids FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + osid + keyRangeClause + NS_LITERAL_CSTRING(" LIMIT 1"); @@ -1979,8 +2326,8 @@ GetHelper::DoDatabaseWork(mozIStorageConnection* /* aConnection */) NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); if (hasResult) { - rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 0, - mCloneBuffer); + rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 0, 1, + mDatabase->Manager(), mCloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); } @@ -1991,9 +2338,9 @@ nsresult GetHelper::GetSuccessResult(JSContext* aCx, jsval* aVal) { - bool result = IDBObjectStore::DeserializeValue(aCx, mCloneBuffer, aVal); + bool result = IDBObjectStore::DeserializeValue(aCx, mCloneReadInfo, aVal); - mCloneBuffer.clear(); + mCloneReadInfo.mCloneBuffer.clear(); NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); return NS_OK; @@ -2121,7 +2468,7 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) } nsCString firstQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn + - NS_LITERAL_CSTRING(", data FROM ") + table + + NS_LITERAL_CSTRING(", data, file_ids FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id + keyRangeClause + directionClause + NS_LITERAL_CSTRING(" LIMIT 1"); @@ -2152,8 +2499,8 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) rv = mKey.SetFromStatement(stmt, 0); NS_ENSURE_SUCCESS(rv, rv); - rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 1, - mCloneBuffer); + rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 1, 2, + mDatabase->Manager(), mCloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); // Now we need to make the query to get the next match. @@ -2200,13 +2547,13 @@ OpenCursorHelper::DoDatabaseWork(mozIStorageConnection* aConnection) } mContinueQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn + - NS_LITERAL_CSTRING(", data FROM ") + table + + NS_LITERAL_CSTRING(", data, file_ids FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id + keyRangeClause + directionClause + NS_LITERAL_CSTRING(" LIMIT "); mContinueToQuery = NS_LITERAL_CSTRING("SELECT ") + keyColumn + - NS_LITERAL_CSTRING(", data FROM ") + table + + NS_LITERAL_CSTRING(", data, file_ids FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + id + continueToKeyRangeClause + directionClause + NS_LITERAL_CSTRING(" LIMIT "); @@ -2226,10 +2573,10 @@ OpenCursorHelper::GetSuccessResult(JSContext* aCx, nsRefPtr cursor = IDBCursor::Create(mRequest, mTransaction, mObjectStore, mDirection, mRangeKey, mContinueQuery, mContinueToQuery, mKey, - mCloneBuffer); + mCloneReadInfo); NS_ENSURE_TRUE(cursor, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - NS_ASSERTION(!mCloneBuffer.data(), "Should have swapped!"); + NS_ASSERTION(!mCloneReadInfo.mCloneBuffer.data(), "Should have swapped!"); return WrapNative(aCx, cursor, aVal); } @@ -2391,11 +2738,11 @@ CreateIndexHelper::InsertDataFromObjectStore(mozIStorageConnection* aConnection) nsCAutoString columns; if (mIndex->IsAutoIncrement()) { table.AssignLiteral("ai_object_data"); - columns.AssignLiteral("id, data"); + columns.AssignLiteral("id, data, file_ids"); } else { table.AssignLiteral("object_data"); - columns.AssignLiteral("id, data, key_value"); + columns.AssignLiteral("id, data, file_ids, key_value"); } nsCString query = NS_LITERAL_CSTRING("SELECT ") + columns + @@ -2435,12 +2782,21 @@ CreateIndexHelper::InsertDataFromObjectStore(mozIStorageConnection* aConnection) JSAutoRequest ar(cx); do { - JSAutoStructuredCloneBuffer buffer; - rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 1, buffer); + StructuredCloneReadInfo cloneReadInfo; + rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 1, 2, + mDatabase->Manager(), cloneReadInfo); NS_ENSURE_SUCCESS(rv, rv); + JSAutoStructuredCloneBuffer& buffer = cloneReadInfo.mCloneBuffer; + + JSStructuredCloneCallbacks callbacks = { + IDBObjectStore::StructuredCloneReadCallback, + nsnull, + nsnull + }; + jsval clone; - if (!buffer.read(cx, &clone)) { + if (!buffer.read(cx, &clone, &callbacks, &cloneReadInfo)) { NS_WARNING("Failed to deserialize structured clone data!"); return NS_ERROR_DOM_DATA_CLONE_ERR; } @@ -2458,7 +2814,7 @@ CreateIndexHelper::InsertDataFromObjectStore(mozIStorageConnection* aConnection) Key key; if (!mIndex->IsAutoIncrement()) { - rv = key.SetFromStatement(stmt, 2); + rv = key.SetFromStatement(stmt, 3); NS_ENSURE_SUCCESS(rv, rv); } else { @@ -2550,12 +2906,12 @@ GetAllHelper::DoDatabaseWork(mozIStorageConnection* aConnection) limitClause.AppendInt(mLimit); } - nsCString query = NS_LITERAL_CSTRING("SELECT data FROM ") + table + + nsCString query = NS_LITERAL_CSTRING("SELECT data, file_ids FROM ") + table + NS_LITERAL_CSTRING(" WHERE object_store_id = :") + osid + keyRangeClause + NS_LITERAL_CSTRING(" ORDER BY ") + keyColumn + NS_LITERAL_CSTRING(" ASC") + limitClause; - mCloneBuffers.SetCapacity(50); + mCloneReadInfos.SetCapacity(50); nsCOMPtr stmt = mTransaction->GetCachedStatement(query); NS_ENSURE_TRUE(stmt, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); @@ -2578,17 +2934,18 @@ GetAllHelper::DoDatabaseWork(mozIStorageConnection* aConnection) bool hasResult; while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) { - if (mCloneBuffers.Capacity() == mCloneBuffers.Length()) { - if (!mCloneBuffers.SetCapacity(mCloneBuffers.Capacity() * 2)) { + if (mCloneReadInfos.Capacity() == mCloneReadInfos.Length()) { + if (!mCloneReadInfos.SetCapacity(mCloneReadInfos.Capacity() * 2)) { NS_ERROR("Out of memory!"); return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } } - JSAutoStructuredCloneBuffer* buffer = mCloneBuffers.AppendElement(); - NS_ASSERTION(buffer, "Shouldn't fail if SetCapacity succeeded!"); + StructuredCloneReadInfo* readInfo = mCloneReadInfos.AppendElement(); + NS_ASSERTION(readInfo, "Shouldn't fail if SetCapacity succeeded!"); - rv = IDBObjectStore::GetStructuredCloneDataFromStatement(stmt, 0, *buffer); + rv = IDBObjectStore::GetStructuredCloneReadInfoFromStatement(stmt, 0, 1, + mDatabase->Manager(), *readInfo); NS_ENSURE_SUCCESS(rv, rv); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); @@ -2600,12 +2957,12 @@ nsresult GetAllHelper::GetSuccessResult(JSContext* aCx, jsval* aVal) { - NS_ASSERTION(mCloneBuffers.Length() <= mLimit, "Too many results!"); + NS_ASSERTION(mCloneReadInfos.Length() <= mLimit, "Too many results!"); - nsresult rv = ConvertCloneBuffersToArray(aCx, mCloneBuffers, aVal); + nsresult rv = ConvertCloneReadInfosToArray(aCx, mCloneReadInfos, aVal); - for (PRUint32 index = 0; index < mCloneBuffers.Length(); index++) { - mCloneBuffers[index].clear(); + for (PRUint32 index = 0; index < mCloneReadInfos.Length(); index++) { + mCloneReadInfos[index].mCloneBuffer.clear(); } NS_ENSURE_SUCCESS(rv, rv); diff --git a/dom/indexedDB/IDBObjectStore.h b/dom/indexedDB/IDBObjectStore.h index 34554c61b1e..798c9b3e720 100644 --- a/dom/indexedDB/IDBObjectStore.h +++ b/dom/indexedDB/IDBObjectStore.h @@ -59,6 +59,8 @@ class Key; struct ObjectStoreInfo; struct IndexInfo; struct IndexUpdateInfo; +struct StructuredCloneReadInfo; +struct StructuredCloneWriteInfo; class IDBObjectStore : public nsIIDBObjectStore { @@ -94,26 +96,40 @@ public: const nsTArray& aUpdateInfoArray); static nsresult - GetStructuredCloneDataFromStatement(mozIStorageStatement* aStatement, - PRUint32 aIndex, - JSAutoStructuredCloneBuffer& aBuffer); + GetStructuredCloneReadInfoFromStatement(mozIStorageStatement* aStatement, + PRUint32 aDataIndex, + PRUint32 aFileIdsIndex, + FileManager* aFileManager, + StructuredCloneReadInfo& aInfo); static void ClearStructuredCloneBuffer(JSAutoStructuredCloneBuffer& aBuffer); static bool DeserializeValue(JSContext* aCx, - JSAutoStructuredCloneBuffer& aBuffer, - jsval* aValue, - JSStructuredCloneCallbacks* aCallbacks = nsnull, - void* aClosure = nsnull); + StructuredCloneReadInfo& aCloneReadInfo, + jsval* aValue); static bool SerializeValue(JSContext* aCx, - JSAutoStructuredCloneBuffer& aBuffer, - jsval aValue, - JSStructuredCloneCallbacks* aCallbacks = nsnull, - void* aClosure = nsnull); + StructuredCloneWriteInfo& aCloneWriteInfo, + jsval aValue); + + static JSObject* + StructuredCloneReadCallback(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32 aTag, + uint32 aData, + void* aClosure); + static JSBool + StructuredCloneWriteCallback(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + JSObject* aObj, + void* aClosure); + + static nsresult + ConvertFileIdsToArray(const nsAString& aFileIds, + nsTArray& aResult); const nsString& Name() const { @@ -151,9 +167,8 @@ public: return mTransaction; } - nsresult ModifyValueForNewKey(JSAutoStructuredCloneBuffer& aBuffer, - Key& aKey, - PRUint64 aOffsetToKeyProp); + nsresult ModifyValueForNewKey(StructuredCloneWriteInfo& aCloneWriteInfo, + Key& aKey); protected: IDBObjectStore(); @@ -162,10 +177,9 @@ protected: nsresult GetAddInfo(JSContext* aCx, jsval aValue, jsval aKeyVal, - JSAutoStructuredCloneBuffer& aCloneBuffer, + StructuredCloneWriteInfo& aCloneWriteInfo, Key& aKey, - nsTArray& aUpdateInfoArray, - PRUint64* aOffsetToKeyProp); + nsTArray& aUpdateInfoArray); nsresult AddOrPut(const jsval& aValue, const jsval& aKey, diff --git a/dom/indexedDB/IDBTransaction.cpp b/dom/indexedDB/IDBTransaction.cpp index 5bc778b689a..1b9e18bcc07 100644 --- a/dom/indexedDB/IDBTransaction.cpp +++ b/dom/indexedDB/IDBTransaction.cpp @@ -43,6 +43,7 @@ #include "mozilla/storage.h" #include "nsDOMClassInfoID.h" +#include "nsDOMLists.h" #include "nsEventDispatcher.h" #include "nsPIDOMWindow.h" #include "nsProxyRelease.h" @@ -325,8 +326,21 @@ IDBTransaction::GetOrCreateConnection(mozIStorageConnection** aResult) IDBFactory::GetConnection(mDatabase->FilePath()); NS_ENSURE_TRUE(connection, NS_ERROR_FAILURE); + nsresult rv; + + nsRefPtr function; nsCString beginTransaction; if (mMode != nsIIDBTransaction::READ_ONLY) { + function = new UpdateRefcountFunction(Database()->Manager()); + NS_ENSURE_TRUE(function, NS_ERROR_OUT_OF_MEMORY); + + rv = function->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = connection->CreateFunction( + NS_LITERAL_CSTRING("update_refcount"), 2, function); + NS_ENSURE_SUCCESS(rv, rv); + beginTransaction.AssignLiteral("BEGIN IMMEDIATE TRANSACTION;"); } else { @@ -334,13 +348,13 @@ IDBTransaction::GetOrCreateConnection(mozIStorageConnection** aResult) } nsCOMPtr stmt; - nsresult rv = connection->CreateStatement(beginTransaction, - getter_AddRefs(stmt)); - NS_ENSURE_SUCCESS(rv, false); + rv = connection->CreateStatement(beginTransaction, getter_AddRefs(stmt)); + NS_ENSURE_SUCCESS(rv, rv); rv = stmt->Execute(); - NS_ENSURE_SUCCESS(rv, false); + NS_ENSURE_SUCCESS(rv, rv); + function.swap(mUpdateFileRefcountFunction); connection.swap(mConnection); } @@ -364,18 +378,19 @@ IDBTransaction::AddStatement(bool aCreate, if (aCreate) { if (aOverwrite) { return GetCachedStatement( - "INSERT OR FAIL INTO ai_object_data (object_store_id, id, data) " - "VALUES (:osid, :key_value, :data)" + "INSERT OR FAIL INTO ai_object_data (object_store_id, id, data, " + "file_ids) " + "VALUES (:osid, :key_value, :data, :file_ids)" ); } return GetCachedStatement( - "INSERT INTO ai_object_data (object_store_id, data) " - "VALUES (:osid, :data)" + "INSERT INTO ai_object_data (object_store_id, data, file_ids) " + "VALUES (:osid, :data, :file_ids)" ); } return GetCachedStatement( "UPDATE ai_object_data " - "SET data = :data " + "SET data = :data, file_ids = :file_ids " "WHERE object_store_id = :osid " "AND id = :key_value" ); @@ -383,18 +398,19 @@ IDBTransaction::AddStatement(bool aCreate, if (aCreate) { if (aOverwrite) { return GetCachedStatement( - "INSERT OR FAIL INTO object_data (object_store_id, key_value, data) " - "VALUES (:osid, :key_value, :data)" + "INSERT OR FAIL INTO object_data (object_store_id, key_value, data, " + "file_ids) " + "VALUES (:osid, :key_value, :data, :file_ids)" ); } return GetCachedStatement( - "INSERT INTO object_data (object_store_id, key_value, data) " - "VALUES (:osid, :key_value, :data)" + "INSERT INTO object_data (object_store_id, key_value, data, file_ids) " + "VALUES (:osid, :key_value, :data, :file_ids)" ); } return GetCachedStatement( "UPDATE object_data " - "SET data = :data " + "SET data = :data, file_ids = :file_ids " "WHERE object_store_id = :osid " "AND key_value = :key_value" ); @@ -552,6 +568,18 @@ IDBTransaction::GetOrCreateObjectStore(const nsAString& aName, return retval.forget(); } +void +IDBTransaction::OnNewFileInfo(FileInfo* aFileInfo) +{ + mCreatedFileInfos.AppendElement(aFileInfo); +} + +void +IDBTransaction::ClearCreatedFileInfos() +{ + mCreatedFileInfos.Clear(); +} + NS_IMPL_CYCLE_COLLECTION_CLASS(IDBTransaction) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IDBTransaction, @@ -827,6 +855,7 @@ CommitHelper::CommitHelper(IDBTransaction* aTransaction, mHaveMetadata(false) { mConnection.swap(aTransaction->mConnection); + mUpdateFileRefcountFunction.swap(aTransaction->mUpdateFileRefcountFunction); } CommitHelper::~CommitHelper() @@ -843,6 +872,14 @@ CommitHelper::Run() mTransaction->mReadyState = nsIIDBTransaction::DONE; + // Release file infos on the main thread, so they will eventually get + // destroyed on correct thread. + mTransaction->ClearCreatedFileInfos(); + if (mUpdateFileRefcountFunction) { + mUpdateFileRefcountFunction->ClearFileInfoEntries(); + mUpdateFileRefcountFunction = nsnull; + } + nsCOMPtr event; if (mAborted) { if (mHaveMetadata) { @@ -896,9 +933,19 @@ CommitHelper::Run() if (mConnection) { IndexedDatabaseManager::SetCurrentWindow(database->Owner()); + if (!mAborted && mUpdateFileRefcountFunction && + NS_FAILED(mUpdateFileRefcountFunction->UpdateDatabase(mConnection))) { + mAborted = true; + } + if (!mAborted) { NS_NAMED_LITERAL_CSTRING(release, "COMMIT TRANSACTION"); - if (NS_FAILED(mConnection->ExecuteSimpleSQL(release))) { + if (NS_SUCCEEDED(mConnection->ExecuteSimpleSQL(release))) { + if (mUpdateFileRefcountFunction) { + mUpdateFileRefcountFunction->UpdateFileInfos(); + } + } + else { mAborted = true; } } @@ -927,6 +974,14 @@ CommitHelper::Run() mDoomedObjects.Clear(); if (mConnection) { + if (mUpdateFileRefcountFunction) { + nsresult rv = mConnection->RemoveFunction( + NS_LITERAL_CSTRING("update_refcount")); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to remove function!"); + } + } + mConnection->Close(); mConnection = nsnull; @@ -935,3 +990,191 @@ CommitHelper::Run() return NS_OK; } + +nsresult +UpdateRefcountFunction::Init() +{ + NS_ENSURE_TRUE(mFileInfoEntries.Init(), NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + +NS_IMPL_THREADSAFE_ISUPPORTS1(UpdateRefcountFunction, mozIStorageFunction) + +NS_IMETHODIMP +UpdateRefcountFunction::OnFunctionCall(mozIStorageValueArray* aValues, + nsIVariant** _retval) +{ + *_retval = nsnull; + + PRUint32 numEntries; + nsresult rv = aValues->GetNumEntries(&numEntries); + NS_ENSURE_SUCCESS(rv, rv); + NS_ASSERTION(numEntries == 2, "unexpected number of arguments"); + +#ifdef DEBUG + PRInt32 type1 = mozIStorageValueArray::VALUE_TYPE_NULL; + aValues->GetTypeOfIndex(0, &type1); + + PRInt32 type2 = mozIStorageValueArray::VALUE_TYPE_NULL; + aValues->GetTypeOfIndex(1, &type2); + + NS_ASSERTION(!(type1 == mozIStorageValueArray::VALUE_TYPE_NULL && + type2 == mozIStorageValueArray::VALUE_TYPE_NULL), + "Shouldn't be called!"); +#endif + + rv = ProcessValue(aValues, 0, eDecrement); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ProcessValue(aValues, 1, eIncrement); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +UpdateRefcountFunction::ProcessValue(mozIStorageValueArray* aValues, + PRInt32 aIndex, + UpdateType aUpdateType) +{ + PRInt32 type; + aValues->GetTypeOfIndex(aIndex, &type); + if (type == mozIStorageValueArray::VALUE_TYPE_NULL) { + return NS_OK; + } + + nsString ids; + aValues->GetString(aIndex, ids); + + nsTArray fileIds; + nsresult rv = IDBObjectStore::ConvertFileIdsToArray(ids, fileIds); + NS_ENSURE_SUCCESS(rv, rv); + + for (PRUint32 i = 0; i < fileIds.Length(); i++) { + PRInt64 id = fileIds.ElementAt(i); + + FileInfoEntry* entry; + if (!mFileInfoEntries.Get(id, &entry)) { + nsRefPtr fileInfo = mFileManager->GetFileInfo(id); + NS_ASSERTION(fileInfo, "Shouldn't be null!"); + + nsAutoPtr newEntry(new FileInfoEntry(fileInfo)); + if (!mFileInfoEntries.Put(id, newEntry)) { + NS_WARNING("Out of memory?"); + return NS_ERROR_OUT_OF_MEMORY; + } + entry = newEntry.forget(); + } + + switch (aUpdateType) { + case eIncrement: + entry->mDelta++; + break; + case eDecrement: + entry->mDelta--; + break; + default: + NS_NOTREACHED("Unknown update type!"); + } + } + + return NS_OK; +} + +PLDHashOperator +UpdateRefcountFunction::DatabaseUpdateCallback(const PRUint64& aKey, + FileInfoEntry* aValue, + void* aUserArg) +{ + if (!aValue->mDelta) { + return PL_DHASH_NEXT; + } + + DatabaseUpdateFunction* function = + static_cast(aUserArg); + + if (!function->Update(aKey, aValue->mDelta)) { + return PL_DHASH_STOP; + } + + return PL_DHASH_NEXT; +} + +PLDHashOperator +UpdateRefcountFunction::FileInfoUpdateCallback(const PRUint64& aKey, + FileInfoEntry* aValue, + void* aUserArg) +{ + if (aValue->mDelta) { + aValue->mFileInfo->UpdateDBRefs(aValue->mDelta); + } + + return PL_DHASH_NEXT; +} + +bool +UpdateRefcountFunction::DatabaseUpdateFunction::Update(PRInt64 aId, + PRInt32 aDelta) +{ + nsresult rv = UpdateInternal(aId, aDelta); + if (NS_FAILED(rv)) { + mErrorCode = rv; + return false; + } + + return true; +} + +nsresult +UpdateRefcountFunction::DatabaseUpdateFunction::UpdateInternal(PRInt64 aId, + PRInt32 aDelta) +{ + nsresult rv; + + if (!mUpdateStatement) { + rv = mConnection->CreateStatement(NS_LITERAL_CSTRING( + "UPDATE file SET refcount = refcount + :delta WHERE id = :id" + ), getter_AddRefs(mUpdateStatement)); + NS_ENSURE_SUCCESS(rv, rv); + } + + mozStorageStatementScoper updateScoper(mUpdateStatement); + + rv = mUpdateStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mUpdateStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mUpdateStatement->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + PRInt32 rows; + rv = mConnection->GetAffectedRows(&rows); + NS_ENSURE_SUCCESS(rv, rv); + + if (rows > 0) { + return NS_OK; + } + + if (!mInsertStatement) { + rv = mConnection->CreateStatement(NS_LITERAL_CSTRING( + "INSERT INTO file (id, refcount) VALUES(:id, :delta)" + ), getter_AddRefs(mInsertStatement)); + NS_ENSURE_SUCCESS(rv, rv); + } + + mozStorageStatementScoper insertScoper(mInsertStatement); + + rv = mInsertStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mInsertStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mInsertStatement->Execute(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} diff --git a/dom/indexedDB/IDBTransaction.h b/dom/indexedDB/IDBTransaction.h index 0bb02c2a764..329102debfb 100644 --- a/dom/indexedDB/IDBTransaction.h +++ b/dom/indexedDB/IDBTransaction.h @@ -42,7 +42,11 @@ #include "mozilla/dom/indexedDB/IndexedDatabase.h" #include "mozilla/dom/indexedDB/IDBDatabase.h" +#include "mozilla/dom/indexedDB/FileInfo.h" +#include "mozIStorageConnection.h" +#include "mozIStorageStatement.h" +#include "mozIStorageFunction.h" #include "nsIIDBTransaction.h" #include "nsIRunnable.h" #include "nsIThreadInternal.h" @@ -51,11 +55,10 @@ #include "nsCycleCollectionParticipant.h" #include "nsAutoPtr.h" +#include "nsClassHashtable.h" #include "nsHashKeys.h" #include "nsInterfaceHashtable.h" -class mozIStorageConnection; -class mozIStorageStatement; class nsIThread; BEGIN_INDEXEDDB_NAMESPACE @@ -64,6 +67,7 @@ class AsyncConnectionHelper; class CommitHelper; struct ObjectStoreInfo; class TransactionThreadPool; +class UpdateRefcountFunction; class IDBTransactionListener { @@ -167,6 +171,10 @@ public: GetOrCreateObjectStore(const nsAString& aName, ObjectStoreInfo* aObjectStoreInfo); + void OnNewFileInfo(FileInfo* aFileInfo); + + void ClearCreatedFileInfos(); + private: IDBTransaction(); ~IDBTransaction(); @@ -204,6 +212,9 @@ private: #ifdef DEBUG bool mFiredCompleteOrAbort; #endif + + nsRefPtr mUpdateFileRefcountFunction; + nsTArray > mCreatedFileInfos; }; class CommitHelper : public nsIRunnable @@ -233,6 +244,7 @@ private: nsRefPtr mTransaction; nsRefPtr mListener; nsCOMPtr mConnection; + nsRefPtr mUpdateFileRefcountFunction; nsAutoTArray, 10> mDoomedObjects; PRUint64 mOldVersion; @@ -242,6 +254,101 @@ private: bool mHaveMetadata; }; +class UpdateRefcountFunction : public mozIStorageFunction +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_MOZISTORAGEFUNCTION + + UpdateRefcountFunction(FileManager* aFileManager) + : mFileManager(aFileManager) + { } + + ~UpdateRefcountFunction() + { } + + nsresult Init(); + + void ClearFileInfoEntries() + { + mFileInfoEntries.Clear(); + } + + nsresult UpdateDatabase(mozIStorageConnection* aConnection) + { + DatabaseUpdateFunction function(aConnection); + + mFileInfoEntries.EnumerateRead(DatabaseUpdateCallback, &function); + + return function.ErrorCode(); + } + + void UpdateFileInfos() + { + mFileInfoEntries.EnumerateRead(FileInfoUpdateCallback, nsnull); + } + +private: + class FileInfoEntry + { + public: + FileInfoEntry(FileInfo* aFileInfo) + : mFileInfo(aFileInfo), mDelta(0) + { } + + ~FileInfoEntry() + { } + + nsRefPtr mFileInfo; + PRInt32 mDelta; + }; + + enum UpdateType { + eIncrement, + eDecrement + }; + + class DatabaseUpdateFunction + { + public: + DatabaseUpdateFunction(mozIStorageConnection* aConnection) + : mConnection(aConnection), mErrorCode(NS_OK) + { } + + bool Update(PRInt64 aId, PRInt32 aDelta); + nsresult ErrorCode() + { + return mErrorCode; + } + + private: + nsresult UpdateInternal(PRInt64 aId, PRInt32 aDelta); + + nsCOMPtr mConnection; + nsCOMPtr mUpdateStatement; + nsCOMPtr mInsertStatement; + + nsresult mErrorCode; + }; + + nsresult ProcessValue(mozIStorageValueArray* aValues, + PRInt32 aIndex, + UpdateType aUpdateType); + + static PLDHashOperator + DatabaseUpdateCallback(const PRUint64& aKey, + FileInfoEntry* aValue, + void* aUserArg); + + static PLDHashOperator + FileInfoUpdateCallback(const PRUint64& aKey, + FileInfoEntry* aValue, + void* aUserArg); + + FileManager* mFileManager; + nsClassHashtable mFileInfoEntries; +}; + END_INDEXEDDB_NAMESPACE #endif // mozilla_dom_indexeddb_idbtransaction_h__ diff --git a/dom/indexedDB/IndexedDatabase.h b/dom/indexedDB/IndexedDatabase.h index 59f1eaf5058..acb3da5823d 100644 --- a/dom/indexedDB/IndexedDatabase.h +++ b/dom/indexedDB/IndexedDatabase.h @@ -60,8 +60,34 @@ #define USING_INDEXEDDB_NAMESPACE \ using namespace mozilla::dom::indexedDB; +class nsIDOMBlob; + BEGIN_INDEXEDDB_NAMESPACE +class FileInfo; + +struct StructuredCloneReadInfo { + void Swap(StructuredCloneReadInfo& aCloneReadInfo) { + mCloneBuffer.swap(aCloneReadInfo.mCloneBuffer); + mFileInfos.SwapElements(aCloneReadInfo.mFileInfos); + } + + JSAutoStructuredCloneBuffer mCloneBuffer; + nsTArray > mFileInfos; +}; + +struct StructuredCloneWriteInfo { + void Swap(StructuredCloneWriteInfo& aCloneWriteInfo) { + mCloneBuffer.swap(aCloneWriteInfo.mCloneBuffer); + mBlobs.SwapElements(aCloneWriteInfo.mBlobs); + mOffsetToKeyProp = aCloneWriteInfo.mOffsetToKeyProp; + } + + JSAutoStructuredCloneBuffer mCloneBuffer; + nsTArray > mBlobs; + PRUint64 mOffsetToKeyProp; +}; + inline void AppendConditionClause(const nsACString& aColumnName, diff --git a/dom/indexedDB/IndexedDatabaseManager.cpp b/dom/indexedDB/IndexedDatabaseManager.cpp index 264079af019..25156a610bf 100644 --- a/dom/indexedDB/IndexedDatabaseManager.cpp +++ b/dom/indexedDB/IndexedDatabaseManager.cpp @@ -54,6 +54,7 @@ #include "nsThreadUtils.h" #include "nsXPCOM.h" #include "nsXPCOMPrivate.h" +#include "test_quota.h" #include "AsyncConnectionHelper.h" #include "CheckQuotaHelper.h" @@ -61,6 +62,7 @@ #include "IDBEvents.h" #include "IDBFactory.h" #include "LazyIdleThread.h" +#include "OpenDatabaseHelper.h" #include "TransactionThreadPool.h" // The amount of time, in milliseconds, that our IO thread will stay alive @@ -87,12 +89,33 @@ using mozilla::Preferences; namespace { PRInt32 gShutdown = 0; +PRInt32 gClosed = 0; // Does not hold a reference. IndexedDatabaseManager* gInstance = nsnull; PRInt32 gIndexedDBQuotaMB = DEFAULT_QUOTA_MB; +bool +GetBaseFilename(const nsAString& aFilename, + nsAString& aBaseFilename) +{ + NS_ASSERTION(!aFilename.IsEmpty(), "Bad argument!"); + + NS_NAMED_LITERAL_STRING(sqlite, ".sqlite"); + nsAString::size_type filenameLen = aFilename.Length(); + nsAString::size_type sqliteLen = sqlite.Length(); + + if (sqliteLen > filenameLen || + Substring(aFilename, filenameLen - sqliteLen, sqliteLen) != sqlite) { + return false; + } + + aBaseFilename = Substring(aFilename, 0, filenameLen - sqliteLen); + + return true; +} + class QuotaCallback : public mozIStorageQuotaCallback { public: @@ -138,11 +161,29 @@ EnumerateToTArray(const nsACString& aKey, return PL_DHASH_NEXT; } +PLDHashOperator +InvalidateAllFileManagers(const nsACString& aKey, + nsTArray >* aValue, + void* aUserArg) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + NS_ASSERTION(!aKey.IsEmpty(), "Empty key!"); + NS_ASSERTION(aValue, "Null pointer!"); + + for (PRUint32 i = 0; i < aValue->Length(); i++) { + nsRefPtr fileManager = aValue->ElementAt(i); + fileManager->Invalidate(); + } + + return PL_DHASH_NEXT; +} + } // anonymous namespace IndexedDatabaseManager::IndexedDatabaseManager() : mCurrentWindowIndex(BAD_TLS_INDEX), - mQuotaHelperMutex("IndexedDatabaseManager.mQuotaHelperMutex") + mQuotaHelperMutex("IndexedDatabaseManager.mQuotaHelperMutex"), + mFileMutex("IndexedDatabaseManager.mFileMutex") { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); NS_ASSERTION(!gInstance, "More than one instance!"); @@ -179,7 +220,8 @@ IndexedDatabaseManager::GetOrCreate() instance = new IndexedDatabaseManager(); if (!instance->mLiveDatabases.Init() || - !instance->mQuotaHelperHash.Init()) { + !instance->mQuotaHelperHash.Init() || + !instance->mFileManagers.Init()) { NS_WARNING("Out of memory!"); return nsnull; } @@ -447,6 +489,13 @@ IndexedDatabaseManager::IsShuttingDown() return !!gShutdown; } +// static +bool +IndexedDatabaseManager::IsClosed() +{ + return !!gClosed; +} + void IndexedDatabaseManager::AbortCloseDatabasesForWindow(nsPIDOMWindow* aWindow) { @@ -573,7 +622,8 @@ IndexedDatabaseManager::GetIndexedDBQuotaMB() } nsresult -IndexedDatabaseManager::EnsureQuotaManagementForDirectory(nsIFile* aDirectory) +IndexedDatabaseManager::EnsureOriginIsInitialized(const nsACString& aOrigin, + nsIFile** aDirectory) { #ifdef DEBUG { @@ -583,19 +633,35 @@ IndexedDatabaseManager::EnsureQuotaManagementForDirectory(nsIFile* aDirectory) "Running on the wrong thread!"); } #endif - NS_ASSERTION(aDirectory, "Null pointer!"); - nsCString path; - nsresult rv = aDirectory->GetNativePath(path); + nsCOMPtr directory; + nsresult rv = IDBFactory::GetDirectoryForOrigin(aOrigin, + getter_AddRefs(directory)); NS_ENSURE_SUCCESS(rv, rv); - if (mTrackedQuotaPaths.Contains(path)) { - return true; + bool exists; + rv = directory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + bool isDirectory; + rv = directory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED); + } + else { + rv = directory->Create(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mFileManagers.Get(aOrigin)) { + NS_ADDREF(*aDirectory = directory); + return NS_OK; } // First figure out the filename pattern we'll use. nsCOMPtr patternFile; - rv = aDirectory->Clone(getter_AddRefs(patternFile)); + rv = directory->Clone(getter_AddRefs(patternFile)); NS_ENSURE_SUCCESS(rv, rv); rv = patternFile->Append(NS_LITERAL_STRING("*")); @@ -615,42 +681,141 @@ IndexedDatabaseManager::EnsureQuotaManagementForDirectory(nsIFile* aDirectory) mQuotaCallbackSingleton, nsnull); NS_ENSURE_SUCCESS(rv, rv); - // If the directory exists then we need to see if there are any files in it - // already. We need to tell SQLite about all of them. - bool exists; - rv = aDirectory->Exists(&exists); + // We need to see if there are any files in the directory already. If they + // are database files then we need to create file managers for them and also + // tell SQLite about all of them. + + nsAutoTArray subdirectories; + nsAutoTArray , 20> unknownFiles; + + nsAutoPtr > > fileManagers( + new nsTArray >()); + + nsCOMPtr entries; + rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); NS_ENSURE_SUCCESS(rv, rv); - if (exists) { - // Make sure this really is a directory. + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { + nsCOMPtr entry; + rv = entries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr file = do_QueryInterface(entry); + NS_ENSURE_TRUE(file, NS_NOINTERFACE); + + nsString leafName; + rv = file->GetLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + bool isDirectory; - rv = aDirectory->IsDirectory(&isDirectory); - NS_ENSURE_SUCCESS(rv, rv); - NS_ENSURE_TRUE(isDirectory, NS_ERROR_UNEXPECTED); - - nsCOMPtr entries; - rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + rv = file->IsDirectory(&isDirectory); NS_ENSURE_SUCCESS(rv, rv); - bool hasMore; - while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { - nsCOMPtr entry; - rv = entries->GetNext(getter_AddRefs(entry)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr file = do_QueryInterface(entry); - NS_ENSURE_TRUE(file, NS_NOINTERFACE); - - rv = ss->UpdateQutoaInformationForFile(file); - NS_ENSURE_SUCCESS(rv, rv); + if (isDirectory) { + subdirectories.AppendElement(leafName); + continue; } + + nsString dbBaseFilename; + if (!GetBaseFilename(leafName, dbBaseFilename)) { + unknownFiles.AppendElement(file); + continue; + } + + nsCOMPtr fileManagerDirectory; + rv = directory->Clone(getter_AddRefs(fileManagerDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = fileManagerDirectory->Append(dbBaseFilename); + NS_ENSURE_SUCCESS(rv, rv); + + nsString voidString; + voidString.SetIsVoid(true); + + nsCOMPtr connection; + rv = OpenDatabaseHelper::CreateDatabaseConnection( + voidString, file, fileManagerDirectory, getter_AddRefs(connection)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr stmt; + rv = connection->CreateStatement(NS_LITERAL_CSTRING( + "SELECT name " + "FROM database" + ), getter_AddRefs(stmt)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasResult; + rv = stmt->ExecuteStep(&hasResult); + NS_ENSURE_SUCCESS(rv, rv); + + if (!hasResult) { + NS_ERROR("Database has no name!"); + return NS_ERROR_UNEXPECTED; + } + + nsString databaseName; + rv = stmt->GetString(0, databaseName); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr fileManager = new FileManager(aOrigin, databaseName); + + rv = fileManager->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = fileManager->InitDirectory(fileManagerDirectory, connection); + NS_ENSURE_SUCCESS(rv, rv); + + fileManagers->AppendElement(fileManager); + + rv = ss->UpdateQuotaInformationForFile(file); NS_ENSURE_SUCCESS(rv, rv); } + NS_ENSURE_SUCCESS(rv, rv); - NS_ASSERTION(!mTrackedQuotaPaths.Contains(path), "What?!"); + for (PRUint32 i = 0; i < subdirectories.Length(); i++) { + const nsString& subdirectory = subdirectories[i]; + bool unknown = true; + for (PRUint32 j = 0; j < fileManagers->Length(); j++) { + nsRefPtr& fileManager = fileManagers->ElementAt(j); + if (fileManager->DirectoryName().Equals(subdirectory)) { + unknown = false; + break; + } + } + if (unknown) { + NS_WARNING("Unknown subdirectory found!"); + return NS_ERROR_UNEXPECTED; + } + } - mTrackedQuotaPaths.AppendElement(path); - return rv; + for (PRUint32 i = 0; i < unknownFiles.Length(); i++) { + nsCOMPtr& unknownFile = unknownFiles[i]; + + bool exists; + rv = unknownFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + nsString leafName; + unknownFile->GetLeafName(leafName); + + if (!StringEndsWith(leafName, NS_LITERAL_STRING(".sqlite-journal"))) { + NS_WARNING("Unknown file found!"); + return NS_ERROR_UNEXPECTED; + } + } + } + + if (!mFileManagers.Put(aOrigin, fileManagers)) { + NS_WARNING("Out of memory?"); + return NS_ERROR_OUT_OF_MEMORY; + } + + fileManagers.forget(); + + NS_ADDREF(*aDirectory = directory); + return NS_OK; } bool @@ -749,6 +914,116 @@ IndexedDatabaseManager::GetASCIIOriginFromWindow(nsPIDOMWindow* aWindow, return NS_OK; } +already_AddRefed +IndexedDatabaseManager::GetOrCreateFileManager(const nsACString& aOrigin, + const nsAString& aDatabaseName) +{ + nsTArray >* array; + if (!mFileManagers.Get(aOrigin, &array)) { + nsAutoPtr > > newArray( + new nsTArray >()); + if (!mFileManagers.Put(aOrigin, newArray)) { + NS_WARNING("Out of memory?"); + return nsnull; + } + array = newArray.forget(); + } + + nsRefPtr fileManager; + for (PRUint32 i = 0; i < array->Length(); i++) { + nsRefPtr fm = array->ElementAt(i); + + if (fm->DatabaseName().Equals(aDatabaseName)) { + fileManager = fm.forget(); + break; + } + } + + if (!fileManager) { + fileManager = new FileManager(aOrigin, aDatabaseName); + + if (NS_FAILED(fileManager->Init())) { + NS_WARNING("Failed to initialize file manager!"); + return nsnull; + } + + array->AppendElement(fileManager); + } + + return fileManager.forget(); +} + +void +IndexedDatabaseManager::InvalidateFileManagersForOrigin( + const nsACString& aOrigin) +{ + nsTArray >* array; + if (mFileManagers.Get(aOrigin, &array)) { + for (PRUint32 i = 0; i < array->Length(); i++) { + nsRefPtr fileManager = array->ElementAt(i); + fileManager->Invalidate(); + } + mFileManagers.Remove(aOrigin); + } +} + +void +IndexedDatabaseManager::InvalidateFileManager(const nsACString& aOrigin, + const nsAString& aDatabaseName) +{ + nsTArray >* array; + if (!mFileManagers.Get(aOrigin, &array)) { + return; + } + + for (PRUint32 i = 0; i < array->Length(); i++) { + nsRefPtr fileManager = array->ElementAt(i); + if (fileManager->DatabaseName().Equals(aDatabaseName)) { + fileManager->Invalidate(); + array->RemoveElementAt(i); + + if (array->IsEmpty()) { + mFileManagers.Remove(aOrigin); + } + + break; + } + } +} + +nsresult +IndexedDatabaseManager::AsyncDeleteFile(FileManager* aFileManager, + PRInt64 aFileId) +{ + NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); + + NS_ENSURE_ARG_POINTER(aFileManager); + + // See if we're currently clearing the databases for this origin. If so then + // we pretend that we've already deleted everything. + if (IsClearOriginPending(aFileManager->Origin())) { + return NS_OK; + } + + nsCOMPtr directory = aFileManager->GetDirectory(); + NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE); + + nsCOMPtr file = aFileManager->GetFileForId(directory, aFileId); + NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); + + nsString filePath; + nsresult rv = file->GetPath(filePath); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr runnable = + new AsyncDeleteFileRunnable(filePath); + + rv = mIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + // static nsresult IndexedDatabaseManager::DispatchHelper(AsyncConnectionHelper* aHelper) @@ -949,6 +1224,12 @@ IndexedDatabaseManager::Observe(nsISupports* aSubject, NS_WARNING("Failed to cancel shutdown timer!"); } + mFileManagers.EnumerateRead(InvalidateAllFileManagers, nsnull); + + if (PR_ATOMIC_SET(&gClosed, 1)) { + NS_ERROR("Close more than once?!"); + } + return NS_OK; } @@ -1007,10 +1288,12 @@ IndexedDatabaseManager::OriginClearRunnable::Run() NS_ASSERTION(!mThread, "Should have been cleared already!"); - // Tell the IndexedDatabaseManager that we're done. IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); NS_ASSERTION(mgr, "This should never fail!"); + mgr->InvalidateFileManagersForOrigin(mOrigin); + + // Tell the IndexedDatabaseManager that we're done. mgr->AllowNextSynchronizedOp(mOrigin, nsnull); return NS_OK; @@ -1046,6 +1329,7 @@ IndexedDatabaseManager::AsyncUsageRunnable::AsyncUsageRunnable( mOrigin(aOrigin), mCallback(aCallback), mUsage(0), + mFileUsage(0), mCanceled(0) { NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); @@ -1062,13 +1346,27 @@ IndexedDatabaseManager::AsyncUsageRunnable::Cancel() } } +inline void +IncrementUsage(PRUint64* aUsage, PRUint64 aDelta) +{ + // Watch for overflow! + if ((LL_MAXINT - *aUsage) <= aDelta) { + NS_WARNING("Database sizes exceed max we can report!"); + *aUsage = LL_MAXINT; + } else { + *aUsage += aDelta; + } +} + nsresult IndexedDatabaseManager::AsyncUsageRunnable::RunInternal() { if (NS_IsMainThread()) { // Call the callback unless we were canceled. if (!mCanceled) { - mCallback->OnUsageResult(mURI, mUsage); + PRUint64 usage = mUsage; + IncrementUsage(&usage, mFileUsage); + mCallback->OnUsageResult(mURI, usage, mFileUsage); } // Clean up. @@ -1101,39 +1399,63 @@ IndexedDatabaseManager::AsyncUsageRunnable::RunInternal() // If the directory exists then enumerate all the files inside, adding up the // sizes to get the final usage statistic. if (exists && !mCanceled) { - nsCOMPtr entries; - rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); + rv = GetUsageForDirectory(directory, &mUsage); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +nsresult +IndexedDatabaseManager::AsyncUsageRunnable::GetUsageForDirectory( + nsIFile* aDirectory, + PRUint64* aUsage) +{ + NS_ASSERTION(aDirectory, "Null pointer!"); + NS_ASSERTION(aUsage, "Null pointer!"); + + nsCOMPtr entries; + nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!entries) { + return NS_OK; + } + + bool hasMore; + while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && + hasMore && !mCanceled) { + nsCOMPtr entry; + rv = entries->GetNext(getter_AddRefs(entry)); NS_ENSURE_SUCCESS(rv, rv); - if (entries) { - bool hasMore; - while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && - hasMore && !mCanceled) { - nsCOMPtr entry; - rv = entries->GetNext(getter_AddRefs(entry)); + nsCOMPtr file(do_QueryInterface(entry)); + NS_ASSERTION(file, "Don't know what this is!"); + + bool isDirectory; + rv = file->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + if (isDirectory) { + if (aUsage == &mFileUsage) { + NS_WARNING("Unknown directory found!"); + } else { + rv = GetUsageForDirectory(file, &mFileUsage); NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr file(do_QueryInterface(entry)); - NS_ASSERTION(file, "Don't know what this is!"); - - PRInt64 fileSize; - rv = file->GetFileSize(&fileSize); - NS_ENSURE_SUCCESS(rv, rv); - - NS_ASSERTION(fileSize > 0, "Negative size?!"); - - // Watch for overflow! - if (NS_UNLIKELY((LL_MAXINT - mUsage) <= PRUint64(fileSize))) { - NS_WARNING("Database sizes exceed max we can report!"); - mUsage = LL_MAXINT; - } - else { - mUsage += fileSize; - } } - NS_ENSURE_SUCCESS(rv, rv); + + continue; } + + PRInt64 fileSize; + rv = file->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(fileSize > 0, "Negative size?!"); + + IncrementUsage(aUsage, PRUint64(fileSize)); } + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; } @@ -1247,3 +1569,20 @@ IndexedDatabaseManager::SynchronizedOp::DispatchDelayedRunnables() mDelayedRunnables.Clear(); } + +NS_IMPL_THREADSAFE_ISUPPORTS1(IndexedDatabaseManager::AsyncDeleteFileRunnable, + nsIRunnable) + +NS_IMETHODIMP +IndexedDatabaseManager::AsyncDeleteFileRunnable::Run() +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + int rc = sqlite3_quota_remove(NS_ConvertUTF16toUTF8(mFilePath).get()); + if (rc != SQLITE_OK) { + NS_WARNING("Failed to delete stored file!"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} diff --git a/dom/indexedDB/IndexedDatabaseManager.h b/dom/indexedDB/IndexedDatabaseManager.h index 48282824529..4bf1cf90cdb 100644 --- a/dom/indexedDB/IndexedDatabaseManager.h +++ b/dom/indexedDB/IndexedDatabaseManager.h @@ -40,6 +40,7 @@ #ifndef mozilla_dom_indexeddb_indexeddatabasemanager_h__ #define mozilla_dom_indexeddb_indexeddatabasemanager_h__ +#include "mozilla/dom/indexedDB/FileManager.h" #include "mozilla/dom/indexedDB/IndexedDatabase.h" #include "mozilla/dom/indexedDB/IDBDatabase.h" #include "mozilla/dom/indexedDB/IDBRequest.h" @@ -103,6 +104,8 @@ public: // Returns true if we've begun the shutdown process. static bool IsShuttingDown(); + static bool IsClosed(); + typedef void (*WaitingOnDatabasesCallback)(nsTArray >&, void*); // Acquire exclusive access to the database given (waits for all others to @@ -148,7 +151,8 @@ public: static PRUint32 GetIndexedDBQuotaMB(); - nsresult EnsureQuotaManagementForDirectory(nsIFile* aDirectory); + nsresult EnsureOriginIsInitialized(const nsACString& aOrigin, + nsIFile** aDirectory); // Determine if the quota is lifted for the Window the current thread is // using. @@ -173,6 +177,26 @@ public: static nsresult GetASCIIOriginFromWindow(nsPIDOMWindow* aWindow, nsCString& aASCIIOrigin); + already_AddRefed + GetOrCreateFileManager(const nsACString& aOrigin, + const nsAString& aDatabaseName); + + void InvalidateFileManagersForOrigin(const nsACString& aOrigin); + + void InvalidateFileManager(const nsACString& aOrigin, + const nsAString& aDatabaseName); + + nsresult AsyncDeleteFile(FileManager* aFileManager, + PRInt64 aFileId); + + static mozilla::Mutex& FileMutex() + { + IndexedDatabaseManager* mgr = Get(); + NS_ASSERTION(mgr, "Must have a manager here!"); + + return mgr->mFileMutex; + } + private: IndexedDatabaseManager(); ~IndexedDatabaseManager(); @@ -250,10 +274,14 @@ private: // to the main thread in case of an error. inline nsresult RunInternal(); + nsresult GetUsageForDirectory(nsIFile* aDirectory, + PRUint64* aUsage); + nsCOMPtr mURI; nsCString mOrigin; nsCOMPtr mCallback; PRUint64 mUsage; + PRUint64 mFileUsage; PRInt32 mCanceled; }; @@ -302,6 +330,19 @@ private: SynchronizedOp* mOp; }; + class AsyncDeleteFileRunnable : public nsIRunnable + { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIRUNNABLE + AsyncDeleteFileRunnable(const nsAString& aFilePath) + : mFilePath(aFilePath) + { } + + private: + nsString mFilePath; + }; + static nsresult DispatchHelper(AsyncConnectionHelper* aHelper); // Maintains a list of live databases per origin. @@ -316,6 +357,12 @@ private: // A map of Windows to the corresponding quota helper. nsRefPtrHashtable, CheckQuotaHelper> mQuotaHelperHash; + // Maintains a list of all file managers per origin. The list is actually also + // a list of all origins that were successfully initialized. This list + // isn't protected by any mutex but it is only ever touched on the IO thread. + nsClassHashtable > > mFileManagers; + // Maintains a list of origins that we're currently enumerating to gather // usage statistics. nsAutoTArray, 1> mUsageRunnables; @@ -333,10 +380,10 @@ private: // thread during GetOrCreate(). nsCOMPtr mQuotaCallbackSingleton; - // A list of all paths that are under SQLite's quota tracking system. This - // list isn't protected by any mutex but it is only ever touched on the IO - // thread. - nsTArray mTrackedQuotaPaths; + // Lock protecting FileManager.mFileInfos and nsDOMFileBase.mFileInfos + // It's s also used to atomically update FileInfo.mRefCnt, FileInfo.mDBRefCnt + // and FileInfo.mSliceRefCnt + mozilla::Mutex mFileMutex; }; class AutoEnterWindow diff --git a/dom/indexedDB/Makefile.in b/dom/indexedDB/Makefile.in index e39d71918c1..5744598364c 100644 --- a/dom/indexedDB/Makefile.in +++ b/dom/indexedDB/Makefile.in @@ -56,6 +56,8 @@ CPPSRCS = \ CheckPermissionsHelper.cpp \ CheckQuotaHelper.cpp \ DatabaseInfo.cpp \ + FileInfo.cpp \ + FileManager.cpp \ IDBCursor.cpp \ IDBDatabase.cpp \ IDBEvents.cpp \ @@ -85,9 +87,12 @@ EXPORTS_mozilla/dom/indexedDB = \ IDBFactory.h \ Key.h \ LazyIdleThread.h \ + FileManager.h \ + FileInfo.h \ $(NULL) LOCAL_INCLUDES = \ + -I$(topsrcdir)/db/sqlite3/src \ -I$(topsrcdir)/xpcom/build \ -I$(topsrcdir)/dom/base \ -I$(topsrcdir)/dom/src/storage \ diff --git a/dom/indexedDB/OpenDatabaseHelper.cpp b/dom/indexedDB/OpenDatabaseHelper.cpp index 7e94bee5b6d..b377cd212b5 100644 --- a/dom/indexedDB/OpenDatabaseHelper.cpp +++ b/dom/indexedDB/OpenDatabaseHelper.cpp @@ -45,6 +45,7 @@ #include "nsEscape.h" #include "nsThreadUtils.h" #include "snappy/snappy.h" +#include "test_quota.h" #include "IDBEvents.h" #include "IDBFactory.h" @@ -59,7 +60,7 @@ namespace { PR_STATIC_ASSERT(JS_STRUCTURED_CLONE_VERSION == 1); // Major schema version. Bump for almost everything. -const PRUint32 kMajorSchemaVersion = 9; +const PRUint32 kMajorSchemaVersion = 10; // Minor schema version. Should almost always be 0 (maybe bump on release // branches if we have to). @@ -95,24 +96,10 @@ PRUint32 GetMinorSchemaVersion(PRInt32 aSchemaVersion) } nsresult -GetDatabaseFile(const nsACString& aASCIIOrigin, - const nsAString& aName, - nsIFile** aDatabaseFile) +GetDatabaseFilename(const nsAString& aName, + nsAString& aDatabaseFilename) { - NS_ASSERTION(!aASCIIOrigin.IsEmpty() && !aName.IsEmpty(), "Bad arguments!"); - - nsCOMPtr dbFile; - nsresult rv = IDBFactory::GetDirectory(getter_AddRefs(dbFile)); - NS_ENSURE_SUCCESS(rv, rv); - - NS_ConvertASCIItoUTF16 originSanitized(aASCIIOrigin); - originSanitized.ReplaceChar(":/", '+'); - - rv = dbFile->Append(originSanitized); - NS_ENSURE_SUCCESS(rv, rv); - - nsAutoString filename; - filename.AppendInt(HashString(aName)); + aDatabaseFilename.AppendInt(HashString(aName)); nsCString escapedName; if (!NS_Escape(NS_ConvertUTF16toUTF8(aName), escapedName, url_XPAlphas)) { @@ -133,13 +120,97 @@ GetDatabaseFile(const nsACString& aASCIIOrigin, } } - filename.Append(NS_ConvertASCIItoUTF16(substring)); - filename.AppendLiteral(".sqlite"); + aDatabaseFilename.Append(NS_ConvertASCIItoUTF16(substring)); - rv = dbFile->Append(filename); + return NS_OK; +} + +nsresult +CreateFileTables(mozIStorageConnection* aDBConn) +{ + // Table `file` + nsresult rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TABLE file (" + "id INTEGER PRIMARY KEY, " + "refcount INTEGER NOT NULL" + ");" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TRIGGER object_data_insert_trigger " + "AFTER INSERT ON object_data " + "FOR EACH ROW " + "WHEN NEW.file_ids IS NOT NULL " + "BEGIN " + "SELECT update_refcount(NULL, NEW.file_ids); " + "END;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TRIGGER object_data_update_trigger " + "AFTER UPDATE OF file_ids ON object_data " + "FOR EACH ROW " + "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL " + "BEGIN " + "SELECT update_refcount(OLD.file_ids, NEW.file_ids); " + "END;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TRIGGER object_data_delete_trigger " + "AFTER DELETE ON object_data " + "FOR EACH ROW WHEN OLD.file_ids IS NOT NULL " + "BEGIN " + "SELECT update_refcount(OLD.file_ids, NULL); " + "END;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TRIGGER ai_object_data_insert_trigger " + "AFTER INSERT ON ai_object_data " + "FOR EACH ROW " + "WHEN NEW.file_ids IS NOT NULL " + "BEGIN " + "SELECT update_refcount(NULL, NEW.file_ids); " + "END;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TRIGGER ai_object_data_update_trigger " + "AFTER UPDATE OF file_ids ON ai_object_data " + "FOR EACH ROW " + "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL " + "BEGIN " + "SELECT update_refcount(OLD.file_ids, NEW.file_ids); " + "END;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TRIGGER ai_object_data_delete_trigger " + "AFTER DELETE ON ai_object_data " + "FOR EACH ROW WHEN OLD.file_ids IS NOT NULL " + "BEGIN " + "SELECT update_refcount(OLD.file_ids, NULL); " + "END;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TRIGGER file_update_trigger " + "AFTER UPDATE ON file " + "FOR EACH ROW WHEN NEW.refcount = 0 " + "BEGIN " + "DELETE FROM file WHERE id = OLD.id; " + "END;" + )); NS_ENSURE_SUCCESS(rv, rv); - dbFile.forget(aDatabaseFile); return NS_OK; } @@ -178,6 +249,7 @@ CreateTables(mozIStorageConnection* aDBConn) "object_store_id INTEGER NOT NULL, " "key_value DEFAULT NULL, " "data BLOB NOT NULL, " + "file_ids TEXT, " "UNIQUE (object_store_id, key_value), " "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " "CASCADE" @@ -191,6 +263,7 @@ CreateTables(mozIStorageConnection* aDBConn) "id INTEGER PRIMARY KEY AUTOINCREMENT, " "object_store_id INTEGER NOT NULL, " "data BLOB NOT NULL, " + "file_ids TEXT, " "UNIQUE (object_store_id, id), " "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " "CASCADE" @@ -310,6 +383,9 @@ CreateTables(mozIStorageConnection* aDBConn) )); NS_ENSURE_SUCCESS(rv, rv); + rv = CreateFileTables(aDBConn); + NS_ENSURE_SUCCESS(rv, rv); + rv = aDBConn->SetSchemaVersion(kSQLiteSchemaVersion); NS_ENSURE_SUCCESS(rv, rv); @@ -961,127 +1037,24 @@ UpgradeSchemaFrom8To9_0(mozIStorageConnection* aConnection) } nsresult -CreateDatabaseConnection(const nsAString& aName, - nsIFile* aDBFile, - mozIStorageConnection** aConnection) +UpgradeSchemaFrom9_0To10_0(mozIStorageConnection* aConnection) { - NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); - - NS_NAMED_LITERAL_CSTRING(quotaVFSName, "quota"); - - nsCOMPtr ss = - do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); - NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE); - - nsCOMPtr connection; - nsresult rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName, - getter_AddRefs(connection)); - if (rv == NS_ERROR_FILE_CORRUPTED) { - // Nuke the database file. The web services can recreate their data. - rv = aDBFile->Remove(false); - NS_ENSURE_SUCCESS(rv, rv); - - rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName, - getter_AddRefs(connection)); - } - NS_ENSURE_SUCCESS(rv, rv); - - // Check to make sure that the database schema is correct. - PRInt32 schemaVersion; - rv = connection->GetSchemaVersion(&schemaVersion); - NS_ENSURE_SUCCESS(rv, rv); - - if (schemaVersion > kSQLiteSchemaVersion) { - NS_WARNING("Unable to open IndexedDB database, schema is too high!"); - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; - } - - bool vacuumNeeded = false; - - if (schemaVersion != kSQLiteSchemaVersion) { - mozStorageTransaction transaction(connection, false, - mozIStorageConnection::TRANSACTION_IMMEDIATE); - - if (!schemaVersion) { - // Brand new file, initialize our tables. - rv = CreateTables(connection); - NS_ENSURE_SUCCESS(rv, rv); - - NS_ASSERTION(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)) && - schemaVersion == kSQLiteSchemaVersion, - "CreateTables set a bad schema version!"); - - nsCOMPtr stmt; - nsresult rv = connection->CreateStatement(NS_LITERAL_CSTRING( - "INSERT INTO database (name) " - "VALUES (:name)" - ), getter_AddRefs(stmt)); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - - rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - - rv = stmt->Execute(); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - } - else { - // This logic needs to change next time we change the schema! - PR_STATIC_ASSERT(kSQLiteSchemaVersion == PRInt32((9 << 4) + 0)); - - while (schemaVersion != kSQLiteSchemaVersion) { - if (schemaVersion == 4) { - rv = UpgradeSchemaFrom4To5(connection); - } - else if (schemaVersion == 5) { - rv = UpgradeSchemaFrom5To6(connection); - } - else if (schemaVersion == 6) { - rv = UpgradeSchemaFrom6To7(connection); - } - else if (schemaVersion == 7) { - rv = UpgradeSchemaFrom7To8(connection); - } - else if (schemaVersion == 8) { - rv = UpgradeSchemaFrom8To9_0(connection); - vacuumNeeded = true; - } -#if 0 - else if (schemaVersion == MakeSchemaVersion(9, 0)) { - // Upgrade. - } -#endif - else { - NS_WARNING("Unable to open IndexedDB database, no upgrade path is " - "available!"); - return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; - } - NS_ENSURE_SUCCESS(rv, rv); - - rv = connection->GetSchemaVersion(&schemaVersion); - NS_ENSURE_SUCCESS(rv, rv); - } - - NS_ASSERTION(schemaVersion == kSQLiteSchemaVersion, "Huh?!"); - } - - rv = transaction.Commit(); - NS_ENSURE_SUCCESS(rv, rv); - } - - if (vacuumNeeded) { - rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "VACUUM;" - )); - NS_ENSURE_SUCCESS(rv, rv); - } - - // Turn on foreign key constraints. - rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "PRAGMA foreign_keys = ON;" + nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "ALTER TABLE object_data ADD COLUMN file_ids TEXT;" )); NS_ENSURE_SUCCESS(rv, rv); - connection.forget(aConnection); + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "ALTER TABLE ai_object_data ADD COLUMN file_ids TEXT;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CreateFileTables(aConnection); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aConnection->SetSchemaVersion(MakeSchemaVersion(10, 0)); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; } @@ -1130,8 +1103,8 @@ protected: private: // In-params - nsRefPtr mOpenHelper; nsRefPtr mOpenRequest; + nsRefPtr mOpenHelper; PRUint64 mRequestedVersion; PRUint64 mCurrentVersion; }; @@ -1345,40 +1318,39 @@ OpenDatabaseHelper::DoDatabaseWork() AutoEnterWindow autoWindow(window); + nsCOMPtr dbDirectory; + + IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); + NS_ASSERTION(mgr, "This should never be null!"); + + nsresult rv = mgr->EnsureOriginIsInitialized(mASCIIOrigin, + getter_AddRefs(dbDirectory)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + nsAutoString filename; + rv = GetDatabaseFilename(mName, filename); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + nsCOMPtr dbFile; - nsresult rv = GetDatabaseFile(mASCIIOrigin, mName, getter_AddRefs(dbFile)); + rv = dbDirectory->Clone(getter_AddRefs(dbFile)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + rv = dbFile->Append(filename + NS_LITERAL_STRING(".sqlite")); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = dbFile->GetPath(mDatabaseFilePath); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - nsCOMPtr dbDirectory; - rv = dbFile->GetParent(getter_AddRefs(dbDirectory)); + nsCOMPtr fileManagerDirectory; + rv = dbDirectory->Clone(getter_AddRefs(fileManagerDirectory)); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - bool exists; - rv = dbDirectory->Exists(&exists); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - - if (exists) { - bool isDirectory; - rv = dbDirectory->IsDirectory(&isDirectory); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - NS_ENSURE_TRUE(isDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - } - else { - rv = dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); - NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - } - - IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); - NS_ASSERTION(mgr, "This should never be null!"); - - rv = mgr->EnsureQuotaManagementForDirectory(dbDirectory); + rv = fileManagerDirectory->Append(filename); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); nsCOMPtr connection; - rv = CreateDatabaseConnection(mName, dbFile, getter_AddRefs(connection)); + rv = CreateDatabaseConnection(mName, dbFile, fileManagerDirectory, + getter_AddRefs(connection)); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); rv = IDBFactory::LoadDatabaseInformation(connection, mDatabaseId, @@ -1422,6 +1394,175 @@ OpenDatabaseHelper::DoDatabaseWork() mState = eSetVersionPending; } + mFileManager = mgr->GetOrCreateFileManager(mASCIIOrigin, mName); + NS_ENSURE_TRUE(mFileManager, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + if (!mFileManager->IsDirectoryInited()) { + rv = mFileManager->InitDirectory(fileManagerDirectory, connection); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + } + + if (!mFileManager->Loaded()) { + rv = mFileManager->Load(connection); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + } + + return NS_OK; +} + +// static +nsresult +OpenDatabaseHelper::CreateDatabaseConnection( + const nsAString& aName, + nsIFile* aDBFile, + nsIFile* aFileManagerDirectory, + mozIStorageConnection** aConnection) +{ + NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); + + NS_NAMED_LITERAL_CSTRING(quotaVFSName, "quota"); + + nsCOMPtr ss = + do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE); + + nsCOMPtr connection; + nsresult rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName, + getter_AddRefs(connection)); + if (rv == NS_ERROR_FILE_CORRUPTED) { + // If we're just opening the database during origin initialization, then + // we don't want to erase any files. The failure here will fail origin + // initialization too. + if (aName.IsVoid()) { + return rv; + } + + // Nuke the database file. The web services can recreate their data. + rv = aDBFile->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + + bool exists; + rv = aFileManagerDirectory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + if (exists) { + bool isDirectory; + rv = aFileManagerDirectory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(isDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + rv = aFileManagerDirectory->Remove(true); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = ss->OpenDatabaseWithVFS(aDBFile, quotaVFSName, + getter_AddRefs(connection)); + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = connection->EnableModule(NS_LITERAL_CSTRING("filesystem")); + NS_ENSURE_SUCCESS(rv, rv); + + // Check to make sure that the database schema is correct. + PRInt32 schemaVersion; + rv = connection->GetSchemaVersion(&schemaVersion); + NS_ENSURE_SUCCESS(rv, rv); + + // Unknown schema will fail origin initialization too + if (!schemaVersion && aName.IsVoid()) { + NS_WARNING("Unable to open IndexedDB database, schema is not set!"); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + if (schemaVersion > kSQLiteSchemaVersion) { + NS_WARNING("Unable to open IndexedDB database, schema is too high!"); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + bool vacuumNeeded = false; + + if (schemaVersion != kSQLiteSchemaVersion) { + mozStorageTransaction transaction(connection, false, + mozIStorageConnection::TRANSACTION_IMMEDIATE); + + if (!schemaVersion) { + // Brand new file, initialize our tables. + rv = CreateTables(connection); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)) && + schemaVersion == kSQLiteSchemaVersion, + "CreateTables set a bad schema version!"); + + nsCOMPtr stmt; + nsresult rv = connection->CreateStatement(NS_LITERAL_CSTRING( + "INSERT INTO database (name) " + "VALUES (:name)" + ), getter_AddRefs(stmt)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + rv = stmt->Execute(); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + } + else { + // This logic needs to change next time we change the schema! + PR_STATIC_ASSERT(kSQLiteSchemaVersion == PRInt32((10 << 4) + 0)); + + while (schemaVersion != kSQLiteSchemaVersion) { + if (schemaVersion == 4) { + rv = UpgradeSchemaFrom4To5(connection); + } + else if (schemaVersion == 5) { + rv = UpgradeSchemaFrom5To6(connection); + } + else if (schemaVersion == 6) { + rv = UpgradeSchemaFrom6To7(connection); + } + else if (schemaVersion == 7) { + rv = UpgradeSchemaFrom7To8(connection); + } + else if (schemaVersion == 8) { + rv = UpgradeSchemaFrom8To9_0(connection); + vacuumNeeded = true; + } + else if (schemaVersion == MakeSchemaVersion(9, 0)) { + rv = UpgradeSchemaFrom9_0To10_0(connection); + } + else { + NS_WARNING("Unable to open IndexedDB database, no upgrade path is " + "available!"); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = connection->GetSchemaVersion(&schemaVersion); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_ASSERTION(schemaVersion == kSQLiteSchemaVersion, "Huh?!"); + } + + rv = transaction.Commit(); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (vacuumNeeded) { + rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "VACUUM;" + )); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Turn on foreign key constraints. + rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "PRAGMA foreign_keys = ON;" + )); + NS_ENSURE_SUCCESS(rv, rv); + + connection.forget(aConnection); return NS_OK; } @@ -1540,6 +1681,11 @@ OpenDatabaseHelper::Run() // Destroy the database now (we should have the only ref). mDatabase = nsnull; + IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); + NS_ASSERTION(mgr, "This should never fail!"); + + mgr->InvalidateFileManager(mASCIIOrigin, mName); + mState = eFiringEvents; break; } @@ -1669,7 +1815,8 @@ OpenDatabaseHelper::EnsureSuccessResult() IDBDatabase::Create(mOpenDBRequest->ScriptContext(), mOpenDBRequest->Owner(), dbInfo.forget(), - mASCIIOrigin); + mASCIIOrigin, + mFileManager); if (!database) { return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; } @@ -1888,19 +2035,68 @@ DeleteDatabaseHelper::DoDatabaseWork(mozIStorageConnection* aConnection) { NS_ASSERTION(!aConnection, "How did we get a connection here?"); - nsCOMPtr dbFile; - nsresult rv = GetDatabaseFile(mASCIIOrigin, mName, getter_AddRefs(dbFile)); + nsCOMPtr directory; + nsresult rv = IDBFactory::GetDirectoryForOrigin(mASCIIOrigin, + getter_AddRefs(directory)); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); - NS_ASSERTION(dbFile, "What?"); + NS_ASSERTION(directory, "What?"); + + nsAutoString filename; + rv = GetDatabaseFilename(mName, filename); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + nsCOMPtr dbFile; + rv = directory->Clone(getter_AddRefs(dbFile)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + rv = dbFile->Append(filename + NS_LITERAL_STRING(".sqlite")); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); bool exists = false; rv = dbFile->Exists(&exists); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + int rc; + if (exists) { - rv = dbFile->Remove(false); + nsString dbFilePath; + rv = dbFile->GetPath(dbFilePath); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + rc = sqlite3_quota_remove(NS_ConvertUTF16toUTF8(dbFilePath).get()); + if (rc != SQLITE_OK) { + NS_WARNING("Failed to delete db file!"); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + } + + nsCOMPtr fileManagerDirectory; + rv = directory->Clone(getter_AddRefs(fileManagerDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = fileManagerDirectory->Append(filename); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + rv = fileManagerDirectory->Exists(&exists); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + if (exists) { + bool isDirectory; + rv = fileManagerDirectory->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(isDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + nsString fileManagerDirectoryPath; + rv = fileManagerDirectory->GetPath(fileManagerDirectoryPath); + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); + + rc = sqlite3_quota_remove( + NS_ConvertUTF16toUTF8(fileManagerDirectoryPath).get()); + if (rc != SQLITE_OK) { + NS_WARNING("Failed to delete file directory!"); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } } return NS_OK; diff --git a/dom/indexedDB/OpenDatabaseHelper.h b/dom/indexedDB/OpenDatabaseHelper.h index 01a55be1556..1e7b7be41e6 100644 --- a/dom/indexedDB/OpenDatabaseHelper.h +++ b/dom/indexedDB/OpenDatabaseHelper.h @@ -102,6 +102,12 @@ public: return mDatabase; } + static + nsresult CreateDatabaseConnection(const nsAString& aName, + nsIFile* aDBFile, + nsIFile* aFileManagerDirectory, + mozIStorageConnection** aConnection); + protected: // Methods only called on the main thread nsresult EnsureSuccessResult(); @@ -145,6 +151,8 @@ private: }; OpenDatabaseState mState; nsresult mResultCode; + + nsRefPtr mFileManager; }; END_INDEXEDDB_NAMESPACE diff --git a/dom/indexedDB/TransactionThreadPool.h b/dom/indexedDB/TransactionThreadPool.h index 89de424862e..d9831f27837 100644 --- a/dom/indexedDB/TransactionThreadPool.h +++ b/dom/indexedDB/TransactionThreadPool.h @@ -87,7 +87,7 @@ public: // committed, for aDatabase. void AbortTransactionsForDatabase(IDBDatabase* aDatabase); - // Returns true iff there are running or pending transactions for aDatabase. + // Returns true if there are running or pending transactions for aDatabase. bool HasTransactionsForDatabase(IDBDatabase* aDatabase); protected: diff --git a/dom/indexedDB/nsIIndexedDatabaseManager.idl b/dom/indexedDB/nsIIndexedDatabaseManager.idl index d794ef6ce53..b6e38b4adde 100644 --- a/dom/indexedDB/nsIIndexedDatabaseManager.idl +++ b/dom/indexedDB/nsIIndexedDatabaseManager.idl @@ -41,14 +41,15 @@ interface nsIURI; -[scriptable, function, uuid(17675af5-0569-4f5b-987f-ff4bb60f73ee)] +[scriptable, function, uuid(ef1795ec-7050-4658-b80f-0e48cbe1d64b)] interface nsIIndexedDatabaseUsageCallback : nsISupports { /** * */ void onUsageResult(in nsIURI aURI, - in unsigned long long aUsage); + in unsigned long long aUsage, + in unsigned long long aFileUsage); }; [scriptable, builtinclass, uuid(415f5684-6c84-4a8b-b777-d01f5df778f2)] diff --git a/dom/indexedDB/test/Makefile.in b/dom/indexedDB/test/Makefile.in index db73453c8f6..3c111d45704 100644 --- a/dom/indexedDB/test/Makefile.in +++ b/dom/indexedDB/test/Makefile.in @@ -50,6 +50,7 @@ TEST_FILES = \ error_events_abort_transactions_iframe.html \ event_propagation_iframe.html \ exceptions_in_success_events_iframe.html \ + file.js \ helpers.js \ leaving_page_iframe.html \ test_add_put.html \ @@ -73,6 +74,15 @@ TEST_FILES = \ test_event_propagation.html \ test_event_source.html \ test_exceptions_in_success_events.html \ + test_file_array.html \ + test_file_cross_database_copying.html \ + test_file_delete.html \ + test_file_os_delete.html \ + test_file_put_get_values.html \ + test_file_resurrection_delete.html \ + test_file_resurrection_transaction_abort.html \ + test_file_sharing.html \ + test_file_transaction_abort.html \ test_getAll.html \ test_global_data.html \ test_index_empty_keyPath.html \ diff --git a/dom/indexedDB/test/file.js b/dom/indexedDB/test/file.js new file mode 100644 index 00000000000..6089a44f835 --- /dev/null +++ b/dom/indexedDB/test/file.js @@ -0,0 +1,225 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +var builder = new MozBlobBuilder(); +var manager = null; +var bufferCache = []; +var utils = SpecialPowers.getDOMWindowUtils(window); + +function getBuffer(size) +{ + let buffer = new ArrayBuffer(size); + is(buffer.byteLength, size, "Correct byte length"); + return buffer; +} + +function getRandomBuffer(size) +{ + let buffer = getBuffer(size); + let view = new Uint8Array(buffer); + for (let i = 0; i < size; i++) { + view[i] = parseInt(Math.random() * 255) + } + return buffer; +} + +function compareBuffers(buffer1, buffer2) +{ + if (buffer1.byteLength != buffer2.byteLength) { + return false; + } + let view1 = new Uint8Array(buffer1); + let view2 = new Uint8Array(buffer2); + for (let i = 0; i < buffer1.byteLength; i++) { + if (view1[i] != view2[i]) { + return false; + } + } + return true; +} + +function getBlob(type, buffer) +{ + builder.append(buffer); + return builder.getBlob(type); +} + +function getFile(name, type, buffer) +{ + builder.append(buffer); + return builder.getFile(name, type); +} + +function getRandomBlob(size) +{ + return getBlob("binary/random", getRandomBuffer(size)); +} + +function getRandomFile(name, size) +{ + return getFile(name, "binary/random", getRandomBuffer(size)); +} + +function getNullBlob(size) +{ + return getBlob("binary/null", getBuffer(size)); +} + +function getNullFile(name, size) +{ + return getFile(name, "binary/null", getBuffer(size)); +} + +function verifyBuffers(buffer1, buffer2) +{ + ok(compareBuffers(buffer1, buffer2), "Correct blob data"); +} + +function verifyBlob(blob1, blob2, fileId, blobReadHandler) +{ + is(blob1 instanceof Components.interfaces.nsIDOMBlob, true, + "Instance of nsIDOMBlob"); + is(blob1 instanceof Components.interfaces.nsIDOMFile, + blob2 instanceof Components.interfaces.nsIDOMFile, + "Instance of nsIDOMFile"); + is(blob1.size, blob2.size, "Correct size"); + is(blob1.type, blob2.type, "Correct type"); + if (blob2 instanceof Components.interfaces.nsIDOMFile) { + is(blob1.name, blob2.name, "Correct name"); + } + is(utils.getFileId(blob1), fileId, "Correct file id"); + + let buffer1; + let buffer2; + + for (let i = 0; i < bufferCache.length; i++) { + if (bufferCache[i].blob == blob2) { + buffer2 = bufferCache[i].buffer; + break; + } + } + + if (!buffer2) { + let reader = new FileReader(); + reader.readAsArrayBuffer(blob2); + reader.onload = function(event) { + buffer2 = event.target.result; + bufferCache.push({ blob: blob2, buffer: buffer2 }); + if (buffer1) { + verifyBuffers(buffer1, buffer2); + if (blobReadHandler) { + blobReadHandler(); + } else { + testGenerator.next(); + } + } + } + } + + let reader = new FileReader(); + reader.readAsArrayBuffer(blob1); + reader.onload = function(event) { + buffer1 = event.target.result; + if (buffer2) { + verifyBuffers(buffer1, buffer2); + if (blobReadHandler) { + blobReadHandler(); + } else { + testGenerator.next(); + } + } + } +} + +function verifyBlobArray(blobs1, blobs2, expectedFileIds) +{ + is(blobs1 instanceof Array, true, "Got an array object"); + is(blobs1.length, blobs2.length, "Correct length"); + + if (!blobs1.length) { + return; + } + + let verifiedCount = 0; + + function blobReadHandler() { + if (++verifiedCount == blobs1.length) { + testGenerator.next(); + } else { + verifyBlob(blobs1[verifiedCount], blobs2[verifiedCount], + expectedFileIds[verifiedCount], blobReadHandler); + } + } + + verifyBlob(blobs1[verifiedCount], blobs2[verifiedCount], + expectedFileIds[verifiedCount], blobReadHandler); +} + +function grabFileUsageAndContinueHandler(usage, fileUsage) +{ + testGenerator.send(fileUsage); +} + +function getUsage(usageHandler) +{ + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + + if (!manager) { + manager = Components.classes["@mozilla.org/dom/indexeddb/manager;1"] + .getService(Components.interfaces.nsIIndexedDatabaseManager); + } + + let uri = SpecialPowers.getDocumentURIObject(window.document); + let callback = { + onUsageResult: function(uri, usage, fileUsage) { + usageHandler(usage, fileUsage); + } + }; + + manager.getUsageForURI(uri, callback); +} + +function getUsageSync() +{ + let usage; + + getUsage(function(aUsage, aFileUsage) { + usage = aUsage; + }); + + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); + let thread = Components.classes["@mozilla.org/thread-manager;1"] + .getService(Components.interfaces.nsIThreadManager) + .currentThread; + while (!usage) { + thread.processNextEvent(true); + } + + return usage; +} + +function scheduleGC() +{ + SpecialPowers.exactGC(window, continueToNextStep); +} + +function hasFileInfo(name, id) +{ + return utils.getFileReferences(name, id); +} + +function getFileRefCount(name, id) +{ + let count = {}; + utils.getFileReferences(name, id, count); + return count.value; +} + +function getFileDBRefCount(name, id) +{ + let count = {}; + utils.getFileReferences(name, id, {}, count); + return count.value; +} diff --git a/dom/indexedDB/test/helpers.js b/dom/indexedDB/test/helpers.js index c355a4f4c5b..4fc3b3a094b 100644 --- a/dom/indexedDB/test/helpers.js +++ b/dom/indexedDB/test/helpers.js @@ -8,6 +8,7 @@ var testGenerator = testSteps(); function runTest() { allowIndexedDB(); + allowUnlimitedQuota(); SimpleTest.waitForExplicitFinish(); testGenerator.next(); @@ -15,7 +16,8 @@ function runTest() function finishTest() { - disallowIndexedDB(); + resetUnlimitedQuota(); + resetIndexedDB(); SimpleTest.executeSoon(function() { testGenerator.close(); @@ -77,7 +79,7 @@ ExpectError.prototype = { } }; -function addPermission(permission, url) +function addPermission(type, allow, url) { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); @@ -86,15 +88,20 @@ function addPermission(permission, url) uri = Components.classes["@mozilla.org/network/io-service;1"] .getService(Components.interfaces.nsIIOService) .newURI(url, null, null); - } - else { + } else { uri = SpecialPowers.getDocumentURIObject(window.document); } + let permission; + if (allow) { + permission = Components.interfaces.nsIPermissionManager.ALLOW_ACTION; + } else { + permission = Components.interfaces.nsIPermissionManager.DENY_ACTION; + } + Components.classes["@mozilla.org/permissionmanager;1"] .getService(Components.interfaces.nsIPermissionManager) - .add(uri, permission, - Components.interfaces.nsIPermissionManager.ALLOW_ACTION); + .add(uri, type, permission); } function removePermission(permission, url) @@ -128,20 +135,25 @@ function setQuota(quota) function allowIndexedDB(url) { - addPermission("indexedDB", url); + addPermission("indexedDB", true, url); } -function disallowIndexedDB(url) +function resetIndexedDB(url) { removePermission("indexedDB", url); } function allowUnlimitedQuota(url) { - addPermission("indexedDB-unlimited", url); + addPermission("indexedDB-unlimited", true, url); } -function disallowUnlimitedQuota(url) +function denyUnlimitedQuota(url) +{ + addPermission("indexedDB-unlimited", false, url); +} + +function resetUnlimitedQuota(url) { removePermission("indexedDB-unlimited", url); } diff --git a/dom/indexedDB/test/test_file_array.html b/dom/indexedDB/test/test_file_array.html new file mode 100644 index 00000000000..70eef4deef6 --- /dev/null +++ b/dom/indexedDB/test/test_file_array.html @@ -0,0 +1,84 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + + diff --git a/dom/indexedDB/test/test_file_cross_database_copying.html b/dom/indexedDB/test/test_file_cross_database_copying.html new file mode 100644 index 00000000000..f221db1d86d --- /dev/null +++ b/dom/indexedDB/test/test_file_cross_database_copying.html @@ -0,0 +1,106 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + + diff --git a/dom/indexedDB/test/test_file_delete.html b/dom/indexedDB/test/test_file_delete.html new file mode 100644 index 00000000000..73aaecde0fd --- /dev/null +++ b/dom/indexedDB/test/test_file_delete.html @@ -0,0 +1,134 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + + diff --git a/dom/indexedDB/test/test_file_os_delete.html b/dom/indexedDB/test/test_file_os_delete.html new file mode 100644 index 00000000000..600ec60468b --- /dev/null +++ b/dom/indexedDB/test/test_file_os_delete.html @@ -0,0 +1,86 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + + diff --git a/dom/indexedDB/test/test_file_put_get_values.html b/dom/indexedDB/test/test_file_put_get_values.html new file mode 100644 index 00000000000..d1c6b524bb9 --- /dev/null +++ b/dom/indexedDB/test/test_file_put_get_values.html @@ -0,0 +1,101 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + + diff --git a/dom/indexedDB/test/test_file_quota.html b/dom/indexedDB/test/test_file_quota.html new file mode 100644 index 00000000000..23018665b6a --- /dev/null +++ b/dom/indexedDB/test/test_file_quota.html @@ -0,0 +1,75 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + + diff --git a/dom/indexedDB/test/test_file_resurrection_delete.html b/dom/indexedDB/test/test_file_resurrection_delete.html new file mode 100644 index 00000000000..6f93bb9cd13 --- /dev/null +++ b/dom/indexedDB/test/test_file_resurrection_delete.html @@ -0,0 +1,136 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + + diff --git a/dom/indexedDB/test/test_file_resurrection_transaction_abort.html b/dom/indexedDB/test/test_file_resurrection_transaction_abort.html new file mode 100644 index 00000000000..6c1cab3e50c --- /dev/null +++ b/dom/indexedDB/test/test_file_resurrection_transaction_abort.html @@ -0,0 +1,93 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + + diff --git a/dom/indexedDB/test/test_file_sharing.html b/dom/indexedDB/test/test_file_sharing.html new file mode 100644 index 00000000000..844f5a15d17 --- /dev/null +++ b/dom/indexedDB/test/test_file_sharing.html @@ -0,0 +1,106 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + + diff --git a/dom/indexedDB/test/test_file_transaction_abort.html b/dom/indexedDB/test/test_file_transaction_abort.html new file mode 100644 index 00000000000..1921531b981 --- /dev/null +++ b/dom/indexedDB/test/test_file_transaction_abort.html @@ -0,0 +1,78 @@ + + + + Indexed Database Property Test + + + + + + + + + + + + + diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index 8d91b22b59a..80288d9aced 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -65,10 +65,11 @@ interface nsIDOMEvent; interface nsITransferable; interface nsIQueryContentEventResult; interface nsIDOMWindow; +interface nsIDOMBlob; interface nsIDOMFile; interface nsIFile; -[scriptable, uuid(bf868921-0288-4799-a806-2fa642590197)] +[scriptable, uuid(36adf309-e5c4-4912-9152-7fb151dc754a)] interface nsIDOMWindowUtils : nsISupports { /** @@ -926,4 +927,18 @@ interface nsIDOMWindowUtils : nsISupports { */ boolean checkAndClearPaintedState(in nsIDOMElement aElement); + /* + * Get internal id of the stored blob. + */ + long long getFileId(in nsIDOMBlob aBlob); + + /* + * Get file ref count info for given database and file id. + * + */ + boolean getFileReferences(in AString aDatabaseName, in long long aId, + [optional] out long aRefCnt, + [optional] out long aDBRefCnt, + [optional] out long aSliceRefCnt); + }; diff --git a/storage/public/mozIStorageConnection.idl b/storage/public/mozIStorageConnection.idl index 872d3b64f6d..e23f354ac15 100644 --- a/storage/public/mozIStorageConnection.idl +++ b/storage/public/mozIStorageConnection.idl @@ -61,7 +61,7 @@ interface nsIFile; * * @threadsafe */ -[scriptable, uuid(ad035628-4ffb-42ff-a256-0ed9e410b859)] +[scriptable, uuid(b2a4b534-f92e-4387-9bd9-d10408173925)] interface mozIStorageConnection : nsISupports { /** * The default size for SQLite database pages used by mozStorage for new @@ -143,6 +143,12 @@ interface mozIStorageConnection : nsISupports { */ readonly attribute long long lastInsertRowID; + /** + * affectedRows returns the number of database rows that were changed or + * inserted or deleted by last operation. + */ + readonly attribute long affectedRows; + /** * The last error SQLite error code. */ @@ -384,4 +390,15 @@ interface mozIStorageConnection : nsISupports { * If the system is short on storage space. */ void setGrowthIncrement(in PRInt32 aIncrement, in AUTF8String aDatabaseName); + + /** + * Enable a predefined virtual table implementation. + * + * @param aModuleName + * The module to enable. Only "filesystem" is currently supported. + * + * @throws NS_ERROR_FAILURE + * For unknown module names. + */ + [noscript] void enableModule(in ACString aModuleName); }; diff --git a/storage/public/mozIStorageServiceQuotaManagement.idl b/storage/public/mozIStorageServiceQuotaManagement.idl index 17691047865..f25a2961548 100644 --- a/storage/public/mozIStorageServiceQuotaManagement.idl +++ b/storage/public/mozIStorageServiceQuotaManagement.idl @@ -75,7 +75,7 @@ interface mozIStorageQuotaCallback : nsISupports * This is a temporary interface that should eventually merge with * mozIStorageService. */ -[scriptable, uuid(11def472-446f-4635-a1d8-8856e85aac50)] +[scriptable, uuid(4d81faf5-fe01-428b-99b8-c94cba12fd72)] interface mozIStorageServiceQuotaManagement : nsISupports { /** @@ -128,5 +128,5 @@ interface mozIStorageServiceQuotaManagement : nsISupports * file does not exist then the file will be removed from tracking * under the quota system. */ - void updateQutoaInformationForFile(in nsIFile aFile); + void updateQuotaInformationForFile(in nsIFile aFile); }; diff --git a/storage/src/FileSystemModule.cpp b/storage/src/FileSystemModule.cpp new file mode 100644 index 00000000000..fd24fb2e9a7 --- /dev/null +++ b/storage/src/FileSystemModule.cpp @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "FileSystemModule.h" + +#include "sqlite3.h" +#include "nsString.h" +#include "nsISimpleEnumerator.h" +#include "nsIFile.h" +#include "nsILocalFile.h" + +namespace { + +struct VirtualTableCursorBase +{ + VirtualTableCursorBase() + { + memset(&mBase, 0, sizeof(mBase)); + } + + sqlite3_vtab_cursor mBase; +}; + +struct VirtualTableCursor : public VirtualTableCursorBase +{ +public: + VirtualTableCursor() + : mRowId(-1) + { + mCurrentFileName.SetIsVoid(true); + } + + const nsString& DirectoryPath() const + { + return mDirectoryPath; + } + + const nsString& CurrentFileName() const + { + return mCurrentFileName; + } + + PRInt64 RowId() const + { + return mRowId; + } + + nsresult Init(const nsAString& aPath); + nsresult NextFile(); + +private: + nsCOMPtr mEntries; + + nsString mDirectoryPath; + nsString mCurrentFileName; + + PRInt64 mRowId; +}; + +nsresult +VirtualTableCursor::Init(const nsAString& aPath) +{ + nsCOMPtr directory = + do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + NS_ENSURE_TRUE(directory, NS_ERROR_FAILURE); + + nsresult rv = directory->InitWithPath(aPath); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directory->GetPath(mDirectoryPath); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directory->GetDirectoryEntries(getter_AddRefs(mEntries)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NextFile(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +VirtualTableCursor::NextFile() +{ + bool hasMore; + nsresult rv = mEntries->HasMoreElements(&hasMore); + NS_ENSURE_SUCCESS(rv, rv); + + if (!hasMore) { + mCurrentFileName.SetIsVoid(true); + return NS_OK; + } + + nsCOMPtr entry; + rv = mEntries->GetNext(getter_AddRefs(entry)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr file = do_QueryInterface(entry); + NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); + + rv = file->GetLeafName(mCurrentFileName); + NS_ENSURE_SUCCESS(rv, rv); + + mRowId++; + + return NS_OK; +} + +int Connect(sqlite3* aDB, void* aAux, int aArgc, const char* const* aArgv, + sqlite3_vtab** aVtab, char** aErr) +{ + static const char virtualTableSchema[] = + "CREATE TABLE fs (" + "name TEXT, " + "path TEXT" + ")"; + + int rc = sqlite3_declare_vtab(aDB, virtualTableSchema); + if (rc != SQLITE_OK) { + return rc; + } + + sqlite3_vtab* vt = new sqlite3_vtab(); + memset(vt, 0, sizeof(*vt)); + + *aVtab = vt; + + return SQLITE_OK; +} + +int Disconnect(sqlite3_vtab* aVtab ) +{ + delete aVtab; + + return SQLITE_OK; +} + +int BestIndex(sqlite3_vtab* aVtab, sqlite3_index_info* aInfo) +{ + // Here we specify what index constraints we want to handle. That is, there + // might be some columns with particular constraints in which we can help + // SQLite narrow down the result set. + // + // For example, take the "path = x" where x is a directory. In this case, + // we can narrow our search to just this directory instead of the entire file + // system. This can be a significant optimization. So, we want to handle that + // constraint. To do so, we would look for two specific input conditions: + // + // 1. aInfo->aConstraint[i].iColumn == 1 + // 2. aInfo->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_EQ + // + // The first states that the path column is being used in one of the input + // constraints and the second states that the constraint involves the equal + // operator. + // + // An even more specific search would be for name='xxx', in which case we + // can limit the search to a single file, if it exists. + // + // What we have to do here is look for all of our index searches and select + // the narrowest. We can only pick one, so obviously we want the one that + // is the most specific, which leads to the smallest result set. + + for(int i = 0; i < aInfo->nConstraint; i++) { + if (aInfo->aConstraint[i].iColumn == 1 && aInfo->aConstraint[i].usable) { + if (aInfo->aConstraint[i].op & SQLITE_INDEX_CONSTRAINT_EQ) { + aInfo->aConstraintUsage[i].argvIndex = 1; + } + break; + } + + // TODO: handle single files (constrained also by the name column) + } + + return SQLITE_OK; +} + +int Open(sqlite3_vtab* aVtab, sqlite3_vtab_cursor** aCursor) +{ + VirtualTableCursor* cursor = new VirtualTableCursor(); + + *aCursor = reinterpret_cast(cursor); + + return SQLITE_OK; +} + +int Close(sqlite3_vtab_cursor* aCursor) +{ + VirtualTableCursor* cursor = reinterpret_cast(aCursor); + + delete cursor; + + return SQLITE_OK; +} + +int Filter(sqlite3_vtab_cursor* aCursor, int aIdxNum, const char* aIdxStr, + int aArgc, sqlite3_value** aArgv) +{ + VirtualTableCursor* cursor = reinterpret_cast(aCursor); + + if(aArgc <= 0) { + return SQLITE_OK; + } + + nsDependentString path( + reinterpret_cast(::sqlite3_value_text16(aArgv[0]))); + + nsresult rv = cursor->Init(path); + NS_ENSURE_SUCCESS(rv, SQLITE_ERROR); + + return SQLITE_OK; +} + +int Next(sqlite3_vtab_cursor* aCursor) +{ + VirtualTableCursor* cursor = reinterpret_cast(aCursor); + + nsresult rv = cursor->NextFile(); + NS_ENSURE_SUCCESS(rv, SQLITE_ERROR); + + return SQLITE_OK; +} + +int Eof(sqlite3_vtab_cursor* aCursor) +{ + VirtualTableCursor* cursor = reinterpret_cast(aCursor); + return cursor->CurrentFileName().IsVoid() ? 1 : 0; +} + +int Column(sqlite3_vtab_cursor* aCursor, sqlite3_context* aContext, + int aColumnIndex) +{ + VirtualTableCursor* cursor = reinterpret_cast(aCursor); + + switch (aColumnIndex) { + // name + case 0: { + const nsString& name = cursor->CurrentFileName(); + sqlite3_result_text16(aContext, name.get(), + name.Length() * sizeof(PRUnichar), + SQLITE_TRANSIENT); + break; + } + + // path + case 1: { + const nsString& path = cursor->DirectoryPath(); + sqlite3_result_text16(aContext, path.get(), + path.Length() * sizeof(PRUnichar), + SQLITE_TRANSIENT); + break; + } + default: + NS_NOTREACHED("Unsupported column!"); + } + + return SQLITE_OK; +} + +int RowId(sqlite3_vtab_cursor* aCursor, sqlite3_int64* aRowid) +{ + VirtualTableCursor* cursor = reinterpret_cast(aCursor); + + *aRowid = cursor->RowId(); + + return SQLITE_OK; +} + +} // anonymous namespace + +namespace mozilla { +namespace storage { + +int RegisterFileSystemModule(sqlite3* aDB, const char* aName) +{ + static sqlite3_module module = { + 1, + Connect, + Connect, + BestIndex, + Disconnect, + Disconnect, + Open, + Close, + Filter, + Next, + Eof, + Column, + RowId, + nsnull, + nsnull, + nsnull, + nsnull, + nsnull, + nsnull, + nsnull + }; + + return sqlite3_create_module(aDB, aName, &module, nsnull); +} + +} // namespace storage +} // namespace mozilla diff --git a/storage/src/FileSystemModule.h b/storage/src/FileSystemModule.h new file mode 100644 index 00000000000..af4d541a86a --- /dev/null +++ b/storage/src/FileSystemModule.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef mozilla_storage_FileSystemModule_h +#define mozilla_storage_FileSystemModule_h + +#include "nscore.h" + +struct sqlite3; + +namespace mozilla { +namespace storage { + +NS_HIDDEN_(int) RegisterFileSystemModule(sqlite3* aDB, const char* aName); + +} // namespace storage +} // namespace mozilla + +#endif // mozilla_storage_FileSystemModule_h diff --git a/storage/src/Makefile.in b/storage/src/Makefile.in index 259361f79b0..43c9ee9dcef 100644 --- a/storage/src/Makefile.in +++ b/storage/src/Makefile.in @@ -86,6 +86,7 @@ CPPSRCS = \ SQLCollations.cpp \ VacuumManager.cpp \ TelemetryVFS.cpp \ + FileSystemModule.cpp \ $(NULL) # For nsDependentJSString diff --git a/storage/src/mozStorageConnection.cpp b/storage/src/mozStorageConnection.cpp index 007355cebf4..abcc0c0c3cf 100644 --- a/storage/src/mozStorageConnection.cpp +++ b/storage/src/mozStorageConnection.cpp @@ -69,6 +69,7 @@ #include "mozStorageStatementData.h" #include "StorageBaseStatementInternal.h" #include "SQLCollations.h" +#include "FileSystemModule.h" #include "prlog.h" #include "prprf.h" @@ -158,6 +159,19 @@ sqlite3_T_blob(sqlite3_context *aCtx, #include "variantToSQLiteT_impl.h" +//////////////////////////////////////////////////////////////////////////////// +//// Modules + +struct Module +{ + const char* name; + int (*registerFunc)(sqlite3*, const char*); +}; + +Module gModules[] = { + { "filesystem", RegisterFileSystemModule } +}; + //////////////////////////////////////////////////////////////////////////////// //// Local Functions @@ -1129,6 +1143,16 @@ Connection::GetLastInsertRowID(PRInt64 *_id) return NS_OK; } +NS_IMETHODIMP +Connection::GetAffectedRows(PRInt32 *_rows) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + *_rows = ::sqlite3_changes(mDBConn); + + return NS_OK; +} + NS_IMETHODIMP Connection::GetLastError(PRInt32 *_error) { @@ -1512,5 +1536,24 @@ Connection::SetGrowthIncrement(PRInt32 aChunkSize, const nsACString &aDatabaseNa return NS_OK; } +NS_IMETHODIMP +Connection::EnableModule(const nsACString& aModuleName) +{ + if (!mDBConn) return NS_ERROR_NOT_INITIALIZED; + + for (size_t i = 0; i < ArrayLength(gModules); i++) { + struct Module* m = &gModules[i]; + if (aModuleName.Equals(m->name)) { + int srv = m->registerFunc(mDBConn, m->name); + if (srv != SQLITE_OK) + return convertResultCode(srv); + + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + } // namespace storage } // namespace mozilla diff --git a/storage/src/mozStorageService.cpp b/storage/src/mozStorageService.cpp index d741ebba71e..4171592eedb 100644 --- a/storage/src/mozStorageService.cpp +++ b/storage/src/mozStorageService.cpp @@ -57,8 +57,14 @@ #include "mozilla/Preferences.h" #include "sqlite3.h" +#include "test_quota.h" #include "test_quota.c" +#ifdef SQLITE_OS_WIN +// "windows.h" was included and it can #define lots of things we care about... +#undef CompareString +#endif + #include "nsIPromptService.h" #include "nsIMemoryReporter.h" @@ -745,7 +751,7 @@ Service::SetQuotaForFilenamePattern(const nsACString &aPattern, } NS_IMETHODIMP -Service::UpdateQutoaInformationForFile(nsIFile *aFile) +Service::UpdateQuotaInformationForFile(nsIFile *aFile) { NS_ENSURE_ARG_POINTER(aFile); diff --git a/testing/mochitest/tests/SimpleTest/specialpowersAPI.js b/testing/mochitest/tests/SimpleTest/specialpowersAPI.js index 3e725e3dd46..6d79f0af489 100644 --- a/testing/mochitest/tests/SimpleTest/specialpowersAPI.js +++ b/testing/mochitest/tests/SimpleTest/specialpowersAPI.js @@ -527,6 +527,27 @@ SpecialPowersAPI.prototype = { Components.utils.forceGC(); }, + exactGC: function(win, callback) { + var self = this; + let count = 0; + + function doPreciseGCandCC() { + function scheduledGCCallback() { + self.getDOMWindowUtils(win).cycleCollect(); + + if (++count < 2) { + doPreciseGCandCC(); + } else { + callback(); + } + } + + Components.utils.schedulePreciseGC(scheduledGCCallback); + } + + doPreciseGCandCC(); + }, + hasContentProcesses: function() { try { var rt = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);