Bumping gaia.json for 2 gaia revision(s) a=gaia-bump
[gecko.git] / netwerk / cache / nsDeleteDir.cpp
blob4572d30e1606c3eb2c39ab890b879a4c170bff1d
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"
8 #include "nsIFile.h"
9 #include "nsString.h"
10 #include "mozilla/Telemetry.h"
11 #include "nsITimer.h"
12 #include "nsISimpleEnumerator.h"
13 #include "nsAutoPtr.h"
14 #include "nsThreadUtils.h"
15 #include "nsISupportsPriority.h"
16 #include "nsCacheUtils.h"
17 #include "prtime.h"
18 #include <time.h>
20 using namespace mozilla;
22 class nsBlockOnBackgroundThreadEvent : public nsRunnable {
23 public:
24 nsBlockOnBackgroundThreadEvent() {}
25 NS_IMETHOD Run()
27 MutexAutoLock lock(nsDeleteDir::gInstance->mLock);
28 nsDeleteDir::gInstance->mCondVar.Notify();
29 return NS_OK;
34 nsDeleteDir * nsDeleteDir::gInstance = nullptr;
36 nsDeleteDir::nsDeleteDir()
37 : mLock("nsDeleteDir.mLock"),
38 mCondVar(mLock, "nsDeleteDir.mCondVar"),
39 mShutdownPending(false),
40 mStopDeleting(false)
42 NS_ASSERTION(gInstance==nullptr, "multiple nsCacheService instances!");
45 nsDeleteDir::~nsDeleteDir()
47 gInstance = nullptr;
50 nsresult
51 nsDeleteDir::Init()
53 if (gInstance)
54 return NS_ERROR_ALREADY_INITIALIZED;
56 gInstance = new nsDeleteDir();
57 return NS_OK;
60 nsresult
61 nsDeleteDir::Shutdown(bool finishDeleting)
63 if (!gInstance)
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;
74 if (!finishDeleting)
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);
81 timer->Cancel();
83 nsCOMArray<nsIFile> *arg;
84 timer->GetClosure((reinterpret_cast<void**>(&arg)));
86 if (finishDeleting)
87 dirsToRemove.AppendObjects(*arg);
89 // delete argument passed to the timer
90 delete arg;
93 thread.swap(gInstance->mThread);
94 if (thread) {
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);
99 if (NS_FAILED(rv)) {
100 NS_WARNING("Failed dispatching block-event");
101 return NS_ERROR_UNEXPECTED;
104 rv = gInstance->mCondVar.Wait();
105 nsShutdownThread::BlockingShutdown(thread);
109 delete gInstance;
111 for (int32_t i = 0; i < dirsToRemove.Count(); i++)
112 dirsToRemove[i]->Remove(true);
114 return NS_OK;
117 nsresult
118 nsDeleteDir::InitThread()
120 if (mThread)
121 return NS_OK;
123 nsresult rv = NS_NewNamedThread("Cache Deleter", getter_AddRefs(mThread));
124 if (NS_FAILED(rv)) {
125 NS_WARNING("Can't create background thread");
126 return rv;
129 nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mThread);
130 if (p) {
131 p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
133 return NS_OK;
136 void
137 nsDeleteDir::DestroyThread()
139 if (!mThread)
140 return;
142 if (mTimers.Count())
143 // more work to do, so don't delete thread.
144 return;
146 nsShutdownThread::Shutdown(mThread);
147 mThread = nullptr;
150 void
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);
158 if (idx == -1) {
159 // Timer was canceled and removed during shutdown.
160 return;
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();
188 nsresult
189 nsDeleteDir::DeleteDir(nsIFile *dirIn, bool moveToTrash, uint32_t delay)
191 Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_TRASHRENAME> timer;
193 if (!gInstance)
194 return NS_ERROR_NOT_INITIALIZED;
196 nsresult rv;
197 nsCOMPtr<nsIFile> trash, dir;
199 // Need to make a clone of this since we don't want to modify the input
200 // file object.
201 rv = dirIn->Clone(getter_AddRefs(dir));
202 if (NS_FAILED(rv))
203 return rv;
205 if (moveToTrash) {
206 rv = GetTrashDir(dir, &trash);
207 if (NS_FAILED(rv))
208 return rv;
209 nsAutoCString origLeaf;
210 rv = trash->GetNativeLeafName(origLeaf);
211 if (NS_FAILED(rv))
212 return rv;
214 // Append random number to the trash directory and check if it exists.
215 srand(static_cast<unsigned>(PR_Now()));
216 nsAutoCString leaf;
217 for (int32_t i = 0; i < 10; i++) {
218 leaf = origLeaf;
219 leaf.AppendInt(rand());
220 rv = trash->SetNativeLeafName(leaf);
221 if (NS_FAILED(rv))
222 return rv;
224 bool exists;
225 if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
226 break;
229 leaf.Truncate();
232 // Fail if we didn't find unused trash directory within the limit
233 if (!leaf.Length())
234 return NS_ERROR_FAILURE;
236 #if defined(MOZ_WIDGET_ANDROID)
237 nsCOMPtr<nsIFile> parent;
238 rv = trash->GetParent(getter_AddRefs(parent));
239 if (NS_FAILED(rv))
240 return rv;
241 rv = dir->MoveToNative(parent, leaf);
242 #else
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);
247 #endif
248 if (NS_FAILED(rv))
249 return rv;
250 } else {
251 // we want to pass a clone of the original off to the worker thread.
252 trash.swap(dir);
255 nsAutoPtr<nsCOMArray<nsIFile> > arg(new nsCOMArray<nsIFile>);
256 arg->AppendObject(trash);
258 rv = gInstance->PostTimer(arg, delay);
259 if (NS_FAILED(rv))
260 return rv;
262 arg.forget();
263 return NS_OK;
266 nsresult
267 nsDeleteDir::GetTrashDir(nsIFile *target, nsCOMPtr<nsIFile> *result)
269 nsresult rv;
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");
273 if (cachePath) {
274 rv = NS_NewNativeLocalFile(nsDependentCString(cachePath),
275 true, getter_AddRefs(*result));
276 if (NS_FAILED(rv))
277 return rv;
279 // Add a sub folder with the cache folder name
280 nsAutoCString leaf;
281 rv = target->GetNativeLeafName(leaf);
282 (*result)->AppendNative(leaf);
283 } else
284 #endif
286 rv = target->Clone(getter_AddRefs(*result));
288 if (NS_FAILED(rv))
289 return rv;
291 nsAutoCString leaf;
292 rv = (*result)->GetNativeLeafName(leaf);
293 if (NS_FAILED(rv))
294 return rv;
295 leaf.AppendLiteral(".Trash");
297 return (*result)->SetNativeLeafName(leaf);
300 nsresult
301 nsDeleteDir::RemoveOldTrashes(nsIFile *cacheDir)
303 if (!gInstance)
304 return NS_ERROR_NOT_INITIALIZED;
306 nsresult rv;
308 nsCOMPtr<nsIFile> trash;
309 rv = GetTrashDir(cacheDir, &trash);
310 if (NS_FAILED(rv))
311 return rv;
313 nsAutoString trashName;
314 rv = trash->GetLeafName(trashName);
315 if (NS_FAILED(rv))
316 return rv;
318 nsCOMPtr<nsIFile> parent;
319 #if defined(MOZ_WIDGET_ANDROID)
320 rv = trash->GetParent(getter_AddRefs(parent));
321 #else
322 rv = cacheDir->GetParent(getter_AddRefs(parent));
323 #endif
324 if (NS_FAILED(rv))
325 return rv;
327 nsCOMPtr<nsISimpleEnumerator> iter;
328 rv = parent->GetDirectoryEntries(getter_AddRefs(iter));
329 if (NS_FAILED(rv))
330 return rv;
332 bool more;
333 nsCOMPtr<nsISupports> elem;
334 nsAutoPtr<nsCOMArray<nsIFile> > dirList;
336 while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
337 rv = iter->GetNext(getter_AddRefs(elem));
338 if (NS_FAILED(rv))
339 continue;
341 nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
342 if (!file)
343 continue;
345 nsAutoString leafName;
346 rv = file->GetLeafName(leafName);
347 if (NS_FAILED(rv))
348 continue;
350 // match all names that begin with the trash name (i.e. "Cache.Trash")
351 if (Substring(leafName, 0, trashName.Length()).Equals(trashName)) {
352 if (!dirList)
353 dirList = new nsCOMArray<nsIFile>;
354 dirList->AppendObject(file);
358 if (dirList) {
359 rv = gInstance->PostTimer(dirList, 90000);
360 if (NS_FAILED(rv))
361 return rv;
363 dirList.forget();
366 return NS_OK;
369 nsresult
370 nsDeleteDir::PostTimer(void *arg, uint32_t delay)
372 nsresult rv;
374 nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
375 if (NS_FAILED(rv))
376 return NS_ERROR_UNEXPECTED;
378 MutexAutoLock lock(mLock);
380 rv = InitThread();
381 if (NS_FAILED(rv))
382 return rv;
384 rv = timer->SetTarget(mThread);
385 if (NS_FAILED(rv))
386 return rv;
388 rv = timer->InitWithFuncCallback(TimerCallback, arg, delay,
389 nsITimer::TYPE_ONE_SHOT);
390 if (NS_FAILED(rv))
391 return rv;
393 mTimers.AppendObject(timer);
394 return NS_OK;
397 nsresult
398 nsDeleteDir::RemoveDir(nsIFile *file, bool *stopDeleting)
400 nsresult rv;
401 bool isLink;
403 rv = file->IsSymlink(&isLink);
404 if (NS_FAILED(rv) || isLink)
405 return NS_ERROR_UNEXPECTED;
407 bool isDir;
408 rv = file->IsDirectory(&isDir);
409 if (NS_FAILED(rv))
410 return rv;
412 if (isDir) {
413 nsCOMPtr<nsISimpleEnumerator> iter;
414 rv = file->GetDirectoryEntries(getter_AddRefs(iter));
415 if (NS_FAILED(rv))
416 return rv;
418 bool more;
419 nsCOMPtr<nsISupports> elem;
420 while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
421 rv = iter->GetNext(getter_AddRefs(elem));
422 if (NS_FAILED(rv)) {
423 NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
424 continue;
427 nsCOMPtr<nsIFile> file2 = do_QueryInterface(elem);
428 if (!file2) {
429 NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
430 continue;
433 RemoveDir(file2, stopDeleting);
434 // No check for errors to remove as much as possible
436 if (*stopDeleting)
437 return NS_OK;
441 file->Remove(false);
442 // No check for errors to remove as much as possible
444 MutexAutoLock lock(mLock);
445 if (mStopDeleting)
446 *stopDeleting = true;
448 return NS_OK;