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 :
51 : using namespace mozilla;
52 :
53 0 : class nsBlockOnBackgroundThreadEvent : public nsRunnable {
54 : public:
55 0 : nsBlockOnBackgroundThreadEvent() {}
56 0 : NS_IMETHOD Run()
57 : {
58 0 : MutexAutoLock lock(nsDeleteDir::gInstance->mLock);
59 0 : nsDeleteDir::gInstance->mCondVar.Notify();
60 0 : return NS_OK;
61 : }
62 : };
63 :
64 244 : class nsDestroyThreadEvent : public nsRunnable {
65 : public:
66 61 : nsDestroyThreadEvent(nsIThread *thread)
67 61 : : mThread(thread)
68 61 : {}
69 61 : NS_IMETHOD Run()
70 : {
71 61 : mThread->Shutdown();
72 61 : return NS_OK;
73 : }
74 : private:
75 : nsCOMPtr<nsIThread> mThread;
76 : };
77 :
78 :
79 : nsDeleteDir * nsDeleteDir::gInstance = nsnull;
80 :
81 269 : nsDeleteDir::nsDeleteDir()
82 : : mLock("nsDeleteDir.mLock"),
83 : mCondVar(mLock, "nsDeleteDir.mCondVar"),
84 : mShutdownPending(false),
85 269 : mStopDeleting(false)
86 : {
87 269 : NS_ASSERTION(gInstance==nsnull, "multiple nsCacheService instances!");
88 269 : }
89 :
90 538 : nsDeleteDir::~nsDeleteDir()
91 : {
92 269 : gInstance = nsnull;
93 269 : }
94 :
95 : nsresult
96 269 : nsDeleteDir::Init()
97 : {
98 269 : if (gInstance)
99 0 : return NS_ERROR_ALREADY_INITIALIZED;
100 :
101 269 : gInstance = new nsDeleteDir();
102 269 : return NS_OK;
103 : }
104 :
105 : nsresult
106 269 : nsDeleteDir::Shutdown(bool finishDeleting)
107 : {
108 269 : if (!gInstance)
109 0 : return NS_ERROR_NOT_INITIALIZED;
110 :
111 538 : nsCOMArray<nsIFile> dirsToRemove;
112 538 : nsCOMPtr<nsIThread> thread;
113 : {
114 538 : MutexAutoLock lock(gInstance->mLock);
115 269 : NS_ASSERTION(!gInstance->mShutdownPending,
116 : "Unexpected state in nsDeleteDir::Shutdown()");
117 269 : gInstance->mShutdownPending = true;
118 :
119 269 : if (!finishDeleting)
120 268 : gInstance->mStopDeleting = true;
121 :
122 : // remove all pending timers
123 269 : for (PRInt32 i = gInstance->mTimers.Count(); i > 0; i--) {
124 0 : nsCOMPtr<nsITimer> timer = gInstance->mTimers[i-1];
125 0 : gInstance->mTimers.RemoveObjectAt(i-1);
126 0 : timer->Cancel();
127 :
128 : nsCOMArray<nsIFile> *arg;
129 0 : timer->GetClosure((reinterpret_cast<void**>(&arg)));
130 :
131 0 : if (finishDeleting)
132 0 : dirsToRemove.AppendObjects(*arg);
133 :
134 : // delete argument passed to the timer
135 0 : delete arg;
136 : }
137 :
138 269 : thread.swap(gInstance->mThread);
139 269 : if (thread) {
140 : // dispatch event and wait for it to run and notify us, so we know thread
141 : // has completed all work and can be shutdown
142 0 : nsCOMPtr<nsIRunnable> event = new nsBlockOnBackgroundThreadEvent();
143 0 : nsresult rv = thread->Dispatch(event, NS_DISPATCH_NORMAL);
144 0 : if (NS_FAILED(rv)) {
145 0 : NS_WARNING("Failed dispatching block-event");
146 0 : return NS_ERROR_UNEXPECTED;
147 : }
148 :
149 0 : rv = gInstance->mCondVar.Wait();
150 0 : thread->Shutdown();
151 : }
152 : }
153 :
154 269 : delete gInstance;
155 :
156 269 : for (PRInt32 i = 0; i < dirsToRemove.Count(); i++)
157 0 : dirsToRemove[i]->Remove(true);
158 :
159 269 : return NS_OK;
160 : }
161 :
162 : nsresult
163 61 : nsDeleteDir::InitThread()
164 : {
165 61 : if (mThread)
166 0 : return NS_OK;
167 :
168 61 : nsresult rv = NS_NewThread(getter_AddRefs(mThread));
169 61 : if (NS_FAILED(rv)) {
170 0 : NS_WARNING("Can't create background thread");
171 0 : return rv;
172 : }
173 :
174 122 : nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mThread);
175 61 : if (p) {
176 61 : p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
177 : }
178 61 : return NS_OK;
179 : }
180 :
181 : void
182 61 : nsDeleteDir::DestroyThread()
183 : {
184 61 : if (!mThread)
185 0 : return;
186 :
187 61 : if (mTimers.Count())
188 : // more work to do, so don't delete thread.
189 0 : return;
190 :
191 122 : NS_DispatchToMainThread(new nsDestroyThreadEvent(mThread));
192 61 : mThread = nsnull;
193 : }
194 :
195 : void
196 61 : nsDeleteDir::TimerCallback(nsITimer *aTimer, void *arg)
197 : {
198 122 : Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR> timer;
199 : {
200 122 : MutexAutoLock lock(gInstance->mLock);
201 :
202 61 : PRInt32 idx = gInstance->mTimers.IndexOf(aTimer);
203 61 : if (idx == -1) {
204 : // Timer was canceled and removed during shutdown.
205 : return;
206 : }
207 :
208 122 : gInstance->mTimers.RemoveObjectAt(idx);
209 : }
210 :
211 122 : nsAutoPtr<nsCOMArray<nsIFile> > dirList;
212 61 : dirList = static_cast<nsCOMArray<nsIFile> *>(arg);
213 :
214 61 : bool shuttingDown = false;
215 122 : for (PRInt32 i = 0; i < dirList->Count() && !shuttingDown; i++) {
216 61 : gInstance->RemoveDir((*dirList)[i], &shuttingDown);
217 : }
218 :
219 : {
220 122 : MutexAutoLock lock(gInstance->mLock);
221 61 : gInstance->DestroyThread();
222 : }
223 : }
224 :
225 : nsresult
226 61 : nsDeleteDir::DeleteDir(nsIFile *dirIn, bool moveToTrash, PRUint32 delay)
227 : {
228 122 : Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_TRASHRENAME> timer;
229 :
230 61 : if (!gInstance)
231 0 : return NS_ERROR_NOT_INITIALIZED;
232 :
233 : nsresult rv;
234 122 : nsCOMPtr<nsIFile> trash, dir;
235 :
236 : // Need to make a clone of this since we don't want to modify the input
237 : // file object.
238 61 : rv = dirIn->Clone(getter_AddRefs(dir));
239 61 : if (NS_FAILED(rv))
240 0 : return rv;
241 :
242 61 : if (moveToTrash) {
243 61 : rv = GetTrashDir(dir, &trash);
244 61 : if (NS_FAILED(rv))
245 0 : return rv;
246 122 : nsCAutoString origLeaf;
247 61 : rv = trash->GetNativeLeafName(origLeaf);
248 61 : if (NS_FAILED(rv))
249 0 : return rv;
250 :
251 : // Important: must rename directory w/o changing parent directory: else on
252 : // NTFS we'll wait (with cache lock) while nsIFile's ACL reset walks file
253 : // tree: was hanging GUI for *minutes* on large cache dirs.
254 : // Append random number to the trash directory and check if it exists.
255 122 : nsCAutoString leaf;
256 61 : for (PRInt32 i = 0; i < 10; i++) {
257 61 : leaf = origLeaf;
258 61 : leaf.AppendInt(rand());
259 61 : rv = trash->SetNativeLeafName(leaf);
260 61 : if (NS_FAILED(rv))
261 0 : return rv;
262 :
263 : bool exists;
264 61 : if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
265 61 : break;
266 : }
267 :
268 0 : leaf.Truncate();
269 : }
270 :
271 : // Fail if we didn't find unused trash directory within the limit
272 61 : if (!leaf.Length())
273 0 : return NS_ERROR_FAILURE;
274 :
275 61 : rv = dir->MoveToNative(nsnull, leaf);
276 61 : if (NS_FAILED(rv))
277 0 : return rv;
278 : } else {
279 : // we want to pass a clone of the original off to the worker thread.
280 0 : trash.swap(dir);
281 : }
282 :
283 122 : nsAutoPtr<nsCOMArray<nsIFile> > arg(new nsCOMArray<nsIFile>);
284 61 : arg->AppendObject(trash);
285 :
286 61 : rv = gInstance->PostTimer(arg, delay);
287 61 : if (NS_FAILED(rv))
288 0 : return rv;
289 :
290 61 : arg.forget();
291 61 : return NS_OK;
292 : }
293 :
294 : nsresult
295 236 : nsDeleteDir::GetTrashDir(nsIFile *target, nsCOMPtr<nsIFile> *result)
296 : {
297 236 : nsresult rv = target->Clone(getter_AddRefs(*result));
298 236 : if (NS_FAILED(rv))
299 0 : return rv;
300 :
301 472 : nsCAutoString leaf;
302 236 : rv = (*result)->GetNativeLeafName(leaf);
303 236 : if (NS_FAILED(rv))
304 0 : return rv;
305 236 : leaf.AppendLiteral(".Trash");
306 :
307 236 : return (*result)->SetNativeLeafName(leaf);
308 : }
309 :
310 : nsresult
311 236 : nsDeleteDir::RemoveOldTrashes(nsIFile *cacheDir)
312 : {
313 236 : if (!gInstance)
314 0 : return NS_ERROR_NOT_INITIALIZED;
315 :
316 : nsresult rv;
317 :
318 : static bool firstRun = true;
319 :
320 236 : if (!firstRun)
321 61 : return NS_OK;
322 :
323 175 : firstRun = false;
324 :
325 350 : nsCOMPtr<nsIFile> trash;
326 175 : rv = GetTrashDir(cacheDir, &trash);
327 175 : if (NS_FAILED(rv))
328 0 : return rv;
329 :
330 350 : nsAutoString trashName;
331 175 : rv = trash->GetLeafName(trashName);
332 175 : if (NS_FAILED(rv))
333 0 : return rv;
334 :
335 350 : nsCOMPtr<nsIFile> parent;
336 175 : rv = cacheDir->GetParent(getter_AddRefs(parent));
337 175 : if (NS_FAILED(rv))
338 0 : return rv;
339 :
340 350 : nsCOMPtr<nsISimpleEnumerator> iter;
341 175 : rv = parent->GetDirectoryEntries(getter_AddRefs(iter));
342 175 : if (NS_FAILED(rv))
343 0 : return rv;
344 :
345 : bool more;
346 350 : nsCOMPtr<nsISupports> elem;
347 350 : nsAutoPtr<nsCOMArray<nsIFile> > dirList;
348 :
349 1975 : while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
350 1625 : rv = iter->GetNext(getter_AddRefs(elem));
351 1625 : if (NS_FAILED(rv))
352 0 : continue;
353 :
354 3250 : nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
355 1625 : if (!file)
356 0 : continue;
357 :
358 3250 : nsAutoString leafName;
359 1625 : rv = file->GetLeafName(leafName);
360 1625 : if (NS_FAILED(rv))
361 0 : continue;
362 :
363 : // match all names that begin with the trash name (i.e. "Cache.Trash")
364 1625 : if (Substring(leafName, 0, trashName.Length()).Equals(trashName)) {
365 0 : if (!dirList)
366 0 : dirList = new nsCOMArray<nsIFile>;
367 0 : dirList->AppendObject(file);
368 : }
369 : }
370 :
371 175 : if (dirList) {
372 0 : rv = gInstance->PostTimer(dirList, 90000);
373 0 : if (NS_FAILED(rv))
374 0 : return rv;
375 :
376 0 : dirList.forget();
377 : }
378 :
379 175 : return NS_OK;
380 : }
381 :
382 : nsresult
383 61 : nsDeleteDir::PostTimer(void *arg, PRUint32 delay)
384 : {
385 : nsresult rv;
386 :
387 122 : nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
388 61 : if (NS_FAILED(rv))
389 0 : return NS_ERROR_UNEXPECTED;
390 :
391 122 : MutexAutoLock lock(mLock);
392 :
393 61 : rv = InitThread();
394 61 : if (NS_FAILED(rv))
395 0 : return rv;
396 :
397 61 : rv = timer->SetTarget(mThread);
398 61 : if (NS_FAILED(rv))
399 0 : return rv;
400 :
401 61 : rv = timer->InitWithFuncCallback(TimerCallback, arg, delay,
402 61 : nsITimer::TYPE_ONE_SHOT);
403 61 : if (NS_FAILED(rv))
404 0 : return rv;
405 :
406 61 : mTimers.AppendObject(timer);
407 61 : return NS_OK;
408 : }
409 :
410 : nsresult
411 1281 : nsDeleteDir::RemoveDir(nsIFile *file, bool *stopDeleting)
412 : {
413 : nsresult rv;
414 : bool isLink;
415 :
416 1281 : rv = file->IsSymlink(&isLink);
417 1281 : if (NS_FAILED(rv) || isLink)
418 0 : return NS_ERROR_UNEXPECTED;
419 :
420 : bool isDir;
421 1281 : rv = file->IsDirectory(&isDir);
422 1281 : if (NS_FAILED(rv))
423 0 : return rv;
424 :
425 1281 : if (isDir) {
426 2074 : nsCOMPtr<nsISimpleEnumerator> iter;
427 1037 : rv = file->GetDirectoryEntries(getter_AddRefs(iter));
428 1037 : if (NS_FAILED(rv))
429 0 : return rv;
430 :
431 : bool more;
432 2074 : nsCOMPtr<nsISupports> elem;
433 1037 : while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
434 1220 : rv = iter->GetNext(getter_AddRefs(elem));
435 1220 : if (NS_FAILED(rv)) {
436 0 : NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
437 0 : continue;
438 : }
439 :
440 2440 : nsCOMPtr<nsIFile> file2 = do_QueryInterface(elem);
441 1220 : if (!file2) {
442 0 : NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
443 0 : continue;
444 : }
445 :
446 1220 : RemoveDir(file2, stopDeleting);
447 : // No check for errors to remove as much as possible
448 :
449 1220 : if (*stopDeleting)
450 0 : return NS_OK;
451 : }
452 : }
453 :
454 1281 : file->Remove(false);
455 : // No check for errors to remove as much as possible
456 :
457 2562 : MutexAutoLock lock(mLock);
458 1281 : if (mStopDeleting)
459 0 : *stopDeleting = true;
460 :
461 1281 : return NS_OK;
462 : }
|