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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is Google Inc.
19 : * Portions created by the Initial Developer are Copyright (C) 2005
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Darin Fisher <darin@meer.net>
24 : * Jason Duell <jduell.mcbugs@gmail.com>
25 : * Michal Novotny <michal.novotny@gmail.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include "nsDeleteDir.h"
42 : #include "nsIFile.h"
43 : #include "nsString.h"
44 : #include "mozilla/Telemetry.h"
45 : #include "nsITimer.h"
46 : #include "nsISimpleEnumerator.h"
47 : #include "nsAutoPtr.h"
48 : #include "nsThreadUtils.h"
49 : #include "nsISupportsPriority.h"
50 : #include <time.h>
51 :
52 : using namespace mozilla;
53 :
54 0 : class nsBlockOnBackgroundThreadEvent : public nsRunnable {
55 : public:
56 0 : nsBlockOnBackgroundThreadEvent() {}
57 0 : NS_IMETHOD Run()
58 : {
59 0 : MutexAutoLock lock(nsDeleteDir::gInstance->mLock);
60 0 : nsDeleteDir::gInstance->mCondVar.Notify();
61 0 : return NS_OK;
62 : }
63 : };
64 :
65 244 : class nsDestroyThreadEvent : public nsRunnable {
66 : public:
67 61 : nsDestroyThreadEvent(nsIThread *thread)
68 61 : : mThread(thread)
69 61 : {}
70 61 : NS_IMETHOD Run()
71 : {
72 61 : mThread->Shutdown();
73 61 : return NS_OK;
74 : }
75 : private:
76 : nsCOMPtr<nsIThread> mThread;
77 : };
78 :
79 :
80 : nsDeleteDir * nsDeleteDir::gInstance = nsnull;
81 :
82 269 : nsDeleteDir::nsDeleteDir()
83 : : mLock("nsDeleteDir.mLock"),
84 : mCondVar(mLock, "nsDeleteDir.mCondVar"),
85 : mShutdownPending(false),
86 269 : mStopDeleting(false)
87 : {
88 269 : NS_ASSERTION(gInstance==nsnull, "multiple nsCacheService instances!");
89 269 : }
90 :
91 538 : nsDeleteDir::~nsDeleteDir()
92 : {
93 269 : gInstance = nsnull;
94 269 : }
95 :
96 : nsresult
97 269 : nsDeleteDir::Init()
98 : {
99 269 : if (gInstance)
100 0 : return NS_ERROR_ALREADY_INITIALIZED;
101 :
102 269 : gInstance = new nsDeleteDir();
103 269 : return NS_OK;
104 : }
105 :
106 : nsresult
107 269 : nsDeleteDir::Shutdown(bool finishDeleting)
108 : {
109 269 : if (!gInstance)
110 0 : return NS_ERROR_NOT_INITIALIZED;
111 :
112 538 : nsCOMArray<nsIFile> dirsToRemove;
113 538 : nsCOMPtr<nsIThread> thread;
114 : {
115 538 : MutexAutoLock lock(gInstance->mLock);
116 269 : NS_ASSERTION(!gInstance->mShutdownPending,
117 : "Unexpected state in nsDeleteDir::Shutdown()");
118 269 : gInstance->mShutdownPending = true;
119 :
120 269 : if (!finishDeleting)
121 268 : gInstance->mStopDeleting = true;
122 :
123 : // remove all pending timers
124 269 : for (PRInt32 i = gInstance->mTimers.Count(); i > 0; i--) {
125 0 : nsCOMPtr<nsITimer> timer = gInstance->mTimers[i-1];
126 0 : gInstance->mTimers.RemoveObjectAt(i-1);
127 0 : timer->Cancel();
128 :
129 : nsCOMArray<nsIFile> *arg;
130 0 : timer->GetClosure((reinterpret_cast<void**>(&arg)));
131 :
132 0 : if (finishDeleting)
133 0 : dirsToRemove.AppendObjects(*arg);
134 :
135 : // delete argument passed to the timer
136 0 : delete arg;
137 : }
138 :
139 269 : thread.swap(gInstance->mThread);
140 269 : if (thread) {
141 : // dispatch event and wait for it to run and notify us, so we know thread
142 : // has completed all work and can be shutdown
143 0 : nsCOMPtr<nsIRunnable> event = new nsBlockOnBackgroundThreadEvent();
144 0 : nsresult rv = thread->Dispatch(event, NS_DISPATCH_NORMAL);
145 0 : if (NS_FAILED(rv)) {
146 0 : NS_WARNING("Failed dispatching block-event");
147 0 : return NS_ERROR_UNEXPECTED;
148 : }
149 :
150 0 : rv = gInstance->mCondVar.Wait();
151 0 : thread->Shutdown();
152 : }
153 : }
154 :
155 269 : delete gInstance;
156 :
157 269 : for (PRInt32 i = 0; i < dirsToRemove.Count(); i++)
158 0 : dirsToRemove[i]->Remove(true);
159 :
160 269 : return NS_OK;
161 : }
162 :
163 : nsresult
164 61 : nsDeleteDir::InitThread()
165 : {
166 61 : if (mThread)
167 0 : return NS_OK;
168 :
169 61 : nsresult rv = NS_NewThread(getter_AddRefs(mThread));
170 61 : if (NS_FAILED(rv)) {
171 0 : NS_WARNING("Can't create background thread");
172 0 : return rv;
173 : }
174 :
175 122 : nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mThread);
176 61 : if (p) {
177 61 : p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
178 : }
179 61 : return NS_OK;
180 : }
181 :
182 : void
183 61 : nsDeleteDir::DestroyThread()
184 : {
185 61 : if (!mThread)
186 0 : return;
187 :
188 61 : if (mTimers.Count())
189 : // more work to do, so don't delete thread.
190 0 : return;
191 :
192 122 : NS_DispatchToMainThread(new nsDestroyThreadEvent(mThread));
193 61 : mThread = nsnull;
194 : }
195 :
196 : void
197 61 : nsDeleteDir::TimerCallback(nsITimer *aTimer, void *arg)
198 : {
199 122 : Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR> timer;
200 : {
201 122 : MutexAutoLock lock(gInstance->mLock);
202 :
203 61 : PRInt32 idx = gInstance->mTimers.IndexOf(aTimer);
204 61 : if (idx == -1) {
205 : // Timer was canceled and removed during shutdown.
206 : return;
207 : }
208 :
209 122 : gInstance->mTimers.RemoveObjectAt(idx);
210 : }
211 :
212 122 : nsAutoPtr<nsCOMArray<nsIFile> > dirList;
213 61 : dirList = static_cast<nsCOMArray<nsIFile> *>(arg);
214 :
215 61 : bool shuttingDown = false;
216 122 : for (PRInt32 i = 0; i < dirList->Count() && !shuttingDown; i++) {
217 61 : gInstance->RemoveDir((*dirList)[i], &shuttingDown);
218 : }
219 :
220 : {
221 122 : MutexAutoLock lock(gInstance->mLock);
222 61 : gInstance->DestroyThread();
223 : }
224 : }
225 :
226 : nsresult
227 61 : nsDeleteDir::DeleteDir(nsIFile *dirIn, bool moveToTrash, PRUint32 delay)
228 : {
229 122 : Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_TRASHRENAME> timer;
230 :
231 61 : if (!gInstance)
232 0 : return NS_ERROR_NOT_INITIALIZED;
233 :
234 : nsresult rv;
235 122 : nsCOMPtr<nsIFile> trash, dir;
236 :
237 : // Need to make a clone of this since we don't want to modify the input
238 : // file object.
239 61 : rv = dirIn->Clone(getter_AddRefs(dir));
240 61 : if (NS_FAILED(rv))
241 0 : return rv;
242 :
243 61 : if (moveToTrash) {
244 61 : rv = GetTrashDir(dir, &trash);
245 61 : if (NS_FAILED(rv))
246 0 : return rv;
247 122 : nsCAutoString origLeaf;
248 61 : rv = trash->GetNativeLeafName(origLeaf);
249 61 : if (NS_FAILED(rv))
250 0 : return rv;
251 :
252 : // Important: must rename directory w/o changing parent directory: else on
253 : // NTFS we'll wait (with cache lock) while nsIFile's ACL reset walks file
254 : // tree: was hanging GUI for *minutes* on large cache dirs.
255 : // Append random number to the trash directory and check if it exists.
256 61 : srand(PR_Now());
257 122 : nsCAutoString leaf;
258 61 : for (PRInt32 i = 0; i < 10; i++) {
259 61 : leaf = origLeaf;
260 61 : leaf.AppendInt(rand());
261 61 : rv = trash->SetNativeLeafName(leaf);
262 61 : if (NS_FAILED(rv))
263 0 : return rv;
264 :
265 : bool exists;
266 61 : if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
267 61 : break;
268 : }
269 :
270 0 : leaf.Truncate();
271 : }
272 :
273 : // Fail if we didn't find unused trash directory within the limit
274 61 : if (!leaf.Length())
275 0 : return NS_ERROR_FAILURE;
276 :
277 61 : rv = dir->MoveToNative(nsnull, leaf);
278 61 : if (NS_FAILED(rv))
279 0 : return rv;
280 : } else {
281 : // we want to pass a clone of the original off to the worker thread.
282 0 : trash.swap(dir);
283 : }
284 :
285 122 : nsAutoPtr<nsCOMArray<nsIFile> > arg(new nsCOMArray<nsIFile>);
286 61 : arg->AppendObject(trash);
287 :
288 61 : rv = gInstance->PostTimer(arg, delay);
289 61 : if (NS_FAILED(rv))
290 0 : return rv;
291 :
292 61 : arg.forget();
293 61 : return NS_OK;
294 : }
295 :
296 : nsresult
297 236 : nsDeleteDir::GetTrashDir(nsIFile *target, nsCOMPtr<nsIFile> *result)
298 : {
299 236 : nsresult rv = target->Clone(getter_AddRefs(*result));
300 236 : if (NS_FAILED(rv))
301 0 : return rv;
302 :
303 472 : nsCAutoString leaf;
304 236 : rv = (*result)->GetNativeLeafName(leaf);
305 236 : if (NS_FAILED(rv))
306 0 : return rv;
307 236 : leaf.AppendLiteral(".Trash");
308 :
309 236 : return (*result)->SetNativeLeafName(leaf);
310 : }
311 :
312 : nsresult
313 236 : nsDeleteDir::RemoveOldTrashes(nsIFile *cacheDir)
314 : {
315 236 : if (!gInstance)
316 0 : return NS_ERROR_NOT_INITIALIZED;
317 :
318 : nsresult rv;
319 :
320 : static bool firstRun = true;
321 :
322 236 : if (!firstRun)
323 61 : return NS_OK;
324 :
325 175 : firstRun = false;
326 :
327 350 : nsCOMPtr<nsIFile> trash;
328 175 : rv = GetTrashDir(cacheDir, &trash);
329 175 : if (NS_FAILED(rv))
330 0 : return rv;
331 :
332 350 : nsAutoString trashName;
333 175 : rv = trash->GetLeafName(trashName);
334 175 : if (NS_FAILED(rv))
335 0 : return rv;
336 :
337 350 : nsCOMPtr<nsIFile> parent;
338 175 : rv = cacheDir->GetParent(getter_AddRefs(parent));
339 175 : if (NS_FAILED(rv))
340 0 : return rv;
341 :
342 350 : nsCOMPtr<nsISimpleEnumerator> iter;
343 175 : rv = parent->GetDirectoryEntries(getter_AddRefs(iter));
344 175 : if (NS_FAILED(rv))
345 0 : return rv;
346 :
347 : bool more;
348 350 : nsCOMPtr<nsISupports> elem;
349 350 : nsAutoPtr<nsCOMArray<nsIFile> > dirList;
350 :
351 1974 : while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
352 1624 : rv = iter->GetNext(getter_AddRefs(elem));
353 1624 : if (NS_FAILED(rv))
354 0 : continue;
355 :
356 3248 : nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
357 1624 : if (!file)
358 0 : continue;
359 :
360 3248 : nsAutoString leafName;
361 1624 : rv = file->GetLeafName(leafName);
362 1624 : if (NS_FAILED(rv))
363 0 : continue;
364 :
365 : // match all names that begin with the trash name (i.e. "Cache.Trash")
366 1624 : if (Substring(leafName, 0, trashName.Length()).Equals(trashName)) {
367 0 : if (!dirList)
368 0 : dirList = new nsCOMArray<nsIFile>;
369 0 : dirList->AppendObject(file);
370 : }
371 : }
372 :
373 175 : if (dirList) {
374 0 : rv = gInstance->PostTimer(dirList, 90000);
375 0 : if (NS_FAILED(rv))
376 0 : return rv;
377 :
378 0 : dirList.forget();
379 : }
380 :
381 175 : return NS_OK;
382 : }
383 :
384 : nsresult
385 61 : nsDeleteDir::PostTimer(void *arg, PRUint32 delay)
386 : {
387 : nsresult rv;
388 :
389 122 : nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
390 61 : if (NS_FAILED(rv))
391 0 : return NS_ERROR_UNEXPECTED;
392 :
393 122 : MutexAutoLock lock(mLock);
394 :
395 61 : rv = InitThread();
396 61 : if (NS_FAILED(rv))
397 0 : return rv;
398 :
399 61 : rv = timer->SetTarget(mThread);
400 61 : if (NS_FAILED(rv))
401 0 : return rv;
402 :
403 61 : rv = timer->InitWithFuncCallback(TimerCallback, arg, delay,
404 61 : nsITimer::TYPE_ONE_SHOT);
405 61 : if (NS_FAILED(rv))
406 0 : return rv;
407 :
408 61 : mTimers.AppendObject(timer);
409 61 : return NS_OK;
410 : }
411 :
412 : nsresult
413 1281 : nsDeleteDir::RemoveDir(nsIFile *file, bool *stopDeleting)
414 : {
415 : nsresult rv;
416 : bool isLink;
417 :
418 1281 : rv = file->IsSymlink(&isLink);
419 1281 : if (NS_FAILED(rv) || isLink)
420 0 : return NS_ERROR_UNEXPECTED;
421 :
422 : bool isDir;
423 1281 : rv = file->IsDirectory(&isDir);
424 1281 : if (NS_FAILED(rv))
425 0 : return rv;
426 :
427 1281 : if (isDir) {
428 2074 : nsCOMPtr<nsISimpleEnumerator> iter;
429 1037 : rv = file->GetDirectoryEntries(getter_AddRefs(iter));
430 1037 : if (NS_FAILED(rv))
431 0 : return rv;
432 :
433 : bool more;
434 2074 : nsCOMPtr<nsISupports> elem;
435 1037 : while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
436 1220 : rv = iter->GetNext(getter_AddRefs(elem));
437 1220 : if (NS_FAILED(rv)) {
438 0 : NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
439 0 : continue;
440 : }
441 :
442 2440 : nsCOMPtr<nsIFile> file2 = do_QueryInterface(elem);
443 1220 : if (!file2) {
444 0 : NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
445 0 : continue;
446 : }
447 :
448 1220 : RemoveDir(file2, stopDeleting);
449 : // No check for errors to remove as much as possible
450 :
451 1220 : if (*stopDeleting)
452 0 : return NS_OK;
453 : }
454 : }
455 :
456 1281 : file->Remove(false);
457 : // No check for errors to remove as much as possible
458 :
459 2562 : MutexAutoLock lock(mLock);
460 1281 : if (mStopDeleting)
461 0 : *stopDeleting = true;
462 :
463 1281 : return NS_OK;
464 : }
|