diff options
Diffstat (limited to 'netwerk/cache/nsDeleteDir.cpp')
-rw-r--r-- | netwerk/cache/nsDeleteDir.cpp | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/netwerk/cache/nsDeleteDir.cpp b/netwerk/cache/nsDeleteDir.cpp new file mode 100644 index 0000000000..f99896b721 --- /dev/null +++ b/netwerk/cache/nsDeleteDir.cpp @@ -0,0 +1,362 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsDeleteDir.h" +#include "nsIFile.h" +#include "nsString.h" +#include "mozilla/Telemetry.h" +#include "nsITimer.h" +#include "nsThreadUtils.h" +#include "nsISupportsPriority.h" +#include "nsCacheUtils.h" +#include "prtime.h" +#include <time.h> + +using namespace mozilla; + +class nsBlockOnBackgroundThreadEvent : public Runnable { + public: + nsBlockOnBackgroundThreadEvent() + : mozilla::Runnable("nsBlockOnBackgroundThreadEvent") {} + NS_IMETHOD Run() override { + MutexAutoLock lock(nsDeleteDir::gInstance->mLock); + nsDeleteDir::gInstance->mNotified = true; + nsDeleteDir::gInstance->mCondVar.Notify(); + return NS_OK; + } +}; + +nsDeleteDir* nsDeleteDir::gInstance = nullptr; + +nsDeleteDir::nsDeleteDir() + : mLock("nsDeleteDir.mLock"), + mCondVar(mLock, "nsDeleteDir.mCondVar"), + mNotified(false), + mShutdownPending(false), + mStopDeleting(false) { + NS_ASSERTION(gInstance == nullptr, "multiple nsCacheService instances!"); +} + +nsDeleteDir::~nsDeleteDir() { gInstance = nullptr; } + +nsresult nsDeleteDir::Init() { + if (gInstance) return NS_ERROR_ALREADY_INITIALIZED; + + gInstance = new nsDeleteDir(); + return NS_OK; +} + +nsresult nsDeleteDir::Shutdown(bool finishDeleting) { + if (!gInstance) return NS_ERROR_NOT_INITIALIZED; + + nsCOMArray<nsIFile> dirsToRemove; + nsCOMPtr<nsISerialEventTarget> eventTarget; + { + MutexAutoLock lock(gInstance->mLock); + NS_ASSERTION(!gInstance->mShutdownPending, + "Unexpected state in nsDeleteDir::Shutdown()"); + gInstance->mShutdownPending = true; + + if (!finishDeleting) gInstance->mStopDeleting = true; + + // remove all pending timers + for (int32_t i = gInstance->mTimers.Count(); i > 0; i--) { + nsCOMPtr<nsITimer> timer = gInstance->mTimers[i - 1]; + gInstance->mTimers.RemoveObjectAt(i - 1); + + nsCOMArray<nsIFile>* arg; + timer->GetClosure((reinterpret_cast<void**>(&arg))); + timer->Cancel(); + + if (finishDeleting) dirsToRemove.AppendObjects(*arg); + + // delete argument passed to the timer + delete arg; + } + + eventTarget.swap(gInstance->mBackgroundET); + if (eventTarget) { + // dispatch event and wait for it to run and notify us, so we know thread + // has completed all work and can be shutdown + nsCOMPtr<nsIRunnable> event = new nsBlockOnBackgroundThreadEvent(); + nsresult rv = eventTarget->Dispatch(event, NS_DISPATCH_EVENT_MAY_BLOCK); + if (NS_FAILED(rv)) { + NS_WARNING("Failed dispatching block-event"); + return NS_ERROR_UNEXPECTED; + } + + gInstance->mNotified = false; + while (!gInstance->mNotified) { + gInstance->mCondVar.Wait(); + } + } + } + + delete gInstance; + + for (int32_t i = 0; i < dirsToRemove.Count(); i++) + dirsToRemove[i]->Remove(true); + + return NS_OK; +} + +nsresult nsDeleteDir::InitThread() { + if (mBackgroundET) return NS_OK; + + nsresult rv = NS_CreateBackgroundTaskQueue("Cache Deleter", + getter_AddRefs(mBackgroundET)); + if (NS_FAILED(rv)) { + NS_WARNING("Can't create background task queue"); + return rv; + } + + return NS_OK; +} + +void nsDeleteDir::DestroyThread() { + if (!mBackgroundET) return; + + if (mTimers.Count()) + // more work to do, so don't delete thread. + return; + + mBackgroundET = nullptr; +} + +void nsDeleteDir::TimerCallback(nsITimer* aTimer, void* arg) { + Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR> timer; + { + MutexAutoLock lock(gInstance->mLock); + + int32_t idx = gInstance->mTimers.IndexOf(aTimer); + if (idx == -1) { + // Timer was canceled and removed during shutdown. + return; + } + + gInstance->mTimers.RemoveObjectAt(idx); + } + + UniquePtr<nsCOMArray<nsIFile>> dirList; + dirList.reset(static_cast<nsCOMArray<nsIFile>*>(arg)); + + bool shuttingDown = false; + + // Intentional extra braces to control variable sope. + { + // Low IO priority can only be set when running in the context of the + // current thread. So this shouldn't be moved to where we set the priority + // of the Cache deleter thread using the nsThread's NSPR priority constants. + nsAutoLowPriorityIO autoLowPriority; + for (int32_t i = 0; i < dirList->Count() && !shuttingDown; i++) { + gInstance->RemoveDir((*dirList)[i], &shuttingDown); + } + } + + { + MutexAutoLock lock(gInstance->mLock); + gInstance->DestroyThread(); + } +} + +nsresult nsDeleteDir::DeleteDir(nsIFile* dirIn, bool moveToTrash, + uint32_t delay) { + Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_TRASHRENAME> timer; + + if (!gInstance) return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + nsCOMPtr<nsIFile> trash, dir; + + // Need to make a clone of this since we don't want to modify the input + // file object. + rv = dirIn->Clone(getter_AddRefs(dir)); + if (NS_FAILED(rv)) return rv; + + if (moveToTrash) { + rv = GetTrashDir(dir, &trash); + if (NS_FAILED(rv)) return rv; + nsAutoCString origLeaf; + rv = trash->GetNativeLeafName(origLeaf); + if (NS_FAILED(rv)) return rv; + + // Append random number to the trash directory and check if it exists. + srand(static_cast<unsigned>(PR_Now())); + nsAutoCString leaf; + for (int32_t i = 0; i < 10; i++) { + leaf = origLeaf; + leaf.AppendInt(rand()); + rv = trash->SetNativeLeafName(leaf); + if (NS_FAILED(rv)) return rv; + + bool exists; + if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) { + break; + } + + leaf.Truncate(); + } + + // Fail if we didn't find unused trash directory within the limit + if (!leaf.Length()) return NS_ERROR_FAILURE; + +#if defined(MOZ_WIDGET_ANDROID) + nsCOMPtr<nsIFile> parent; + rv = trash->GetParent(getter_AddRefs(parent)); + if (NS_FAILED(rv)) return rv; + rv = dir->MoveToNative(parent, leaf); +#else + // Important: must rename directory w/o changing parent directory: else on + // NTFS we'll wait (with cache lock) while nsIFile's ACL reset walks file + // tree: was hanging GUI for *minutes* on large cache dirs. + rv = dir->MoveToNative(nullptr, leaf); +#endif + if (NS_FAILED(rv)) return rv; + } else { + // we want to pass a clone of the original off to the worker thread. + trash.swap(dir); + } + + UniquePtr<nsCOMArray<nsIFile>> arg(new nsCOMArray<nsIFile>); + arg->AppendObject(trash); + + rv = gInstance->PostTimer(arg.get(), delay); + if (NS_FAILED(rv)) return rv; + + Unused << arg.release(); + return NS_OK; +} + +nsresult nsDeleteDir::GetTrashDir(nsIFile* target, nsCOMPtr<nsIFile>* result) { + nsresult rv; +#if defined(MOZ_WIDGET_ANDROID) + // Try to use the app cache folder for cache trash on Android + char* cachePath = getenv("CACHE_DIRECTORY"); + if (cachePath) { + rv = NS_NewNativeLocalFile(nsDependentCString(cachePath), true, + getter_AddRefs(*result)); + if (NS_FAILED(rv)) return rv; + + // Add a sub folder with the cache folder name + nsAutoCString leaf; + rv = target->GetNativeLeafName(leaf); + (*result)->AppendNative(leaf); + } else +#endif + { + rv = target->Clone(getter_AddRefs(*result)); + } + if (NS_FAILED(rv)) return rv; + + nsAutoCString leaf; + rv = (*result)->GetNativeLeafName(leaf); + if (NS_FAILED(rv)) return rv; + leaf.AppendLiteral(".Trash"); + + return (*result)->SetNativeLeafName(leaf); +} + +nsresult nsDeleteDir::RemoveOldTrashes(nsIFile* cacheDir) { + if (!gInstance) return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + + nsCOMPtr<nsIFile> trash; + rv = GetTrashDir(cacheDir, &trash); + if (NS_FAILED(rv)) return rv; + + nsAutoString trashName; + rv = trash->GetLeafName(trashName); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIFile> parent; +#if defined(MOZ_WIDGET_ANDROID) + rv = trash->GetParent(getter_AddRefs(parent)); +#else + rv = cacheDir->GetParent(getter_AddRefs(parent)); +#endif + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIDirectoryEnumerator> iter; + rv = parent->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) return rv; + + UniquePtr<nsCOMArray<nsIFile>> dirList; + + nsCOMPtr<nsIFile> file; + while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file))) && file) { + nsAutoString leafName; + rv = file->GetLeafName(leafName); + if (NS_FAILED(rv)) continue; + + // match all names that begin with the trash name (i.e. "Cache.Trash") + if (Substring(leafName, 0, trashName.Length()).Equals(trashName)) { + if (!dirList) dirList = MakeUnique<nsCOMArray<nsIFile>>(); + dirList->AppendObject(file); + } + } + + if (dirList) { + rv = gInstance->PostTimer(dirList.get(), 90000); + if (NS_FAILED(rv)) return rv; + + Unused << dirList.release(); + } + + return NS_OK; +} + +nsresult nsDeleteDir::PostTimer(void* arg, uint32_t delay) { + nsresult rv; + + MutexAutoLock lock(mLock); + + rv = InitThread(); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsITimer> timer; + rv = NS_NewTimerWithFuncCallback(getter_AddRefs(timer), TimerCallback, arg, + delay, nsITimer::TYPE_ONE_SHOT, + "nsDeleteDir::PostTimer", mBackgroundET); + if (NS_FAILED(rv)) return rv; + + mTimers.AppendObject(timer); + return NS_OK; +} + +nsresult nsDeleteDir::RemoveDir(nsIFile* file, bool* stopDeleting) { + nsresult rv; + bool isLink; + + rv = file->IsSymlink(&isLink); + if (NS_FAILED(rv) || isLink) return NS_ERROR_UNEXPECTED; + + bool isDir; + rv = file->IsDirectory(&isDir); + if (NS_FAILED(rv)) return rv; + + if (isDir) { + nsCOMPtr<nsIDirectoryEnumerator> iter; + rv = file->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIFile> file2; + while (NS_SUCCEEDED(iter->GetNextFile(getter_AddRefs(file2))) && file2) { + RemoveDir(file2, stopDeleting); + // No check for errors to remove as much as possible + + if (*stopDeleting) return NS_OK; + } + } + + file->Remove(false); + // No check for errors to remove as much as possible + + MutexAutoLock lock(mLock); + if (mStopDeleting) *stopDeleting = true; + + return NS_OK; +} |