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 "FileService.h" michael@0: michael@0: #include "nsIFile.h" michael@0: #include "nsIFileStorage.h" michael@0: #include "nsIObserverService.h" michael@0: #include "nsIStreamTransportService.h" michael@0: michael@0: #include "nsNetCID.h" michael@0: michael@0: #include "FileHandle.h" michael@0: #include "FileRequest.h" michael@0: michael@0: USING_FILE_NAMESPACE michael@0: michael@0: namespace { michael@0: michael@0: FileService* gInstance = nullptr; michael@0: bool gShutdown = false; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: FileService::FileService() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!gInstance, "More than one instance!"); michael@0: } michael@0: michael@0: FileService::~FileService() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!gInstance, "More than one instance!"); michael@0: } michael@0: michael@0: nsresult michael@0: FileService::Init() michael@0: { michael@0: nsresult rv; michael@0: mStreamTransportTarget = michael@0: do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: FileService::Cleanup() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: nsIThread* thread = NS_GetCurrentThread(); michael@0: while (mFileStorageInfos.Count()) { michael@0: if (!NS_ProcessNextEvent(thread)) { michael@0: NS_ERROR("Failed to process next event!"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Make sure the service is still accessible while any generated callbacks michael@0: // are processed. michael@0: nsresult rv = NS_ProcessPendingEvents(thread); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!mCompleteCallbacks.IsEmpty()) { michael@0: // Run all callbacks manually now. michael@0: for (uint32_t index = 0; index < mCompleteCallbacks.Length(); index++) { michael@0: mCompleteCallbacks[index].mCallback->Run(); michael@0: } michael@0: mCompleteCallbacks.Clear(); michael@0: michael@0: // And make sure they get processed. michael@0: rv = NS_ProcessPendingEvents(thread); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: FileService* michael@0: FileService::GetOrCreate() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: if (gShutdown) { michael@0: NS_WARNING("Calling GetOrCreate() after shutdown!"); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!gInstance) { michael@0: nsRefPtr service(new FileService); michael@0: michael@0: nsresult rv = service->Init(); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: nsCOMPtr obs = michael@0: do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: rv = obs->AddObserver(service, "profile-before-change", false); michael@0: NS_ENSURE_SUCCESS(rv, nullptr); michael@0: michael@0: // The observer service now owns us. michael@0: gInstance = service; michael@0: } michael@0: michael@0: return gInstance; michael@0: } michael@0: michael@0: // static michael@0: FileService* michael@0: FileService::Get() michael@0: { michael@0: // Does not return an owning reference. michael@0: return gInstance; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: FileService::Shutdown() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: gShutdown = true; michael@0: michael@0: if (gInstance) { michael@0: if (NS_FAILED(gInstance->Cleanup())) { michael@0: NS_WARNING("Failed to shutdown file service!"); michael@0: } michael@0: gInstance = nullptr; michael@0: } michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: FileService::IsShuttingDown() michael@0: { michael@0: return gShutdown; michael@0: } michael@0: michael@0: nsresult michael@0: FileService::Enqueue(LockedFile* aLockedFile, FileHelper* aFileHelper) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aLockedFile, "Null pointer!"); michael@0: michael@0: FileHandle* fileHandle = aLockedFile->mFileHandle; michael@0: michael@0: if (fileHandle->mFileStorage->IsInvalidated()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: const nsACString& storageId = fileHandle->mFileStorage->Id(); michael@0: const nsAString& fileName = fileHandle->mFileName; michael@0: bool modeIsWrite = aLockedFile->mMode == FileMode::Readwrite; michael@0: michael@0: FileStorageInfo* fileStorageInfo; michael@0: if (!mFileStorageInfos.Get(storageId, &fileStorageInfo)) { michael@0: nsAutoPtr newFileStorageInfo(new FileStorageInfo()); michael@0: michael@0: mFileStorageInfos.Put(storageId, newFileStorageInfo); michael@0: michael@0: fileStorageInfo = newFileStorageInfo.forget(); michael@0: } michael@0: michael@0: LockedFileQueue* existingLockedFileQueue = michael@0: fileStorageInfo->GetLockedFileQueue(aLockedFile); michael@0: michael@0: if (existingLockedFileQueue) { michael@0: existingLockedFileQueue->Enqueue(aFileHelper); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool lockedForReading = fileStorageInfo->IsFileLockedForReading(fileName); michael@0: bool lockedForWriting = fileStorageInfo->IsFileLockedForWriting(fileName); michael@0: michael@0: if (modeIsWrite) { michael@0: if (!lockedForWriting) { michael@0: fileStorageInfo->LockFileForWriting(fileName); michael@0: } michael@0: } michael@0: else { michael@0: if (!lockedForReading) { michael@0: fileStorageInfo->LockFileForReading(fileName); michael@0: } michael@0: } michael@0: michael@0: if (lockedForWriting || (lockedForReading && modeIsWrite)) { michael@0: fileStorageInfo->CreateDelayedEnqueueInfo(aLockedFile, aFileHelper); michael@0: } michael@0: else { michael@0: LockedFileQueue* lockedFileQueue = michael@0: fileStorageInfo->CreateLockedFileQueue(aLockedFile); michael@0: michael@0: if (aFileHelper) { michael@0: // Enqueue() will queue the file helper if there's already something michael@0: // running. That can't fail, so no need to eventually remove michael@0: // fileStorageInfo from the hash table. michael@0: // michael@0: // If the file helper is free to run then AsyncRun() is called on the michael@0: // file helper. AsyncRun() is responsible for calling all necessary michael@0: // callbacks when something fails. We're propagating the error here, michael@0: // however there's no need to eventually remove fileStorageInfo from michael@0: // the hash table. Code behind AsyncRun() will take care of it. The last michael@0: // item in the code path is NotifyLockedFileCompleted() which removes michael@0: // fileStorageInfo from the hash table if there are no locked files for michael@0: // the file storage. michael@0: nsresult rv = lockedFileQueue->Enqueue(aFileHelper); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: FileService::NotifyLockedFileCompleted(LockedFile* aLockedFile) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aLockedFile, "Null pointer!"); michael@0: michael@0: FileHandle* fileHandle = aLockedFile->mFileHandle; michael@0: const nsACString& storageId = fileHandle->mFileStorage->Id(); michael@0: michael@0: FileStorageInfo* fileStorageInfo; michael@0: if (!mFileStorageInfos.Get(storageId, &fileStorageInfo)) { michael@0: NS_ERROR("We don't know anyting about this locked file?!"); michael@0: return; michael@0: } michael@0: michael@0: fileStorageInfo->RemoveLockedFileQueue(aLockedFile); michael@0: michael@0: if (!fileStorageInfo->HasRunningLockedFiles()) { michael@0: mFileStorageInfos.Remove(storageId); michael@0: michael@0: // See if we need to fire any complete callbacks. michael@0: uint32_t index = 0; michael@0: while (index < mCompleteCallbacks.Length()) { michael@0: if (MaybeFireCallback(mCompleteCallbacks[index])) { michael@0: mCompleteCallbacks.RemoveElementAt(index); michael@0: } michael@0: else { michael@0: index++; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: FileService::WaitForStoragesToComplete( michael@0: nsTArray >& aStorages, michael@0: nsIRunnable* aCallback) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!aStorages.IsEmpty(), "No databases to wait on!"); michael@0: NS_ASSERTION(aCallback, "Null pointer!"); michael@0: michael@0: StoragesCompleteCallback* callback = mCompleteCallbacks.AppendElement(); michael@0: callback->mCallback = aCallback; michael@0: callback->mStorages.SwapElements(aStorages); michael@0: michael@0: if (MaybeFireCallback(*callback)) { michael@0: mCompleteCallbacks.RemoveElementAt(mCompleteCallbacks.Length() - 1); michael@0: } michael@0: } michael@0: michael@0: void michael@0: FileService::AbortLockedFilesForStorage(nsIFileStorage* aFileStorage) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aFileStorage, "Null pointer!"); michael@0: michael@0: FileStorageInfo* fileStorageInfo; michael@0: if (!mFileStorageInfos.Get(aFileStorage->Id(), &fileStorageInfo)) { michael@0: return; michael@0: } michael@0: michael@0: nsAutoTArray, 10> lockedFiles; michael@0: fileStorageInfo->CollectRunningAndDelayedLockedFiles(aFileStorage, michael@0: lockedFiles); michael@0: michael@0: for (uint32_t index = 0; index < lockedFiles.Length(); index++) { michael@0: ErrorResult ignored; michael@0: lockedFiles[index]->Abort(ignored); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: FileService::HasLockedFilesForStorage(nsIFileStorage* aFileStorage) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aFileStorage, "Null pointer!"); michael@0: michael@0: FileStorageInfo* fileStorageInfo; michael@0: if (!mFileStorageInfos.Get(aFileStorage->Id(), &fileStorageInfo)) { michael@0: return false; michael@0: } michael@0: michael@0: return fileStorageInfo->HasRunningLockedFiles(aFileStorage); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(FileService, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: FileService::Observe(nsISupports* aSubject, const char* aTopic, michael@0: const char16_t* aData) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!strcmp(aTopic, "profile-before-change"), "Wrong topic!"); michael@0: michael@0: Shutdown(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: FileService::MaybeFireCallback(StoragesCompleteCallback& aCallback) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: for (uint32_t index = 0; index < aCallback.mStorages.Length(); index++) { michael@0: if (mFileStorageInfos.Get(aCallback.mStorages[index]->Id(), nullptr)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: aCallback.mCallback->Run(); michael@0: return true; michael@0: } michael@0: michael@0: FileService::LockedFileQueue::LockedFileQueue(LockedFile* aLockedFile) michael@0: : mLockedFile(aLockedFile) michael@0: { michael@0: NS_ASSERTION(aLockedFile, "Null pointer!"); michael@0: } michael@0: michael@0: NS_IMPL_ADDREF(FileService::LockedFileQueue) michael@0: NS_IMPL_RELEASE(FileService::LockedFileQueue) michael@0: michael@0: nsresult michael@0: FileService::LockedFileQueue::Enqueue(FileHelper* aFileHelper) michael@0: { michael@0: mQueue.AppendElement(aFileHelper); michael@0: michael@0: nsresult rv; michael@0: if (mLockedFile->mRequestMode == LockedFile::PARALLEL) { michael@0: rv = aFileHelper->AsyncRun(this); michael@0: } michael@0: else { michael@0: rv = ProcessQueue(); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: FileService::LockedFileQueue::OnFileHelperComplete(FileHelper* aFileHelper) michael@0: { michael@0: if (mLockedFile->mRequestMode == LockedFile::PARALLEL) { michael@0: int32_t index = mQueue.IndexOf(aFileHelper); michael@0: NS_ASSERTION(index != -1, "We don't know anything about this helper!"); michael@0: michael@0: mQueue.RemoveElementAt(index); michael@0: } michael@0: else { michael@0: NS_ASSERTION(mCurrentHelper == aFileHelper, "How can this happen?!"); michael@0: michael@0: mCurrentHelper = nullptr; michael@0: michael@0: nsresult rv = ProcessQueue(); michael@0: if (NS_FAILED(rv)) { michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: FileService::LockedFileQueue::ProcessQueue() michael@0: { michael@0: if (mQueue.IsEmpty() || mCurrentHelper) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mCurrentHelper = mQueue[0]; michael@0: mQueue.RemoveElementAt(0); michael@0: michael@0: nsresult rv = mCurrentHelper->AsyncRun(this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: FileService::LockedFileQueue* michael@0: FileService::FileStorageInfo::CreateLockedFileQueue(LockedFile* aLockedFile) michael@0: { michael@0: nsRefPtr* lockedFileQueue = michael@0: mLockedFileQueues.AppendElement(); michael@0: *lockedFileQueue = new LockedFileQueue(aLockedFile); michael@0: return lockedFileQueue->get(); michael@0: } michael@0: michael@0: FileService::LockedFileQueue* michael@0: FileService::FileStorageInfo::GetLockedFileQueue(LockedFile* aLockedFile) michael@0: { michael@0: uint32_t count = mLockedFileQueues.Length(); michael@0: for (uint32_t index = 0; index < count; index++) { michael@0: nsRefPtr& lockedFileQueue = mLockedFileQueues[index]; michael@0: if (lockedFileQueue->mLockedFile == aLockedFile) { michael@0: return lockedFileQueue; michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: FileService::FileStorageInfo::RemoveLockedFileQueue(LockedFile* aLockedFile) michael@0: { michael@0: for (uint32_t index = 0; index < mDelayedEnqueueInfos.Length(); index++) { michael@0: if (mDelayedEnqueueInfos[index].mLockedFile == aLockedFile) { michael@0: NS_ASSERTION(!mDelayedEnqueueInfos[index].mFileHelper, "Should be null!"); michael@0: mDelayedEnqueueInfos.RemoveElementAt(index); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: uint32_t lockedFileCount = mLockedFileQueues.Length(); michael@0: michael@0: // We can't just remove entries from lock hash tables, we have to rebuild michael@0: // them instead. Multiple LockedFile objects may lock the same file michael@0: // (one entry can represent multiple locks). michael@0: michael@0: mFilesReading.Clear(); michael@0: mFilesWriting.Clear(); michael@0: michael@0: for (uint32_t index = 0, count = lockedFileCount; index < count; index++) { michael@0: LockedFile* lockedFile = mLockedFileQueues[index]->mLockedFile; michael@0: if (lockedFile == aLockedFile) { michael@0: NS_ASSERTION(count == lockedFileCount, "More than one match?!"); michael@0: michael@0: mLockedFileQueues.RemoveElementAt(index); michael@0: index--; michael@0: count--; michael@0: michael@0: continue; michael@0: } michael@0: michael@0: const nsAString& fileName = lockedFile->mFileHandle->mFileName; michael@0: michael@0: if (lockedFile->mMode == FileMode::Readwrite) { michael@0: if (!IsFileLockedForWriting(fileName)) { michael@0: LockFileForWriting(fileName); michael@0: } michael@0: } michael@0: else { michael@0: if (!IsFileLockedForReading(fileName)) { michael@0: LockFileForReading(fileName); michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(mLockedFileQueues.Length() == lockedFileCount - 1, michael@0: "Didn't find the locked file we were looking for!"); michael@0: michael@0: nsTArray delayedEnqueueInfos; michael@0: delayedEnqueueInfos.SwapElements(mDelayedEnqueueInfos); michael@0: michael@0: for (uint32_t index = 0; index < delayedEnqueueInfos.Length(); index++) { michael@0: DelayedEnqueueInfo& delayedEnqueueInfo = delayedEnqueueInfos[index]; michael@0: if (NS_FAILED(gInstance->Enqueue(delayedEnqueueInfo.mLockedFile, michael@0: delayedEnqueueInfo.mFileHelper))) { michael@0: NS_WARNING("Enqueue failed!"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: FileService::FileStorageInfo::HasRunningLockedFiles( michael@0: nsIFileStorage* aFileStorage) michael@0: { michael@0: for (uint32_t index = 0; index < mLockedFileQueues.Length(); index++) { michael@0: LockedFile* lockedFile = mLockedFileQueues[index]->mLockedFile; michael@0: if (lockedFile->mFileHandle->mFileStorage == aFileStorage) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: FileService::DelayedEnqueueInfo* michael@0: FileService::FileStorageInfo::CreateDelayedEnqueueInfo(LockedFile* aLockedFile, michael@0: FileHelper* aFileHelper) michael@0: { michael@0: DelayedEnqueueInfo* info = mDelayedEnqueueInfos.AppendElement(); michael@0: info->mLockedFile = aLockedFile; michael@0: info->mFileHelper = aFileHelper; michael@0: return info; michael@0: } michael@0: michael@0: void michael@0: FileService::FileStorageInfo::CollectRunningAndDelayedLockedFiles( michael@0: nsIFileStorage* aFileStorage, michael@0: nsTArray >& aLockedFiles) michael@0: { michael@0: for (uint32_t index = 0; index < mLockedFileQueues.Length(); index++) { michael@0: LockedFile* lockedFile = mLockedFileQueues[index]->mLockedFile; michael@0: if (lockedFile->mFileHandle->mFileStorage == aFileStorage) { michael@0: aLockedFiles.AppendElement(lockedFile); michael@0: } michael@0: } michael@0: michael@0: for (uint32_t index = 0; index < mDelayedEnqueueInfos.Length(); index++) { michael@0: LockedFile* lockedFile = mDelayedEnqueueInfos[index].mLockedFile; michael@0: if (lockedFile->mFileHandle->mFileStorage == aFileStorage) { michael@0: aLockedFiles.AppendElement(lockedFile); michael@0: } michael@0: } michael@0: }