diff -r 000000000000 -r 6474c204b198 netwerk/cache/nsDeleteDir.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/netwerk/cache/nsDeleteDir.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,456 @@ +/* -*- 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 "nsISimpleEnumerator.h" +#include "nsAutoPtr.h" +#include "nsThreadUtils.h" +#include "nsISupportsPriority.h" +#include "nsCacheUtils.h" +#include "prtime.h" +#include + +using namespace mozilla; + +class nsBlockOnBackgroundThreadEvent : public nsRunnable { +public: + nsBlockOnBackgroundThreadEvent() {} + NS_IMETHOD Run() + { + MutexAutoLock lock(nsDeleteDir::gInstance->mLock); + nsDeleteDir::gInstance->mCondVar.Notify(); + return NS_OK; + } +}; + + +nsDeleteDir * nsDeleteDir::gInstance = nullptr; + +nsDeleteDir::nsDeleteDir() + : mLock("nsDeleteDir.mLock"), + mCondVar(mLock, "nsDeleteDir.mCondVar"), + 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 dirsToRemove; + nsCOMPtr thread; + { + 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 timer = gInstance->mTimers[i-1]; + gInstance->mTimers.RemoveObjectAt(i-1); + timer->Cancel(); + + nsCOMArray *arg; + timer->GetClosure((reinterpret_cast(&arg))); + + if (finishDeleting) + dirsToRemove.AppendObjects(*arg); + + // delete argument passed to the timer + delete arg; + } + + thread.swap(gInstance->mThread); + if (thread) { + // dispatch event and wait for it to run and notify us, so we know thread + // has completed all work and can be shutdown + nsCOMPtr event = new nsBlockOnBackgroundThreadEvent(); + nsresult rv = thread->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("Failed dispatching block-event"); + return NS_ERROR_UNEXPECTED; + } + + rv = gInstance->mCondVar.Wait(); + nsShutdownThread::BlockingShutdown(thread); + } + } + + delete gInstance; + + for (int32_t i = 0; i < dirsToRemove.Count(); i++) + dirsToRemove[i]->Remove(true); + + return NS_OK; +} + +nsresult +nsDeleteDir::InitThread() +{ + if (mThread) + return NS_OK; + + nsresult rv = NS_NewNamedThread("Cache Deleter", getter_AddRefs(mThread)); + if (NS_FAILED(rv)) { + NS_WARNING("Can't create background thread"); + return rv; + } + + nsCOMPtr p = do_QueryInterface(mThread); + if (p) { + p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST); + } + return NS_OK; +} + +void +nsDeleteDir::DestroyThread() +{ + if (!mThread) + return; + + if (mTimers.Count()) + // more work to do, so don't delete thread. + return; + + nsShutdownThread::Shutdown(mThread); + mThread = nullptr; +} + +void +nsDeleteDir::TimerCallback(nsITimer *aTimer, void *arg) +{ + Telemetry::AutoTimer 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); + } + + nsAutoPtr > dirList; + dirList = static_cast *>(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 timer; + + if (!gInstance) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + nsCOMPtr 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(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 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); + } + + nsAutoPtr > arg(new nsCOMArray); + arg->AppendObject(trash); + + rv = gInstance->PostTimer(arg, delay); + if (NS_FAILED(rv)) + return rv; + + arg.forget(); + return NS_OK; +} + +nsresult +nsDeleteDir::GetTrashDir(nsIFile *target, nsCOMPtr *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; + + static bool firstRun = true; + + if (!firstRun) + return NS_OK; + + firstRun = false; + + nsCOMPtr trash; + rv = GetTrashDir(cacheDir, &trash); + if (NS_FAILED(rv)) + return rv; + + nsAutoString trashName; + rv = trash->GetLeafName(trashName); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr 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 iter; + rv = parent->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) + return rv; + + bool more; + nsCOMPtr elem; + nsAutoPtr > dirList; + + while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) { + rv = iter->GetNext(getter_AddRefs(elem)); + if (NS_FAILED(rv)) + continue; + + nsCOMPtr file = do_QueryInterface(elem); + if (!file) + continue; + + 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 = new nsCOMArray; + dirList->AppendObject(file); + } + } + + if (dirList) { + rv = gInstance->PostTimer(dirList, 90000); + if (NS_FAILED(rv)) + return rv; + + dirList.forget(); + } + + return NS_OK; +} + +nsresult +nsDeleteDir::PostTimer(void *arg, uint32_t delay) +{ + nsresult rv; + + nsCOMPtr timer = do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_FAILED(rv)) + return NS_ERROR_UNEXPECTED; + + MutexAutoLock lock(mLock); + + rv = InitThread(); + if (NS_FAILED(rv)) + return rv; + + rv = timer->SetTarget(mThread); + if (NS_FAILED(rv)) + return rv; + + rv = timer->InitWithFuncCallback(TimerCallback, arg, delay, + nsITimer::TYPE_ONE_SHOT); + 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 iter; + rv = file->GetDirectoryEntries(getter_AddRefs(iter)); + if (NS_FAILED(rv)) + return rv; + + bool more; + nsCOMPtr elem; + while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) { + rv = iter->GetNext(getter_AddRefs(elem)); + if (NS_FAILED(rv)) { + NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir"); + continue; + } + + nsCOMPtr file2 = do_QueryInterface(elem); + if (!file2) { + NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir"); + continue; + } + + 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; +}