michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "Client.h" michael@0: michael@0: #include "mozilla/dom/quota/QuotaManager.h" michael@0: #include "mozilla/dom/quota/UsageInfo.h" michael@0: #include "mozilla/dom/quota/Utilities.h" michael@0: michael@0: #include "IDBDatabase.h" michael@0: #include "IndexedDatabaseManager.h" michael@0: #include "TransactionThreadPool.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: michael@0: USING_INDEXEDDB_NAMESPACE michael@0: using mozilla::dom::quota::AssertIsOnIOThread; michael@0: using mozilla::dom::quota::QuotaManager; michael@0: michael@0: namespace { michael@0: michael@0: bool michael@0: GetDatabaseBaseFilename(const nsAString& aFilename, michael@0: nsAString& aDatabaseBaseFilename) michael@0: { michael@0: NS_ASSERTION(!aFilename.IsEmpty(), "Bad argument!"); michael@0: michael@0: NS_NAMED_LITERAL_STRING(sqlite, ".sqlite"); michael@0: michael@0: if (!StringEndsWith(aFilename, sqlite)) { michael@0: return false; michael@0: } michael@0: michael@0: aDatabaseBaseFilename = michael@0: Substring(aFilename, 0, aFilename.Length() - sqlite.Length()); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: // This needs to be fully qualified to not confuse trace refcnt assertions. michael@0: NS_IMPL_ADDREF(mozilla::dom::indexedDB::Client) michael@0: NS_IMPL_RELEASE(mozilla::dom::indexedDB::Client) michael@0: michael@0: nsresult michael@0: Client::InitOrigin(PersistenceType aPersistenceType, const nsACString& aGroup, michael@0: const nsACString& aOrigin, UsageInfo* aUsageInfo) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: nsCOMPtr directory; michael@0: nsresult rv = michael@0: GetDirectory(aPersistenceType, aOrigin, getter_AddRefs(directory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We need to see if there are any files in the directory already. If they michael@0: // are database files then we need to cleanup stored files (if it's needed) michael@0: // and also get the usage. michael@0: michael@0: nsAutoTArray subdirsToProcess; michael@0: nsAutoTArray , 20> unknownFiles; michael@0: nsTHashtable validSubdirs(20); michael@0: michael@0: nsCOMPtr entries; michael@0: rv = directory->GetDirectoryEntries(getter_AddRefs(entries)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasMore; michael@0: while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && michael@0: hasMore && (!aUsageInfo || !aUsageInfo->Canceled())) { michael@0: nsCOMPtr entry; michael@0: rv = entries->GetNext(getter_AddRefs(entry)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr file = do_QueryInterface(entry); michael@0: NS_ENSURE_TRUE(file, NS_NOINTERFACE); michael@0: michael@0: nsString leafName; michael@0: rv = file->GetLeafName(leafName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (StringEndsWith(leafName, NS_LITERAL_STRING(".sqlite-journal"))) { michael@0: continue; michael@0: } michael@0: michael@0: if (leafName.EqualsLiteral(DSSTORE_FILE_NAME)) { michael@0: continue; michael@0: } michael@0: michael@0: bool isDirectory; michael@0: rv = file->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (isDirectory) { michael@0: if (!validSubdirs.GetEntry(leafName)) { michael@0: subdirsToProcess.AppendElement(leafName); michael@0: } michael@0: continue; michael@0: } michael@0: michael@0: nsString dbBaseFilename; michael@0: if (!GetDatabaseBaseFilename(leafName, dbBaseFilename)) { michael@0: unknownFiles.AppendElement(file); michael@0: continue; michael@0: } michael@0: michael@0: nsCOMPtr fmDirectory; michael@0: rv = directory->Clone(getter_AddRefs(fmDirectory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = fmDirectory->Append(dbBaseFilename); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = FileManager::InitDirectory(fmDirectory, file, aPersistenceType, aGroup, michael@0: aOrigin); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (aUsageInfo) { michael@0: int64_t fileSize; michael@0: rv = file->GetFileSize(&fileSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(fileSize >= 0, "Negative size?!"); michael@0: michael@0: aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize)); michael@0: michael@0: uint64_t usage; michael@0: rv = FileManager::GetUsage(fmDirectory, &usage); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aUsageInfo->AppendToFileUsage(usage); michael@0: } michael@0: michael@0: validSubdirs.PutEntry(dbBaseFilename); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: for (uint32_t i = 0; i < subdirsToProcess.Length(); i++) { michael@0: const nsString& subdir = subdirsToProcess[i]; michael@0: if (!validSubdirs.GetEntry(subdir)) { michael@0: NS_WARNING("Unknown subdirectory found!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < unknownFiles.Length(); i++) { michael@0: nsCOMPtr& unknownFile = unknownFiles[i]; michael@0: michael@0: // Some temporary SQLite files could disappear, so we have to check if the michael@0: // unknown file still exists. michael@0: bool exists; michael@0: rv = unknownFile->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists) { michael@0: nsString leafName; michael@0: unknownFile->GetLeafName(leafName); michael@0: michael@0: // The journal file may exists even after db has been correctly opened. michael@0: if (!StringEndsWith(leafName, NS_LITERAL_STRING(".sqlite-journal"))) { michael@0: NS_WARNING("Unknown file found!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Client::GetUsageForOrigin(PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, const nsACString& aOrigin, michael@0: UsageInfo* aUsageInfo) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(aUsageInfo, "Null pointer!"); michael@0: michael@0: nsCOMPtr directory; michael@0: nsresult rv = michael@0: GetDirectory(aPersistenceType, aOrigin, getter_AddRefs(directory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = GetUsageForDirectoryInternal(directory, aUsageInfo, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Client::OnOriginClearCompleted(PersistenceType aPersistenceType, michael@0: const OriginOrPatternString& aOriginOrPattern) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); michael@0: if (mgr) { michael@0: mgr->InvalidateFileManagers(aPersistenceType, aOriginOrPattern); michael@0: } michael@0: } michael@0: michael@0: void michael@0: Client::ReleaseIOThreadObjects() michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); michael@0: if (mgr) { michael@0: mgr->InvalidateAllFileManagers(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: Client::IsTransactionServiceActivated() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: return !!TransactionThreadPool::Get(); michael@0: } michael@0: michael@0: void michael@0: Client::WaitForStoragesToComplete(nsTArray& aStorages, michael@0: nsIRunnable* aCallback) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!aStorages.IsEmpty(), "No storages to wait on!"); michael@0: NS_ASSERTION(aCallback, "Passed null callback!"); michael@0: michael@0: TransactionThreadPool* pool = TransactionThreadPool::Get(); michael@0: NS_ASSERTION(pool, "Should have checked if transaction service is active!"); michael@0: michael@0: nsTArray > databases(aStorages.Length()); michael@0: for (uint32_t index = 0; index < aStorages.Length(); index++) { michael@0: IDBDatabase* database = IDBDatabase::FromStorage(aStorages[index]); michael@0: if (!database) { michael@0: MOZ_CRASH(); michael@0: } michael@0: michael@0: databases.AppendElement(database); michael@0: } michael@0: michael@0: pool->WaitForDatabasesToComplete(databases, aCallback); michael@0: } michael@0: michael@0: void michael@0: Client::AbortTransactionsForStorage(nsIOfflineStorage* aStorage) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aStorage, "Passed null storage!"); michael@0: michael@0: TransactionThreadPool* pool = TransactionThreadPool::Get(); michael@0: NS_ASSERTION(pool, "Should have checked if transaction service is active!"); michael@0: michael@0: IDBDatabase* database = IDBDatabase::FromStorage(aStorage); michael@0: NS_ASSERTION(database, "This shouldn't be null!"); michael@0: michael@0: pool->AbortTransactionsForDatabase(database); michael@0: } michael@0: michael@0: bool michael@0: Client::HasTransactionsForStorage(nsIOfflineStorage* aStorage) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: TransactionThreadPool* pool = TransactionThreadPool::Get(); michael@0: NS_ASSERTION(pool, "Should have checked if transaction service is active!"); michael@0: michael@0: IDBDatabase* database = IDBDatabase::FromStorage(aStorage); michael@0: NS_ASSERTION(database, "This shouldn't be null!"); michael@0: michael@0: return pool->HasTransactionsForDatabase(database); michael@0: } michael@0: michael@0: void michael@0: Client::ShutdownTransactionService() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: TransactionThreadPool::Shutdown(); michael@0: } michael@0: michael@0: nsresult michael@0: Client::GetDirectory(PersistenceType aPersistenceType, michael@0: const nsACString& aOrigin, nsIFile** aDirectory) michael@0: { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never fail!"); michael@0: michael@0: nsCOMPtr directory; michael@0: nsresult rv = quotaManager->GetDirectoryForOrigin(aPersistenceType, aOrigin, michael@0: getter_AddRefs(directory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(directory, "What?"); michael@0: michael@0: rv = directory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: directory.forget(aDirectory); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Client::GetUsageForDirectoryInternal(nsIFile* aDirectory, michael@0: UsageInfo* aUsageInfo, michael@0: bool aDatabaseFiles) michael@0: { michael@0: NS_ASSERTION(aDirectory, "Null pointer!"); michael@0: NS_ASSERTION(aUsageInfo, "Null pointer!"); michael@0: michael@0: nsCOMPtr entries; michael@0: nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!entries) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool hasMore; michael@0: while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && michael@0: hasMore && !aUsageInfo->Canceled()) { michael@0: nsCOMPtr entry; michael@0: rv = entries->GetNext(getter_AddRefs(entry)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr file(do_QueryInterface(entry)); michael@0: NS_ASSERTION(file, "Don't know what this is!"); michael@0: michael@0: bool isDirectory; michael@0: rv = file->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (isDirectory) { michael@0: if (aDatabaseFiles) { michael@0: rv = GetUsageForDirectoryInternal(file, aUsageInfo, false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: nsString leafName; michael@0: rv = file->GetLeafName(leafName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!leafName.EqualsLiteral(JOURNAL_DIRECTORY_NAME)) { michael@0: NS_WARNING("Unknown directory found!"); michael@0: } michael@0: } michael@0: michael@0: continue; michael@0: } michael@0: michael@0: int64_t fileSize; michael@0: rv = file->GetFileSize(&fileSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(fileSize >= 0, "Negative size?!"); michael@0: michael@0: if (aDatabaseFiles) { michael@0: aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize)); michael@0: } michael@0: else { michael@0: aUsageInfo->AppendToFileUsage(uint64_t(fileSize)); michael@0: } michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: }