|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 et sw=2 tw=80: */ |
|
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 file, |
|
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "FileService.h" |
|
8 |
|
9 #include "nsIFile.h" |
|
10 #include "nsIFileStorage.h" |
|
11 #include "nsIObserverService.h" |
|
12 #include "nsIStreamTransportService.h" |
|
13 |
|
14 #include "nsNetCID.h" |
|
15 |
|
16 #include "FileHandle.h" |
|
17 #include "FileRequest.h" |
|
18 |
|
19 USING_FILE_NAMESPACE |
|
20 |
|
21 namespace { |
|
22 |
|
23 FileService* gInstance = nullptr; |
|
24 bool gShutdown = false; |
|
25 |
|
26 } // anonymous namespace |
|
27 |
|
28 FileService::FileService() |
|
29 { |
|
30 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
31 NS_ASSERTION(!gInstance, "More than one instance!"); |
|
32 } |
|
33 |
|
34 FileService::~FileService() |
|
35 { |
|
36 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
37 NS_ASSERTION(!gInstance, "More than one instance!"); |
|
38 } |
|
39 |
|
40 nsresult |
|
41 FileService::Init() |
|
42 { |
|
43 nsresult rv; |
|
44 mStreamTransportTarget = |
|
45 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); |
|
46 |
|
47 return rv; |
|
48 } |
|
49 |
|
50 nsresult |
|
51 FileService::Cleanup() |
|
52 { |
|
53 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
54 |
|
55 nsIThread* thread = NS_GetCurrentThread(); |
|
56 while (mFileStorageInfos.Count()) { |
|
57 if (!NS_ProcessNextEvent(thread)) { |
|
58 NS_ERROR("Failed to process next event!"); |
|
59 break; |
|
60 } |
|
61 } |
|
62 |
|
63 // Make sure the service is still accessible while any generated callbacks |
|
64 // are processed. |
|
65 nsresult rv = NS_ProcessPendingEvents(thread); |
|
66 NS_ENSURE_SUCCESS(rv, rv); |
|
67 |
|
68 if (!mCompleteCallbacks.IsEmpty()) { |
|
69 // Run all callbacks manually now. |
|
70 for (uint32_t index = 0; index < mCompleteCallbacks.Length(); index++) { |
|
71 mCompleteCallbacks[index].mCallback->Run(); |
|
72 } |
|
73 mCompleteCallbacks.Clear(); |
|
74 |
|
75 // And make sure they get processed. |
|
76 rv = NS_ProcessPendingEvents(thread); |
|
77 NS_ENSURE_SUCCESS(rv, rv); |
|
78 } |
|
79 |
|
80 return NS_OK; |
|
81 } |
|
82 |
|
83 // static |
|
84 FileService* |
|
85 FileService::GetOrCreate() |
|
86 { |
|
87 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
88 |
|
89 if (gShutdown) { |
|
90 NS_WARNING("Calling GetOrCreate() after shutdown!"); |
|
91 return nullptr; |
|
92 } |
|
93 |
|
94 if (!gInstance) { |
|
95 nsRefPtr<FileService> service(new FileService); |
|
96 |
|
97 nsresult rv = service->Init(); |
|
98 NS_ENSURE_SUCCESS(rv, nullptr); |
|
99 |
|
100 nsCOMPtr<nsIObserverService> obs = |
|
101 do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv); |
|
102 NS_ENSURE_SUCCESS(rv, nullptr); |
|
103 |
|
104 rv = obs->AddObserver(service, "profile-before-change", false); |
|
105 NS_ENSURE_SUCCESS(rv, nullptr); |
|
106 |
|
107 // The observer service now owns us. |
|
108 gInstance = service; |
|
109 } |
|
110 |
|
111 return gInstance; |
|
112 } |
|
113 |
|
114 // static |
|
115 FileService* |
|
116 FileService::Get() |
|
117 { |
|
118 // Does not return an owning reference. |
|
119 return gInstance; |
|
120 } |
|
121 |
|
122 // static |
|
123 void |
|
124 FileService::Shutdown() |
|
125 { |
|
126 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
127 |
|
128 gShutdown = true; |
|
129 |
|
130 if (gInstance) { |
|
131 if (NS_FAILED(gInstance->Cleanup())) { |
|
132 NS_WARNING("Failed to shutdown file service!"); |
|
133 } |
|
134 gInstance = nullptr; |
|
135 } |
|
136 } |
|
137 |
|
138 // static |
|
139 bool |
|
140 FileService::IsShuttingDown() |
|
141 { |
|
142 return gShutdown; |
|
143 } |
|
144 |
|
145 nsresult |
|
146 FileService::Enqueue(LockedFile* aLockedFile, FileHelper* aFileHelper) |
|
147 { |
|
148 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
149 NS_ASSERTION(aLockedFile, "Null pointer!"); |
|
150 |
|
151 FileHandle* fileHandle = aLockedFile->mFileHandle; |
|
152 |
|
153 if (fileHandle->mFileStorage->IsInvalidated()) { |
|
154 return NS_ERROR_NOT_AVAILABLE; |
|
155 } |
|
156 |
|
157 const nsACString& storageId = fileHandle->mFileStorage->Id(); |
|
158 const nsAString& fileName = fileHandle->mFileName; |
|
159 bool modeIsWrite = aLockedFile->mMode == FileMode::Readwrite; |
|
160 |
|
161 FileStorageInfo* fileStorageInfo; |
|
162 if (!mFileStorageInfos.Get(storageId, &fileStorageInfo)) { |
|
163 nsAutoPtr<FileStorageInfo> newFileStorageInfo(new FileStorageInfo()); |
|
164 |
|
165 mFileStorageInfos.Put(storageId, newFileStorageInfo); |
|
166 |
|
167 fileStorageInfo = newFileStorageInfo.forget(); |
|
168 } |
|
169 |
|
170 LockedFileQueue* existingLockedFileQueue = |
|
171 fileStorageInfo->GetLockedFileQueue(aLockedFile); |
|
172 |
|
173 if (existingLockedFileQueue) { |
|
174 existingLockedFileQueue->Enqueue(aFileHelper); |
|
175 return NS_OK; |
|
176 } |
|
177 |
|
178 bool lockedForReading = fileStorageInfo->IsFileLockedForReading(fileName); |
|
179 bool lockedForWriting = fileStorageInfo->IsFileLockedForWriting(fileName); |
|
180 |
|
181 if (modeIsWrite) { |
|
182 if (!lockedForWriting) { |
|
183 fileStorageInfo->LockFileForWriting(fileName); |
|
184 } |
|
185 } |
|
186 else { |
|
187 if (!lockedForReading) { |
|
188 fileStorageInfo->LockFileForReading(fileName); |
|
189 } |
|
190 } |
|
191 |
|
192 if (lockedForWriting || (lockedForReading && modeIsWrite)) { |
|
193 fileStorageInfo->CreateDelayedEnqueueInfo(aLockedFile, aFileHelper); |
|
194 } |
|
195 else { |
|
196 LockedFileQueue* lockedFileQueue = |
|
197 fileStorageInfo->CreateLockedFileQueue(aLockedFile); |
|
198 |
|
199 if (aFileHelper) { |
|
200 // Enqueue() will queue the file helper if there's already something |
|
201 // running. That can't fail, so no need to eventually remove |
|
202 // fileStorageInfo from the hash table. |
|
203 // |
|
204 // If the file helper is free to run then AsyncRun() is called on the |
|
205 // file helper. AsyncRun() is responsible for calling all necessary |
|
206 // callbacks when something fails. We're propagating the error here, |
|
207 // however there's no need to eventually remove fileStorageInfo from |
|
208 // the hash table. Code behind AsyncRun() will take care of it. The last |
|
209 // item in the code path is NotifyLockedFileCompleted() which removes |
|
210 // fileStorageInfo from the hash table if there are no locked files for |
|
211 // the file storage. |
|
212 nsresult rv = lockedFileQueue->Enqueue(aFileHelper); |
|
213 NS_ENSURE_SUCCESS(rv, rv); |
|
214 } |
|
215 } |
|
216 |
|
217 return NS_OK; |
|
218 } |
|
219 |
|
220 void |
|
221 FileService::NotifyLockedFileCompleted(LockedFile* aLockedFile) |
|
222 { |
|
223 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
224 NS_ASSERTION(aLockedFile, "Null pointer!"); |
|
225 |
|
226 FileHandle* fileHandle = aLockedFile->mFileHandle; |
|
227 const nsACString& storageId = fileHandle->mFileStorage->Id(); |
|
228 |
|
229 FileStorageInfo* fileStorageInfo; |
|
230 if (!mFileStorageInfos.Get(storageId, &fileStorageInfo)) { |
|
231 NS_ERROR("We don't know anyting about this locked file?!"); |
|
232 return; |
|
233 } |
|
234 |
|
235 fileStorageInfo->RemoveLockedFileQueue(aLockedFile); |
|
236 |
|
237 if (!fileStorageInfo->HasRunningLockedFiles()) { |
|
238 mFileStorageInfos.Remove(storageId); |
|
239 |
|
240 // See if we need to fire any complete callbacks. |
|
241 uint32_t index = 0; |
|
242 while (index < mCompleteCallbacks.Length()) { |
|
243 if (MaybeFireCallback(mCompleteCallbacks[index])) { |
|
244 mCompleteCallbacks.RemoveElementAt(index); |
|
245 } |
|
246 else { |
|
247 index++; |
|
248 } |
|
249 } |
|
250 } |
|
251 } |
|
252 |
|
253 void |
|
254 FileService::WaitForStoragesToComplete( |
|
255 nsTArray<nsCOMPtr<nsIFileStorage> >& aStorages, |
|
256 nsIRunnable* aCallback) |
|
257 { |
|
258 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
259 NS_ASSERTION(!aStorages.IsEmpty(), "No databases to wait on!"); |
|
260 NS_ASSERTION(aCallback, "Null pointer!"); |
|
261 |
|
262 StoragesCompleteCallback* callback = mCompleteCallbacks.AppendElement(); |
|
263 callback->mCallback = aCallback; |
|
264 callback->mStorages.SwapElements(aStorages); |
|
265 |
|
266 if (MaybeFireCallback(*callback)) { |
|
267 mCompleteCallbacks.RemoveElementAt(mCompleteCallbacks.Length() - 1); |
|
268 } |
|
269 } |
|
270 |
|
271 void |
|
272 FileService::AbortLockedFilesForStorage(nsIFileStorage* aFileStorage) |
|
273 { |
|
274 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
275 NS_ASSERTION(aFileStorage, "Null pointer!"); |
|
276 |
|
277 FileStorageInfo* fileStorageInfo; |
|
278 if (!mFileStorageInfos.Get(aFileStorage->Id(), &fileStorageInfo)) { |
|
279 return; |
|
280 } |
|
281 |
|
282 nsAutoTArray<nsRefPtr<LockedFile>, 10> lockedFiles; |
|
283 fileStorageInfo->CollectRunningAndDelayedLockedFiles(aFileStorage, |
|
284 lockedFiles); |
|
285 |
|
286 for (uint32_t index = 0; index < lockedFiles.Length(); index++) { |
|
287 ErrorResult ignored; |
|
288 lockedFiles[index]->Abort(ignored); |
|
289 } |
|
290 } |
|
291 |
|
292 bool |
|
293 FileService::HasLockedFilesForStorage(nsIFileStorage* aFileStorage) |
|
294 { |
|
295 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
296 NS_ASSERTION(aFileStorage, "Null pointer!"); |
|
297 |
|
298 FileStorageInfo* fileStorageInfo; |
|
299 if (!mFileStorageInfos.Get(aFileStorage->Id(), &fileStorageInfo)) { |
|
300 return false; |
|
301 } |
|
302 |
|
303 return fileStorageInfo->HasRunningLockedFiles(aFileStorage); |
|
304 } |
|
305 |
|
306 NS_IMPL_ISUPPORTS(FileService, nsIObserver) |
|
307 |
|
308 NS_IMETHODIMP |
|
309 FileService::Observe(nsISupports* aSubject, const char* aTopic, |
|
310 const char16_t* aData) |
|
311 { |
|
312 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
313 NS_ASSERTION(!strcmp(aTopic, "profile-before-change"), "Wrong topic!"); |
|
314 |
|
315 Shutdown(); |
|
316 |
|
317 return NS_OK; |
|
318 } |
|
319 |
|
320 bool |
|
321 FileService::MaybeFireCallback(StoragesCompleteCallback& aCallback) |
|
322 { |
|
323 NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); |
|
324 |
|
325 for (uint32_t index = 0; index < aCallback.mStorages.Length(); index++) { |
|
326 if (mFileStorageInfos.Get(aCallback.mStorages[index]->Id(), nullptr)) { |
|
327 return false; |
|
328 } |
|
329 } |
|
330 |
|
331 aCallback.mCallback->Run(); |
|
332 return true; |
|
333 } |
|
334 |
|
335 FileService::LockedFileQueue::LockedFileQueue(LockedFile* aLockedFile) |
|
336 : mLockedFile(aLockedFile) |
|
337 { |
|
338 NS_ASSERTION(aLockedFile, "Null pointer!"); |
|
339 } |
|
340 |
|
341 NS_IMPL_ADDREF(FileService::LockedFileQueue) |
|
342 NS_IMPL_RELEASE(FileService::LockedFileQueue) |
|
343 |
|
344 nsresult |
|
345 FileService::LockedFileQueue::Enqueue(FileHelper* aFileHelper) |
|
346 { |
|
347 mQueue.AppendElement(aFileHelper); |
|
348 |
|
349 nsresult rv; |
|
350 if (mLockedFile->mRequestMode == LockedFile::PARALLEL) { |
|
351 rv = aFileHelper->AsyncRun(this); |
|
352 } |
|
353 else { |
|
354 rv = ProcessQueue(); |
|
355 } |
|
356 NS_ENSURE_SUCCESS(rv, rv); |
|
357 |
|
358 return NS_OK; |
|
359 } |
|
360 |
|
361 void |
|
362 FileService::LockedFileQueue::OnFileHelperComplete(FileHelper* aFileHelper) |
|
363 { |
|
364 if (mLockedFile->mRequestMode == LockedFile::PARALLEL) { |
|
365 int32_t index = mQueue.IndexOf(aFileHelper); |
|
366 NS_ASSERTION(index != -1, "We don't know anything about this helper!"); |
|
367 |
|
368 mQueue.RemoveElementAt(index); |
|
369 } |
|
370 else { |
|
371 NS_ASSERTION(mCurrentHelper == aFileHelper, "How can this happen?!"); |
|
372 |
|
373 mCurrentHelper = nullptr; |
|
374 |
|
375 nsresult rv = ProcessQueue(); |
|
376 if (NS_FAILED(rv)) { |
|
377 return; |
|
378 } |
|
379 } |
|
380 } |
|
381 |
|
382 nsresult |
|
383 FileService::LockedFileQueue::ProcessQueue() |
|
384 { |
|
385 if (mQueue.IsEmpty() || mCurrentHelper) { |
|
386 return NS_OK; |
|
387 } |
|
388 |
|
389 mCurrentHelper = mQueue[0]; |
|
390 mQueue.RemoveElementAt(0); |
|
391 |
|
392 nsresult rv = mCurrentHelper->AsyncRun(this); |
|
393 NS_ENSURE_SUCCESS(rv, rv); |
|
394 |
|
395 return NS_OK; |
|
396 } |
|
397 |
|
398 FileService::LockedFileQueue* |
|
399 FileService::FileStorageInfo::CreateLockedFileQueue(LockedFile* aLockedFile) |
|
400 { |
|
401 nsRefPtr<LockedFileQueue>* lockedFileQueue = |
|
402 mLockedFileQueues.AppendElement(); |
|
403 *lockedFileQueue = new LockedFileQueue(aLockedFile); |
|
404 return lockedFileQueue->get(); |
|
405 } |
|
406 |
|
407 FileService::LockedFileQueue* |
|
408 FileService::FileStorageInfo::GetLockedFileQueue(LockedFile* aLockedFile) |
|
409 { |
|
410 uint32_t count = mLockedFileQueues.Length(); |
|
411 for (uint32_t index = 0; index < count; index++) { |
|
412 nsRefPtr<LockedFileQueue>& lockedFileQueue = mLockedFileQueues[index]; |
|
413 if (lockedFileQueue->mLockedFile == aLockedFile) { |
|
414 return lockedFileQueue; |
|
415 } |
|
416 } |
|
417 return nullptr; |
|
418 } |
|
419 |
|
420 void |
|
421 FileService::FileStorageInfo::RemoveLockedFileQueue(LockedFile* aLockedFile) |
|
422 { |
|
423 for (uint32_t index = 0; index < mDelayedEnqueueInfos.Length(); index++) { |
|
424 if (mDelayedEnqueueInfos[index].mLockedFile == aLockedFile) { |
|
425 NS_ASSERTION(!mDelayedEnqueueInfos[index].mFileHelper, "Should be null!"); |
|
426 mDelayedEnqueueInfos.RemoveElementAt(index); |
|
427 return; |
|
428 } |
|
429 } |
|
430 |
|
431 uint32_t lockedFileCount = mLockedFileQueues.Length(); |
|
432 |
|
433 // We can't just remove entries from lock hash tables, we have to rebuild |
|
434 // them instead. Multiple LockedFile objects may lock the same file |
|
435 // (one entry can represent multiple locks). |
|
436 |
|
437 mFilesReading.Clear(); |
|
438 mFilesWriting.Clear(); |
|
439 |
|
440 for (uint32_t index = 0, count = lockedFileCount; index < count; index++) { |
|
441 LockedFile* lockedFile = mLockedFileQueues[index]->mLockedFile; |
|
442 if (lockedFile == aLockedFile) { |
|
443 NS_ASSERTION(count == lockedFileCount, "More than one match?!"); |
|
444 |
|
445 mLockedFileQueues.RemoveElementAt(index); |
|
446 index--; |
|
447 count--; |
|
448 |
|
449 continue; |
|
450 } |
|
451 |
|
452 const nsAString& fileName = lockedFile->mFileHandle->mFileName; |
|
453 |
|
454 if (lockedFile->mMode == FileMode::Readwrite) { |
|
455 if (!IsFileLockedForWriting(fileName)) { |
|
456 LockFileForWriting(fileName); |
|
457 } |
|
458 } |
|
459 else { |
|
460 if (!IsFileLockedForReading(fileName)) { |
|
461 LockFileForReading(fileName); |
|
462 } |
|
463 } |
|
464 } |
|
465 |
|
466 NS_ASSERTION(mLockedFileQueues.Length() == lockedFileCount - 1, |
|
467 "Didn't find the locked file we were looking for!"); |
|
468 |
|
469 nsTArray<DelayedEnqueueInfo> delayedEnqueueInfos; |
|
470 delayedEnqueueInfos.SwapElements(mDelayedEnqueueInfos); |
|
471 |
|
472 for (uint32_t index = 0; index < delayedEnqueueInfos.Length(); index++) { |
|
473 DelayedEnqueueInfo& delayedEnqueueInfo = delayedEnqueueInfos[index]; |
|
474 if (NS_FAILED(gInstance->Enqueue(delayedEnqueueInfo.mLockedFile, |
|
475 delayedEnqueueInfo.mFileHelper))) { |
|
476 NS_WARNING("Enqueue failed!"); |
|
477 } |
|
478 } |
|
479 } |
|
480 |
|
481 bool |
|
482 FileService::FileStorageInfo::HasRunningLockedFiles( |
|
483 nsIFileStorage* aFileStorage) |
|
484 { |
|
485 for (uint32_t index = 0; index < mLockedFileQueues.Length(); index++) { |
|
486 LockedFile* lockedFile = mLockedFileQueues[index]->mLockedFile; |
|
487 if (lockedFile->mFileHandle->mFileStorage == aFileStorage) { |
|
488 return true; |
|
489 } |
|
490 } |
|
491 return false; |
|
492 } |
|
493 |
|
494 FileService::DelayedEnqueueInfo* |
|
495 FileService::FileStorageInfo::CreateDelayedEnqueueInfo(LockedFile* aLockedFile, |
|
496 FileHelper* aFileHelper) |
|
497 { |
|
498 DelayedEnqueueInfo* info = mDelayedEnqueueInfos.AppendElement(); |
|
499 info->mLockedFile = aLockedFile; |
|
500 info->mFileHelper = aFileHelper; |
|
501 return info; |
|
502 } |
|
503 |
|
504 void |
|
505 FileService::FileStorageInfo::CollectRunningAndDelayedLockedFiles( |
|
506 nsIFileStorage* aFileStorage, |
|
507 nsTArray<nsRefPtr<LockedFile> >& aLockedFiles) |
|
508 { |
|
509 for (uint32_t index = 0; index < mLockedFileQueues.Length(); index++) { |
|
510 LockedFile* lockedFile = mLockedFileQueues[index]->mLockedFile; |
|
511 if (lockedFile->mFileHandle->mFileStorage == aFileStorage) { |
|
512 aLockedFiles.AppendElement(lockedFile); |
|
513 } |
|
514 } |
|
515 |
|
516 for (uint32_t index = 0; index < mDelayedEnqueueInfos.Length(); index++) { |
|
517 LockedFile* lockedFile = mDelayedEnqueueInfos[index].mLockedFile; |
|
518 if (lockedFile->mFileHandle->mFileStorage == aFileStorage) { |
|
519 aLockedFiles.AppendElement(lockedFile); |
|
520 } |
|
521 } |
|
522 } |