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 michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "FileManager.h" michael@0: michael@0: #include "mozIStorageConnection.h" michael@0: #include "mozIStorageStatement.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: michael@0: #include "mozilla/dom/quota/Utilities.h" michael@0: #include "mozStorageCID.h" michael@0: #include "mozStorageHelper.h" michael@0: michael@0: #include "Client.h" michael@0: #include "FileInfo.h" michael@0: #include "IndexedDatabaseManager.h" michael@0: #include "OpenDatabaseHelper.h" michael@0: michael@0: #include "IndexedDatabaseInlines.h" michael@0: #include michael@0: michael@0: USING_INDEXEDDB_NAMESPACE michael@0: using mozilla::dom::quota::AssertIsOnIOThread; michael@0: michael@0: namespace { michael@0: michael@0: PLDHashOperator michael@0: EnumerateToTArray(const uint64_t& aKey, michael@0: FileInfo* aValue, michael@0: void* aUserArg) michael@0: { michael@0: NS_ASSERTION(aValue, "Null pointer!"); michael@0: NS_ASSERTION(aUserArg, "Null pointer!"); michael@0: michael@0: nsTArray* array = michael@0: static_cast*>(aUserArg); michael@0: michael@0: array->AppendElement(aValue); michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: already_AddRefed michael@0: GetDirectoryFor(const nsAString& aDirectoryPath) michael@0: { michael@0: nsCOMPtr directory = michael@0: do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); michael@0: NS_ENSURE_TRUE(directory, nullptr); michael@0: michael@0: nsresult rv = directory->InitWithPath(aDirectoryPath); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: return directory.forget(); michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: nsresult michael@0: FileManager::Init(nsIFile* aDirectory, michael@0: mozIStorageConnection* aConnection) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(aDirectory, "Null directory!"); michael@0: NS_ASSERTION(aConnection, "Null connection!"); michael@0: michael@0: bool exists; michael@0: nsresult rv = aDirectory->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists) { michael@0: bool isDirectory; michael@0: rv = aDirectory->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(isDirectory, NS_ERROR_FAILURE); michael@0: } michael@0: else { michael@0: rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = aDirectory->GetPath(mDirectoryPath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr journalDirectory; michael@0: rv = aDirectory->Clone(getter_AddRefs(journalDirectory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = journalDirectory->Append(NS_LITERAL_STRING(JOURNAL_DIRECTORY_NAME)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = journalDirectory->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists) { michael@0: bool isDirectory; michael@0: rv = journalDirectory->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(isDirectory, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: rv = journalDirectory->GetPath(mJournalDirectoryPath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr stmt; michael@0: rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT id, refcount " michael@0: "FROM file" michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasResult; michael@0: while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { michael@0: int64_t id; michael@0: rv = stmt->GetInt64(0, &id); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int32_t refcount; michael@0: rv = stmt->GetInt32(1, &refcount); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(refcount, "This shouldn't happen!"); michael@0: michael@0: nsRefPtr fileInfo = FileInfo::Create(this, id); michael@0: fileInfo->mDBRefCnt = refcount; michael@0: michael@0: mFileInfos.Put(id, fileInfo); michael@0: michael@0: mLastFileId = std::max(id, mLastFileId); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: FileManager::Invalidate() michael@0: { michael@0: if (IndexedDatabaseManager::IsClosed()) { michael@0: NS_ERROR("Shouldn't be called after shutdown!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: nsTArray fileInfos; michael@0: { michael@0: MutexAutoLock lock(IndexedDatabaseManager::FileMutex()); michael@0: michael@0: NS_ASSERTION(!mInvalidated, "Invalidate more than once?!"); michael@0: mInvalidated = true; michael@0: michael@0: fileInfos.SetCapacity(mFileInfos.Count()); michael@0: mFileInfos.EnumerateRead(EnumerateToTArray, &fileInfos); michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < fileInfos.Length(); i++) { michael@0: FileInfo* fileInfo = fileInfos.ElementAt(i); michael@0: fileInfo->ClearDBRefs(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: FileManager::GetDirectory() michael@0: { michael@0: return GetDirectoryFor(mDirectoryPath); michael@0: } michael@0: michael@0: already_AddRefed michael@0: FileManager::GetJournalDirectory() michael@0: { michael@0: return GetDirectoryFor(mJournalDirectoryPath); michael@0: } michael@0: michael@0: already_AddRefed michael@0: FileManager::EnsureJournalDirectory() michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsCOMPtr journalDirectory = GetDirectoryFor(mJournalDirectoryPath); michael@0: NS_ENSURE_TRUE(journalDirectory, nullptr); michael@0: michael@0: bool exists; michael@0: nsresult rv = journalDirectory->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: if (exists) { michael@0: bool isDirectory; michael@0: rv = journalDirectory->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: NS_ENSURE_TRUE(isDirectory, nullptr); michael@0: } michael@0: else { michael@0: rv = journalDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: } michael@0: michael@0: return journalDirectory.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: FileManager::GetFileInfo(int64_t aId) michael@0: { michael@0: if (IndexedDatabaseManager::IsClosed()) { michael@0: NS_ERROR("Shouldn't be called after shutdown!"); michael@0: return nullptr; michael@0: } michael@0: michael@0: FileInfo* fileInfo = nullptr; michael@0: { michael@0: MutexAutoLock lock(IndexedDatabaseManager::FileMutex()); michael@0: fileInfo = mFileInfos.Get(aId); michael@0: } michael@0: nsRefPtr result = fileInfo; michael@0: return result.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: FileManager::GetNewFileInfo() michael@0: { michael@0: if (IndexedDatabaseManager::IsClosed()) { michael@0: NS_ERROR("Shouldn't be called after shutdown!"); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsAutoPtr fileInfo; michael@0: michael@0: { michael@0: MutexAutoLock lock(IndexedDatabaseManager::FileMutex()); michael@0: michael@0: int64_t id = mLastFileId + 1; michael@0: michael@0: fileInfo = FileInfo::Create(this, id); michael@0: michael@0: mFileInfos.Put(id, fileInfo); michael@0: michael@0: mLastFileId = id; michael@0: } michael@0: michael@0: nsRefPtr result = fileInfo.forget(); michael@0: return result.forget(); michael@0: } michael@0: michael@0: // static michael@0: already_AddRefed michael@0: FileManager::GetFileForId(nsIFile* aDirectory, int64_t aId) michael@0: { michael@0: NS_ASSERTION(aDirectory, "Null pointer!"); michael@0: michael@0: nsAutoString id; michael@0: id.AppendInt(aId); michael@0: michael@0: nsCOMPtr file; michael@0: nsresult rv = aDirectory->Clone(getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: rv = file->Append(id); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: return file.forget(); michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: FileManager::InitDirectory(nsIFile* aDirectory, michael@0: nsIFile* aDatabaseFile, michael@0: PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, michael@0: const nsACString& aOrigin) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(aDirectory, "Null directory!"); michael@0: NS_ASSERTION(aDatabaseFile, "Null database file!"); michael@0: michael@0: bool exists; michael@0: nsresult rv = aDirectory->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!exists) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool isDirectory; michael@0: rv = aDirectory->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(isDirectory, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr journalDirectory; michael@0: rv = aDirectory->Clone(getter_AddRefs(journalDirectory)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = journalDirectory->Append(NS_LITERAL_STRING(JOURNAL_DIRECTORY_NAME)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = journalDirectory->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists) { michael@0: rv = journalDirectory->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(isDirectory, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr entries; michael@0: rv = journalDirectory->GetDirectoryEntries(getter_AddRefs(entries)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasElements; michael@0: rv = entries->HasMoreElements(&hasElements); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (hasElements) { michael@0: nsCOMPtr connection; michael@0: rv = OpenDatabaseHelper::CreateDatabaseConnection(aDatabaseFile, michael@0: aDirectory, NullString(), aPersistenceType, aGroup, aOrigin, michael@0: getter_AddRefs(connection)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mozStorageTransaction transaction(connection, false); michael@0: michael@0: rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE VIRTUAL TABLE fs USING filesystem;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr stmt; michael@0: rv = connection->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT name, (name IN (SELECT id FROM file)) FROM fs " michael@0: "WHERE path = :path" michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsString path; michael@0: rv = journalDirectory->GetPath(path); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->BindStringByName(NS_LITERAL_CSTRING("path"), path); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasResult; michael@0: while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { michael@0: nsString name; michael@0: rv = stmt->GetString(0, name); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: int32_t flag = stmt->AsInt32(1); michael@0: michael@0: if (!flag) { michael@0: nsCOMPtr file; michael@0: rv = aDirectory->Clone(getter_AddRefs(file)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = file->Append(name); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (NS_FAILED(file->Remove(false))) { michael@0: NS_WARNING("Failed to remove orphaned file!"); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr journalFile; michael@0: rv = journalDirectory->Clone(getter_AddRefs(journalFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = journalFile->Append(name); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (NS_FAILED(journalFile->Remove(false))) { michael@0: NS_WARNING("Failed to remove journal file!"); michael@0: } michael@0: } michael@0: michael@0: rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE fs;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: transaction.Commit(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: FileManager::GetUsage(nsIFile* aDirectory, uint64_t* aUsage) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: michael@0: bool exists; michael@0: nsresult rv = aDirectory->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!exists) { michael@0: *aUsage = 0; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr entries; michael@0: rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint64_t usage = 0; michael@0: michael@0: bool hasMore; michael@0: while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) { 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 (leafName.EqualsLiteral(JOURNAL_DIRECTORY_NAME)) { 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: quota::IncrementUsage(&usage, uint64_t(fileSize)); michael@0: } michael@0: michael@0: *aUsage = usage; michael@0: return NS_OK; michael@0: }