1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsDeleteDir.h"
10 #include "mozilla/Telemetry.h"
12 #include "nsISimpleEnumerator.h"
13 #include "nsAutoPtr.h"
14 #include "nsThreadUtils.h"
15 #include "nsISupportsPriority.h"
16 #include "nsCacheUtils.h"
20 using namespace mozilla
;
22 class nsBlockOnBackgroundThreadEvent
: public nsRunnable
{
24 nsBlockOnBackgroundThreadEvent() {}
27 MutexAutoLock
lock(nsDeleteDir::gInstance
->mLock
);
28 nsDeleteDir::gInstance
->mCondVar
.Notify();
34 nsDeleteDir
* nsDeleteDir::gInstance
= nullptr;
36 nsDeleteDir::nsDeleteDir()
37 : mLock("nsDeleteDir.mLock"),
38 mCondVar(mLock
, "nsDeleteDir.mCondVar"),
39 mShutdownPending(false),
42 NS_ASSERTION(gInstance
==nullptr, "multiple nsCacheService instances!");
45 nsDeleteDir::~nsDeleteDir()
54 return NS_ERROR_ALREADY_INITIALIZED
;
56 gInstance
= new nsDeleteDir();
61 nsDeleteDir::Shutdown(bool finishDeleting
)
64 return NS_ERROR_NOT_INITIALIZED
;
66 nsCOMArray
<nsIFile
> dirsToRemove
;
67 nsCOMPtr
<nsIThread
> thread
;
69 MutexAutoLock
lock(gInstance
->mLock
);
70 NS_ASSERTION(!gInstance
->mShutdownPending
,
71 "Unexpected state in nsDeleteDir::Shutdown()");
72 gInstance
->mShutdownPending
= true;
75 gInstance
->mStopDeleting
= true;
77 // remove all pending timers
78 for (int32_t i
= gInstance
->mTimers
.Count(); i
> 0; i
--) {
79 nsCOMPtr
<nsITimer
> timer
= gInstance
->mTimers
[i
-1];
80 gInstance
->mTimers
.RemoveObjectAt(i
-1);
83 nsCOMArray
<nsIFile
> *arg
;
84 timer
->GetClosure((reinterpret_cast<void**>(&arg
)));
87 dirsToRemove
.AppendObjects(*arg
);
89 // delete argument passed to the timer
93 thread
.swap(gInstance
->mThread
);
95 // dispatch event and wait for it to run and notify us, so we know thread
96 // has completed all work and can be shutdown
97 nsCOMPtr
<nsIRunnable
> event
= new nsBlockOnBackgroundThreadEvent();
98 nsresult rv
= thread
->Dispatch(event
, NS_DISPATCH_NORMAL
);
100 NS_WARNING("Failed dispatching block-event");
101 return NS_ERROR_UNEXPECTED
;
104 rv
= gInstance
->mCondVar
.Wait();
105 nsShutdownThread::BlockingShutdown(thread
);
111 for (int32_t i
= 0; i
< dirsToRemove
.Count(); i
++)
112 dirsToRemove
[i
]->Remove(true);
118 nsDeleteDir::InitThread()
123 nsresult rv
= NS_NewNamedThread("Cache Deleter", getter_AddRefs(mThread
));
125 NS_WARNING("Can't create background thread");
129 nsCOMPtr
<nsISupportsPriority
> p
= do_QueryInterface(mThread
);
131 p
->SetPriority(nsISupportsPriority::PRIORITY_LOWEST
);
137 nsDeleteDir::DestroyThread()
143 // more work to do, so don't delete thread.
146 nsShutdownThread::Shutdown(mThread
);
151 nsDeleteDir::TimerCallback(nsITimer
*aTimer
, void *arg
)
153 Telemetry::AutoTimer
<Telemetry::NETWORK_DISK_CACHE_DELETEDIR
> timer
;
155 MutexAutoLock
lock(gInstance
->mLock
);
157 int32_t idx
= gInstance
->mTimers
.IndexOf(aTimer
);
159 // Timer was canceled and removed during shutdown.
163 gInstance
->mTimers
.RemoveObjectAt(idx
);
166 nsAutoPtr
<nsCOMArray
<nsIFile
> > dirList
;
167 dirList
= static_cast<nsCOMArray
<nsIFile
> *>(arg
);
169 bool shuttingDown
= false;
171 // Intentional extra braces to control variable sope.
173 // Low IO priority can only be set when running in the context of the
174 // current thread. So this shouldn't be moved to where we set the priority
175 // of the Cache deleter thread using the nsThread's NSPR priority constants.
176 nsAutoLowPriorityIO autoLowPriority
;
177 for (int32_t i
= 0; i
< dirList
->Count() && !shuttingDown
; i
++) {
178 gInstance
->RemoveDir((*dirList
)[i
], &shuttingDown
);
183 MutexAutoLock
lock(gInstance
->mLock
);
184 gInstance
->DestroyThread();
189 nsDeleteDir::DeleteDir(nsIFile
*dirIn
, bool moveToTrash
, uint32_t delay
)
191 Telemetry::AutoTimer
<Telemetry::NETWORK_DISK_CACHE_TRASHRENAME
> timer
;
194 return NS_ERROR_NOT_INITIALIZED
;
197 nsCOMPtr
<nsIFile
> trash
, dir
;
199 // Need to make a clone of this since we don't want to modify the input
201 rv
= dirIn
->Clone(getter_AddRefs(dir
));
206 rv
= GetTrashDir(dir
, &trash
);
209 nsAutoCString origLeaf
;
210 rv
= trash
->GetNativeLeafName(origLeaf
);
214 // Append random number to the trash directory and check if it exists.
215 srand(static_cast<unsigned>(PR_Now()));
217 for (int32_t i
= 0; i
< 10; i
++) {
219 leaf
.AppendInt(rand());
220 rv
= trash
->SetNativeLeafName(leaf
);
225 if (NS_SUCCEEDED(trash
->Exists(&exists
)) && !exists
) {
232 // Fail if we didn't find unused trash directory within the limit
234 return NS_ERROR_FAILURE
;
236 #if defined(MOZ_WIDGET_ANDROID)
237 nsCOMPtr
<nsIFile
> parent
;
238 rv
= trash
->GetParent(getter_AddRefs(parent
));
241 rv
= dir
->MoveToNative(parent
, leaf
);
243 // Important: must rename directory w/o changing parent directory: else on
244 // NTFS we'll wait (with cache lock) while nsIFile's ACL reset walks file
245 // tree: was hanging GUI for *minutes* on large cache dirs.
246 rv
= dir
->MoveToNative(nullptr, leaf
);
251 // we want to pass a clone of the original off to the worker thread.
255 nsAutoPtr
<nsCOMArray
<nsIFile
> > arg(new nsCOMArray
<nsIFile
>);
256 arg
->AppendObject(trash
);
258 rv
= gInstance
->PostTimer(arg
, delay
);
267 nsDeleteDir::GetTrashDir(nsIFile
*target
, nsCOMPtr
<nsIFile
> *result
)
270 #if defined(MOZ_WIDGET_ANDROID)
271 // Try to use the app cache folder for cache trash on Android
272 char* cachePath
= getenv("CACHE_DIRECTORY");
274 rv
= NS_NewNativeLocalFile(nsDependentCString(cachePath
),
275 true, getter_AddRefs(*result
));
279 // Add a sub folder with the cache folder name
281 rv
= target
->GetNativeLeafName(leaf
);
282 (*result
)->AppendNative(leaf
);
286 rv
= target
->Clone(getter_AddRefs(*result
));
292 rv
= (*result
)->GetNativeLeafName(leaf
);
295 leaf
.AppendLiteral(".Trash");
297 return (*result
)->SetNativeLeafName(leaf
);
301 nsDeleteDir::RemoveOldTrashes(nsIFile
*cacheDir
)
304 return NS_ERROR_NOT_INITIALIZED
;
308 nsCOMPtr
<nsIFile
> trash
;
309 rv
= GetTrashDir(cacheDir
, &trash
);
313 nsAutoString trashName
;
314 rv
= trash
->GetLeafName(trashName
);
318 nsCOMPtr
<nsIFile
> parent
;
319 #if defined(MOZ_WIDGET_ANDROID)
320 rv
= trash
->GetParent(getter_AddRefs(parent
));
322 rv
= cacheDir
->GetParent(getter_AddRefs(parent
));
327 nsCOMPtr
<nsISimpleEnumerator
> iter
;
328 rv
= parent
->GetDirectoryEntries(getter_AddRefs(iter
));
333 nsCOMPtr
<nsISupports
> elem
;
334 nsAutoPtr
<nsCOMArray
<nsIFile
> > dirList
;
336 while (NS_SUCCEEDED(iter
->HasMoreElements(&more
)) && more
) {
337 rv
= iter
->GetNext(getter_AddRefs(elem
));
341 nsCOMPtr
<nsIFile
> file
= do_QueryInterface(elem
);
345 nsAutoString leafName
;
346 rv
= file
->GetLeafName(leafName
);
350 // match all names that begin with the trash name (i.e. "Cache.Trash")
351 if (Substring(leafName
, 0, trashName
.Length()).Equals(trashName
)) {
353 dirList
= new nsCOMArray
<nsIFile
>;
354 dirList
->AppendObject(file
);
359 rv
= gInstance
->PostTimer(dirList
, 90000);
370 nsDeleteDir::PostTimer(void *arg
, uint32_t delay
)
374 nsCOMPtr
<nsITimer
> timer
= do_CreateInstance("@mozilla.org/timer;1", &rv
);
376 return NS_ERROR_UNEXPECTED
;
378 MutexAutoLock
lock(mLock
);
384 rv
= timer
->SetTarget(mThread
);
388 rv
= timer
->InitWithFuncCallback(TimerCallback
, arg
, delay
,
389 nsITimer::TYPE_ONE_SHOT
);
393 mTimers
.AppendObject(timer
);
398 nsDeleteDir::RemoveDir(nsIFile
*file
, bool *stopDeleting
)
403 rv
= file
->IsSymlink(&isLink
);
404 if (NS_FAILED(rv
) || isLink
)
405 return NS_ERROR_UNEXPECTED
;
408 rv
= file
->IsDirectory(&isDir
);
413 nsCOMPtr
<nsISimpleEnumerator
> iter
;
414 rv
= file
->GetDirectoryEntries(getter_AddRefs(iter
));
419 nsCOMPtr
<nsISupports
> elem
;
420 while (NS_SUCCEEDED(iter
->HasMoreElements(&more
)) && more
) {
421 rv
= iter
->GetNext(getter_AddRefs(elem
));
423 NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
427 nsCOMPtr
<nsIFile
> file2
= do_QueryInterface(elem
);
429 NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
433 RemoveDir(file2
, stopDeleting
);
434 // No check for errors to remove as much as possible
442 // No check for errors to remove as much as possible
444 MutexAutoLock
lock(mLock
);
446 *stopDeleting
= true;