mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-28 11:28:38 +00:00
531 lines
14 KiB
C++
531 lines
14 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "FileService.h"
|
|
|
|
#include "nsIFile.h"
|
|
#include "nsIFileStorage.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIStreamTransportService.h"
|
|
|
|
#include "nsNetCID.h"
|
|
|
|
#include "FileHandle.h"
|
|
#include "FileRequest.h"
|
|
|
|
USING_FILE_NAMESPACE
|
|
|
|
namespace {
|
|
|
|
FileService* gInstance = nullptr;
|
|
bool gShutdown = false;
|
|
|
|
} // anonymous namespace
|
|
|
|
FileService::FileService()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(!gInstance, "More than one instance!");
|
|
}
|
|
|
|
FileService::~FileService()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(!gInstance, "More than one instance!");
|
|
}
|
|
|
|
nsresult
|
|
FileService::Init()
|
|
{
|
|
mFileStorageInfos.Init();
|
|
|
|
nsresult rv;
|
|
|
|
mStreamTransportTarget =
|
|
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIStreamTransportService> sts =
|
|
do_QueryInterface(mStreamTransportTarget);
|
|
|
|
rv = sts->RaiseThreadLimit();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
FileService::Cleanup()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
nsIThread* thread = NS_GetCurrentThread();
|
|
while (mFileStorageInfos.Count()) {
|
|
if (!NS_ProcessNextEvent(thread)) {
|
|
NS_ERROR("Failed to process next event!");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Make sure the service is still accessible while any generated callbacks
|
|
// are processed.
|
|
nsresult rv = NS_ProcessPendingEvents(thread);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!mCompleteCallbacks.IsEmpty()) {
|
|
// Run all callbacks manually now.
|
|
for (PRUint32 index = 0; index < mCompleteCallbacks.Length(); index++) {
|
|
mCompleteCallbacks[index].mCallback->Run();
|
|
}
|
|
mCompleteCallbacks.Clear();
|
|
|
|
// And make sure they get processed.
|
|
rv = NS_ProcessPendingEvents(thread);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
FileService*
|
|
FileService::GetOrCreate()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
if (gShutdown) {
|
|
NS_WARNING("Calling GetOrCreate() after shutdown!");
|
|
return nullptr;
|
|
}
|
|
|
|
if (!gInstance) {
|
|
nsRefPtr<FileService> service(new FileService);
|
|
|
|
nsresult rv = service->Init();
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
nsCOMPtr<nsIObserverService> obs =
|
|
do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
rv = obs->AddObserver(service, "profile-before-change", false);
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
// The observer service now owns us.
|
|
gInstance = service;
|
|
}
|
|
|
|
return gInstance;
|
|
}
|
|
|
|
// static
|
|
FileService*
|
|
FileService::Get()
|
|
{
|
|
// Does not return an owning reference.
|
|
return gInstance;
|
|
}
|
|
|
|
// static
|
|
void
|
|
FileService::Shutdown()
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
gShutdown = true;
|
|
|
|
if (gInstance) {
|
|
if (NS_FAILED(gInstance->Cleanup())) {
|
|
NS_WARNING("Failed to shutdown file service!");
|
|
}
|
|
gInstance = nullptr;
|
|
}
|
|
}
|
|
|
|
// static
|
|
bool
|
|
FileService::IsShuttingDown()
|
|
{
|
|
return gShutdown;
|
|
}
|
|
|
|
nsresult
|
|
FileService::Enqueue(LockedFile* aLockedFile, FileHelper* aFileHelper)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(aLockedFile, "Null pointer!");
|
|
|
|
FileHandle* fileHandle = aLockedFile->mFileHandle;
|
|
|
|
if (fileHandle->mFileStorage->IsStorageInvalidated()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsISupports* storageId = fileHandle->mFileStorage->StorageId();
|
|
const nsAString& fileName = fileHandle->mFileName;
|
|
bool modeIsWrite = aLockedFile->mMode == LockedFile::READ_WRITE;
|
|
|
|
FileStorageInfo* fileStorageInfo;
|
|
if (!mFileStorageInfos.Get(storageId, &fileStorageInfo)) {
|
|
nsAutoPtr<FileStorageInfo> newFileStorageInfo(new FileStorageInfo());
|
|
|
|
mFileStorageInfos.Put(storageId, newFileStorageInfo);
|
|
|
|
fileStorageInfo = newFileStorageInfo.forget();
|
|
}
|
|
|
|
LockedFileQueue* existingLockedFileQueue =
|
|
fileStorageInfo->GetLockedFileQueue(aLockedFile);
|
|
|
|
if (existingLockedFileQueue) {
|
|
existingLockedFileQueue->Enqueue(aFileHelper);
|
|
return NS_OK;
|
|
}
|
|
|
|
bool lockedForReading = fileStorageInfo->IsFileLockedForReading(fileName);
|
|
bool lockedForWriting = fileStorageInfo->IsFileLockedForWriting(fileName);
|
|
|
|
if (modeIsWrite) {
|
|
if (!lockedForWriting) {
|
|
fileStorageInfo->LockFileForWriting(fileName);
|
|
}
|
|
}
|
|
else {
|
|
if (!lockedForReading) {
|
|
fileStorageInfo->LockFileForReading(fileName);
|
|
}
|
|
}
|
|
|
|
if (lockedForWriting || (lockedForReading && modeIsWrite)) {
|
|
fileStorageInfo->CreateDelayedEnqueueInfo(aLockedFile, aFileHelper);
|
|
}
|
|
else {
|
|
LockedFileQueue* lockedFileQueue =
|
|
fileStorageInfo->CreateLockedFileQueue(aLockedFile);
|
|
|
|
if (aFileHelper) {
|
|
// Enqueue() will queue the file helper if there's already something
|
|
// running. That can't fail, so no need to eventually remove
|
|
// fileStorageInfo from the hash table.
|
|
//
|
|
// If the file helper is free to run then AsyncRun() is called on the
|
|
// file helper. AsyncRun() is responsible for calling all necessary
|
|
// callbacks when something fails. We're propagating the error here,
|
|
// however there's no need to eventually remove fileStorageInfo from
|
|
// the hash table. Code behind AsyncRun() will take care of it. The last
|
|
// item in the code path is NotifyLockedFileCompleted() which removes
|
|
// fileStorageInfo from the hash table if there are no locked files for
|
|
// the file storage.
|
|
nsresult rv = lockedFileQueue->Enqueue(aFileHelper);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
FileService::NotifyLockedFileCompleted(LockedFile* aLockedFile)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(aLockedFile, "Null pointer!");
|
|
|
|
FileHandle* fileHandle = aLockedFile->mFileHandle;
|
|
nsISupports* storageId = fileHandle->mFileStorage->StorageId();
|
|
|
|
FileStorageInfo* fileStorageInfo;
|
|
if (!mFileStorageInfos.Get(storageId, &fileStorageInfo)) {
|
|
NS_ERROR("We don't know anyting about this locked file?!");
|
|
return;
|
|
}
|
|
|
|
fileStorageInfo->RemoveLockedFileQueue(aLockedFile);
|
|
|
|
if (!fileStorageInfo->HasRunningLockedFiles()) {
|
|
mFileStorageInfos.Remove(storageId);
|
|
|
|
#ifdef DEBUG
|
|
storageId = nullptr;
|
|
#endif
|
|
|
|
// See if we need to fire any complete callbacks.
|
|
PRUint32 index = 0;
|
|
while (index < mCompleteCallbacks.Length()) {
|
|
if (MaybeFireCallback(mCompleteCallbacks[index])) {
|
|
mCompleteCallbacks.RemoveElementAt(index);
|
|
}
|
|
else {
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
FileService::WaitForAllStoragesToComplete(
|
|
nsTArray<nsCOMPtr<nsIFileStorage> >& aStorages,
|
|
nsIRunnable* aCallback)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(!aStorages.IsEmpty(), "No databases to wait on!");
|
|
NS_ASSERTION(aCallback, "Null pointer!");
|
|
|
|
StoragesCompleteCallback* callback = mCompleteCallbacks.AppendElement();
|
|
callback->mCallback = aCallback;
|
|
callback->mStorages.SwapElements(aStorages);
|
|
|
|
if (MaybeFireCallback(*callback)) {
|
|
mCompleteCallbacks.RemoveElementAt(mCompleteCallbacks.Length() - 1);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
FileService::AbortLockedFilesForStorage(nsIFileStorage* aFileStorage)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(aFileStorage, "Null pointer!");
|
|
|
|
FileStorageInfo* fileStorageInfo;
|
|
if (!mFileStorageInfos.Get(aFileStorage->StorageId(), &fileStorageInfo)) {
|
|
return;
|
|
}
|
|
|
|
nsAutoTArray<nsRefPtr<LockedFile>, 10> lockedFiles;
|
|
fileStorageInfo->CollectRunningAndDelayedLockedFiles(aFileStorage,
|
|
lockedFiles);
|
|
|
|
for (PRUint32 index = 0; index < lockedFiles.Length(); index++) {
|
|
lockedFiles[index]->Abort();
|
|
}
|
|
}
|
|
|
|
bool
|
|
FileService::HasLockedFilesForStorage(nsIFileStorage* aFileStorage)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(aFileStorage, "Null pointer!");
|
|
|
|
FileStorageInfo* fileStorageInfo;
|
|
if (!mFileStorageInfos.Get(aFileStorage->StorageId(), &fileStorageInfo)) {
|
|
return false;
|
|
}
|
|
|
|
return fileStorageInfo->HasRunningLockedFiles(aFileStorage);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS1(FileService, nsIObserver)
|
|
|
|
NS_IMETHODIMP
|
|
FileService::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const PRUnichar* aData)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
NS_ASSERTION(!strcmp(aTopic, "profile-before-change"), "Wrong topic!");
|
|
|
|
Shutdown();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
FileService::MaybeFireCallback(StoragesCompleteCallback& aCallback)
|
|
{
|
|
NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
|
|
|
|
for (PRUint32 index = 0; index < aCallback.mStorages.Length(); index++) {
|
|
if (mFileStorageInfos.Get(aCallback.mStorages[index]->StorageId(),
|
|
nullptr)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
aCallback.mCallback->Run();
|
|
return true;
|
|
}
|
|
|
|
FileService::LockedFileQueue::LockedFileQueue(LockedFile* aLockedFile)
|
|
: mLockedFile(aLockedFile)
|
|
{
|
|
NS_ASSERTION(aLockedFile, "Null pointer!");
|
|
}
|
|
|
|
NS_IMPL_THREADSAFE_ADDREF(FileService::LockedFileQueue)
|
|
NS_IMPL_THREADSAFE_RELEASE(FileService::LockedFileQueue)
|
|
|
|
nsresult
|
|
FileService::LockedFileQueue::Enqueue(FileHelper* aFileHelper)
|
|
{
|
|
mQueue.AppendElement(aFileHelper);
|
|
|
|
nsresult rv;
|
|
if (mLockedFile->mRequestMode == LockedFile::PARALLEL) {
|
|
rv = aFileHelper->AsyncRun(this);
|
|
}
|
|
else {
|
|
rv = ProcessQueue();
|
|
}
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
FileService::LockedFileQueue::OnFileHelperComplete(FileHelper* aFileHelper)
|
|
{
|
|
if (mLockedFile->mRequestMode == LockedFile::PARALLEL) {
|
|
PRInt32 index = mQueue.IndexOf(aFileHelper);
|
|
NS_ASSERTION(index != -1, "We don't know anything about this helper!");
|
|
|
|
mQueue.RemoveElementAt(index);
|
|
}
|
|
else {
|
|
NS_ASSERTION(mCurrentHelper == aFileHelper, "How can this happen?!");
|
|
|
|
mCurrentHelper = nullptr;
|
|
|
|
nsresult rv = ProcessQueue();
|
|
if (NS_FAILED(rv)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
FileService::LockedFileQueue::ProcessQueue()
|
|
{
|
|
if (mQueue.IsEmpty() || mCurrentHelper) {
|
|
return NS_OK;
|
|
}
|
|
|
|
mCurrentHelper = mQueue[0];
|
|
mQueue.RemoveElementAt(0);
|
|
|
|
nsresult rv = mCurrentHelper->AsyncRun(this);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
FileService::LockedFileQueue*
|
|
FileService::FileStorageInfo::CreateLockedFileQueue(LockedFile* aLockedFile)
|
|
{
|
|
nsRefPtr<LockedFileQueue>* lockedFileQueue =
|
|
mLockedFileQueues.AppendElement();
|
|
*lockedFileQueue = new LockedFileQueue(aLockedFile);
|
|
return lockedFileQueue->get();
|
|
}
|
|
|
|
FileService::LockedFileQueue*
|
|
FileService::FileStorageInfo::GetLockedFileQueue(LockedFile* aLockedFile)
|
|
{
|
|
PRUint32 count = mLockedFileQueues.Length();
|
|
for (PRUint32 index = 0; index < count; index++) {
|
|
nsRefPtr<LockedFileQueue>& lockedFileQueue = mLockedFileQueues[index];
|
|
if (lockedFileQueue->mLockedFile == aLockedFile) {
|
|
return lockedFileQueue;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
FileService::FileStorageInfo::RemoveLockedFileQueue(LockedFile* aLockedFile)
|
|
{
|
|
PRUint32 lockedFileCount = mLockedFileQueues.Length();
|
|
|
|
// We can't just remove entries from lock hash tables, we have to rebuild
|
|
// them instead. Multiple LockedFile objects may lock the same file
|
|
// (one entry can represent multiple locks).
|
|
|
|
mFilesReading.Clear();
|
|
mFilesWriting.Clear();
|
|
|
|
for (PRUint32 index = 0, count = lockedFileCount; index < count; index++) {
|
|
LockedFile* lockedFile = mLockedFileQueues[index]->mLockedFile;
|
|
if (lockedFile == aLockedFile) {
|
|
NS_ASSERTION(count == lockedFileCount, "More than one match?!");
|
|
|
|
mLockedFileQueues.RemoveElementAt(index);
|
|
index--;
|
|
count--;
|
|
|
|
continue;
|
|
}
|
|
|
|
const nsAString& fileName = lockedFile->mFileHandle->mFileName;
|
|
|
|
if (lockedFile->mMode == LockedFile::READ_WRITE) {
|
|
if (!IsFileLockedForWriting(fileName)) {
|
|
LockFileForWriting(fileName);
|
|
}
|
|
}
|
|
else {
|
|
if (!IsFileLockedForReading(fileName)) {
|
|
LockFileForReading(fileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_ASSERTION(mLockedFileQueues.Length() == lockedFileCount - 1,
|
|
"Didn't find the locked file we were looking for!");
|
|
|
|
nsTArray<DelayedEnqueueInfo> delayedEnqueueInfos;
|
|
delayedEnqueueInfos.SwapElements(mDelayedEnqueueInfos);
|
|
|
|
for (PRUint32 index = 0; index < delayedEnqueueInfos.Length(); index++) {
|
|
DelayedEnqueueInfo& delayedEnqueueInfo = delayedEnqueueInfos[index];
|
|
if (NS_FAILED(gInstance->Enqueue(delayedEnqueueInfo.mLockedFile,
|
|
delayedEnqueueInfo.mFileHelper))) {
|
|
NS_WARNING("Enqueue failed!");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
FileService::FileStorageInfo::HasRunningLockedFiles(
|
|
nsIFileStorage* aFileStorage)
|
|
{
|
|
for (PRUint32 index = 0; index < mLockedFileQueues.Length(); index++) {
|
|
LockedFile* lockedFile = mLockedFileQueues[index]->mLockedFile;
|
|
if (lockedFile->mFileHandle->mFileStorage == aFileStorage) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FileService::DelayedEnqueueInfo*
|
|
FileService::FileStorageInfo::CreateDelayedEnqueueInfo(LockedFile* aLockedFile,
|
|
FileHelper* aFileHelper)
|
|
{
|
|
DelayedEnqueueInfo* info = mDelayedEnqueueInfos.AppendElement();
|
|
info->mLockedFile = aLockedFile;
|
|
info->mFileHelper = aFileHelper;
|
|
return info;
|
|
}
|
|
|
|
void
|
|
FileService::FileStorageInfo::CollectRunningAndDelayedLockedFiles(
|
|
nsIFileStorage* aFileStorage,
|
|
nsTArray<nsRefPtr<LockedFile> >& aLockedFiles)
|
|
{
|
|
for (PRUint32 index = 0; index < mLockedFileQueues.Length(); index++) {
|
|
LockedFile* lockedFile = mLockedFileQueues[index]->mLockedFile;
|
|
if (lockedFile->mFileHandle->mFileStorage == aFileStorage) {
|
|
aLockedFiles.AppendElement(lockedFile);
|
|
}
|
|
}
|
|
|
|
for (PRUint32 index = 0; index < mDelayedEnqueueInfos.Length(); index++) {
|
|
LockedFile* lockedFile = mDelayedEnqueueInfos[index].mLockedFile;
|
|
if (lockedFile->mFileHandle->mFileStorage == aFileStorage) {
|
|
aLockedFiles.AppendElement(lockedFile);
|
|
}
|
|
}
|
|
}
|