Bug 634734 - Fennec ASSERTION: mFUnitsConvFactor not valid: mFUnitsConvFactor > 0...
[mozilla-central.git] / storage / src / mozStorageService.cpp
blob01f1c05adcb2bb5303502eb12475d72095915901
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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.
16 * The Original Code is Oracle Corporation code.
18 * The Initial Developer of the Original Code is
19 * Oracle Corporation
20 * Portions created by the Initial Developer are Copyright (C) 2004
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
25 * Brett Wilson <brettw@gmail.com>
26 * Shawn Wilsher <me@shawnwilsher.com>
27 * Drew Willcoxon <adw@mozilla.com>
29 * Alternatively, the contents of this file may be used under the terms of
30 * either the GNU General Public License Version 2 or later (the "GPL"), or
31 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 * in which case the provisions of the GPL or the LGPL are applicable instead
33 * of those above. If you wish to allow use of your version of this file only
34 * under the terms of either the GPL or the LGPL, and not to allow others to
35 * use your version of this file under the terms of the MPL, indicate your
36 * decision by deleting the provisions above and replace them with the notice
37 * and other provisions required by the GPL or the LGPL. If you do not delete
38 * the provisions above, a recipient may use your version of this file under
39 * the terms of any one of the MPL, the GPL or the LGPL.
41 * ***** END LICENSE BLOCK ***** */
43 #include "mozStorageService.h"
44 #include "mozStorageConnection.h"
45 #include "prinit.h"
46 #include "pratom.h"
47 #include "nsAutoPtr.h"
48 #include "nsCollationCID.h"
49 #include "nsEmbedCID.h"
50 #include "nsThreadUtils.h"
51 #include "mozStoragePrivateHelpers.h"
52 #include "nsILocale.h"
53 #include "nsILocaleService.h"
54 #include "nsIXPConnect.h"
55 #include "nsIObserverService.h"
56 #include "mozilla/Services.h"
57 #include "nsIPrefService.h"
58 #include "nsIPrefBranch.h"
60 #include "sqlite3.h"
61 #include "test_quota.c"
63 #include "nsIPromptService.h"
64 #include "nsIMemoryReporter.h"
66 #include "mozilla/FunctionTimer.h"
68 namespace {
70 class QuotaCallbackData
72 public:
73 QuotaCallbackData(mozIStorageQuotaCallback *aCallback,
74 nsISupports *aUserData)
75 : callback(aCallback), userData(aUserData)
77 MOZ_COUNT_CTOR(QuotaCallbackData);
80 ~QuotaCallbackData()
82 MOZ_COUNT_DTOR(QuotaCallbackData);
85 static void Callback(const char *zFilename,
86 sqlite3_int64 *piLimit,
87 sqlite3_int64 iSize,
88 void *pArg)
90 NS_ASSERTION(zFilename && strlen(zFilename), "Null or empty filename!");
91 NS_ASSERTION(piLimit, "Null pointer!");
93 QuotaCallbackData *data = static_cast<QuotaCallbackData*>(pArg);
94 if (!data) {
95 // No callback specified, return immediately.
96 return;
99 NS_ASSERTION(data->callback, "Should never have a null callback!");
101 nsDependentCString filename(zFilename);
103 PRInt64 newLimit;
104 if (NS_SUCCEEDED(data->callback->QuotaExceeded(filename, *piLimit,
105 iSize, data->userData,
106 &newLimit))) {
107 *piLimit = newLimit;
111 static void Destroy(void *aUserData)
113 delete static_cast<QuotaCallbackData*>(aUserData);
116 private:
117 nsCOMPtr<mozIStorageQuotaCallback> callback;
118 nsCOMPtr<nsISupports> userData;
121 } // anonymous namespace
123 ////////////////////////////////////////////////////////////////////////////////
124 //// Defines
126 #define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous"
127 #define PREF_TS_SYNCHRONOUS_DEFAULT 1
129 namespace mozilla {
130 namespace storage {
132 ////////////////////////////////////////////////////////////////////////////////
133 //// Memory Reporting
135 static PRInt64
136 GetStorageSQLitePageCacheMemoryUsed(void *)
138 int current, high;
139 int rc = ::sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &current, &high,
141 return rc == SQLITE_OK ? current : 0;
144 static PRInt64
145 GetStorageSQLiteOtherMemoryUsed(void *)
147 int pageCacheCurrent, pageCacheHigh;
148 int rc = ::sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &pageCacheCurrent,
149 &pageCacheHigh, 0);
150 return rc == SQLITE_OK ? ::sqlite3_memory_used() - pageCacheCurrent : 0;
153 NS_MEMORY_REPORTER_IMPLEMENT(StorageSQLitePageCacheMemoryUsed,
154 "storage/sqlite/pagecache",
155 "Memory in use by SQLite for the page cache",
156 GetStorageSQLitePageCacheMemoryUsed,
157 nsnull)
159 NS_MEMORY_REPORTER_IMPLEMENT(StorageSQLiteOtherMemoryUsed,
160 "storage/sqlite/other",
161 "Memory in use by SQLite for other various reasons",
162 GetStorageSQLiteOtherMemoryUsed,
163 nsnull)
165 ////////////////////////////////////////////////////////////////////////////////
166 //// Helpers
168 class ServiceMainThreadInitializer : public nsRunnable
170 public:
171 ServiceMainThreadInitializer(nsIObserver *aObserver,
172 nsIXPConnect **aXPConnectPtr,
173 PRInt32 *aSynchronousPrefValPtr)
174 : mObserver(aObserver)
175 , mXPConnectPtr(aXPConnectPtr)
176 , mSynchronousPrefValPtr(aSynchronousPrefValPtr)
180 NS_IMETHOD Run()
182 NS_PRECONDITION(NS_IsMainThread(), "Must be running on the main thread!");
184 // NOTE: All code that can only run on the main thread and needs to be run
185 // during initialization should be placed here. During the off-
186 // chance that storage is initialized on a background thread, this
187 // will ensure everything that isn't threadsafe is initialized in
188 // the right place.
190 // Register for xpcom-shutdown so we can cleanup after ourselves. The
191 // observer service can only be used on the main thread.
192 nsCOMPtr<nsIObserverService> os =
193 mozilla::services::GetObserverService();
194 NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
195 nsresult rv = os->AddObserver(mObserver, "xpcom-shutdown", PR_FALSE);
196 NS_ENSURE_SUCCESS(rv, rv);
198 // We cache XPConnect for our language helpers. XPConnect can only be
199 // used on the main thread.
200 (void)CallGetService(nsIXPConnect::GetCID(), mXPConnectPtr);
202 // We need to obtain the toolkit.storage.synchronous preferences on the main
203 // thread because the preference service can only be accessed there. This
204 // is cached in the service for all future Open[Unshared]Database calls.
205 nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID));
206 PRInt32 synchronous = PREF_TS_SYNCHRONOUS_DEFAULT;
207 if (pref)
208 (void)pref->GetIntPref(PREF_TS_SYNCHRONOUS, &synchronous);
209 ::PR_AtomicSet(mSynchronousPrefValPtr, synchronous);
211 // Register our SQLite memory reporters. Registration can only happen on
212 // the main thread (otherwise you'll get cryptic crashes).
213 NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(StorageSQLitePageCacheMemoryUsed));
214 NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(StorageSQLiteOtherMemoryUsed));
216 return NS_OK;
219 private:
220 nsIObserver *mObserver;
221 nsIXPConnect **mXPConnectPtr;
222 PRInt32 *mSynchronousPrefValPtr;
225 ////////////////////////////////////////////////////////////////////////////////
226 //// Service
228 NS_IMPL_THREADSAFE_ISUPPORTS3(
229 Service,
230 mozIStorageService,
231 nsIObserver,
232 mozIStorageServiceQuotaManagement
235 Service *Service::gService = nsnull;
237 Service *
238 Service::getSingleton()
240 if (gService) {
241 NS_ADDREF(gService);
242 return gService;
245 // Ensure that we are using the same version of SQLite that we compiled with
246 // or newer. Our configure check ensures we are using a new enough version
247 // at compile time.
248 if (SQLITE_VERSION_NUMBER > ::sqlite3_libversion_number()) {
249 nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
250 if (ps) {
251 nsAutoString title, message;
252 title.AppendASCII("SQLite Version Error");
253 message.AppendASCII("The application has been updated, but your version "
254 "of SQLite is too old and the application cannot "
255 "run.");
256 (void)ps->Alert(nsnull, title.get(), message.get());
258 ::PR_Abort();
261 gService = new Service();
262 if (gService) {
263 NS_ADDREF(gService);
264 if (NS_FAILED(gService->initialize()))
265 NS_RELEASE(gService);
268 return gService;
271 nsIXPConnect *Service::sXPConnect = nsnull;
273 // static
274 already_AddRefed<nsIXPConnect>
275 Service::getXPConnect()
277 NS_PRECONDITION(NS_IsMainThread(),
278 "Must only get XPConnect on the main thread!");
279 NS_PRECONDITION(gService,
280 "Can not get XPConnect without an instance of our service!");
282 // If we've been shutdown, sXPConnect will be null. To prevent leaks, we do
283 // not cache the service after this point.
284 nsCOMPtr<nsIXPConnect> xpc(sXPConnect);
285 if (!xpc)
286 xpc = do_GetService(nsIXPConnect::GetCID());
287 NS_ASSERTION(xpc, "Could not get XPConnect!");
288 return xpc.forget();
291 PRInt32 Service::sSynchronousPref;
293 // static
294 PRInt32
295 Service::getSynchronousPref()
297 return sSynchronousPref;
300 Service::Service()
301 : mMutex("Service::mMutex")
305 Service::~Service()
307 // Shutdown the sqlite3 API. Warn if shutdown did not turn out okay, but
308 // there is nothing actionable we can do in that case.
309 int rc = ::sqlite3_quota_shutdown();
310 if (rc != SQLITE_OK)
311 NS_WARNING("sqlite3 did not shutdown cleanly.");
313 rc = ::sqlite3_shutdown();
314 if (rc != SQLITE_OK)
315 NS_WARNING("sqlite3 did not shutdown cleanly.");
317 bool shutdownObserved = !sXPConnect;
318 NS_ASSERTION(shutdownObserved, "Shutdown was not observed!");
320 gService = nsnull;
323 void
324 Service::shutdown()
326 NS_IF_RELEASE(sXPConnect);
329 nsresult
330 Service::initialize()
332 NS_TIME_FUNCTION;
334 int rc;
336 // Explicitly initialize sqlite3. Although this is implicitly called by
337 // various sqlite3 functions (and the sqlite3_open calls in our case),
338 // the documentation suggests calling this directly. So we do.
339 rc = ::sqlite3_initialize();
340 if (rc != SQLITE_OK)
341 return convertResultCode(rc);
343 rc = ::sqlite3_quota_initialize(NULL, 0);
344 if (rc != SQLITE_OK)
345 return convertResultCode(rc);
347 // Set the default value for the toolkit.storage.synchronous pref. It will be
348 // updated with the user preference on the main thread.
349 sSynchronousPref = PREF_TS_SYNCHRONOUS_DEFAULT;
351 // Run the things that need to run on the main thread there.
352 nsCOMPtr<nsIRunnable> event =
353 new ServiceMainThreadInitializer(this, &sXPConnect, &sSynchronousPref);
354 if (event && ::NS_IsMainThread()) {
355 (void)event->Run();
357 else {
358 (void)::NS_DispatchToMainThread(event);
361 return NS_OK;
365 Service::localeCompareStrings(const nsAString &aStr1,
366 const nsAString &aStr2,
367 PRInt32 aComparisonStrength)
369 // The implementation of nsICollation.CompareString() is platform-dependent.
370 // On Linux it's not thread-safe. It may not be on Windows and OS X either,
371 // but it's more difficult to tell. We therefore synchronize this method.
372 MutexAutoLock mutex(mMutex);
374 nsICollation *coll = getLocaleCollation();
375 if (!coll) {
376 NS_ERROR("Storage service has no collation");
377 return 0;
380 PRInt32 res;
381 nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res);
382 if (NS_FAILED(rv)) {
383 NS_ERROR("Collation compare string failed");
384 return 0;
387 return res;
390 nsICollation *
391 Service::getLocaleCollation()
393 mMutex.AssertCurrentThreadOwns();
395 if (mLocaleCollation)
396 return mLocaleCollation;
398 nsCOMPtr<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID));
399 if (!svc) {
400 NS_WARNING("Could not get locale service");
401 return nsnull;
404 nsCOMPtr<nsILocale> appLocale;
405 nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale));
406 if (NS_FAILED(rv)) {
407 NS_WARNING("Could not get application locale");
408 return nsnull;
411 nsCOMPtr<nsICollationFactory> collFact =
412 do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
413 if (!collFact) {
414 NS_WARNING("Could not create collation factory");
415 return nsnull;
418 rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation));
419 if (NS_FAILED(rv)) {
420 NS_WARNING("Could not create collation");
421 return nsnull;
424 return mLocaleCollation;
427 ////////////////////////////////////////////////////////////////////////////////
428 //// mozIStorageService
430 #ifndef NS_APP_STORAGE_50_FILE
431 #define NS_APP_STORAGE_50_FILE "UStor"
432 #endif
434 NS_IMETHODIMP
435 Service::OpenSpecialDatabase(const char *aStorageKey,
436 mozIStorageConnection **_connection)
438 nsresult rv;
440 nsCOMPtr<nsIFile> storageFile;
441 if (::strcmp(aStorageKey, "memory") == 0) {
442 // just fall through with NULL storageFile, this will cause the storage
443 // connection to use a memory DB.
445 else if (::strcmp(aStorageKey, "profile") == 0) {
447 rv = NS_GetSpecialDirectory(NS_APP_STORAGE_50_FILE,
448 getter_AddRefs(storageFile));
449 NS_ENSURE_SUCCESS(rv, rv);
451 nsString filename;
452 storageFile->GetPath(filename);
453 nsCString filename8 = NS_ConvertUTF16toUTF8(filename.get());
454 // fall through to DB initialization
456 else {
457 return NS_ERROR_INVALID_ARG;
460 Connection *msc = new Connection(this, SQLITE_OPEN_READWRITE);
461 NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY);
463 rv = msc->initialize(storageFile);
464 NS_ENSURE_SUCCESS(rv, rv);
466 NS_ADDREF(*_connection = msc);
467 return NS_OK;
470 NS_IMETHODIMP
471 Service::OpenDatabase(nsIFile *aDatabaseFile,
472 mozIStorageConnection **_connection)
474 NS_ENSURE_ARG(aDatabaseFile);
476 #ifdef NS_FUNCTION_TIMER
477 nsCString leafname;
478 (void)aDatabaseFile->GetNativeLeafName(leafname);
479 NS_TIME_FUNCTION_FMT("mozIStorageService::OpenDatabase(%s)", leafname.get());
480 #endif
482 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
483 // reasons.
484 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
485 SQLITE_OPEN_CREATE;
486 nsRefPtr<Connection> msc = new Connection(this, flags);
487 NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY);
489 nsresult rv = msc->initialize(aDatabaseFile);
490 NS_ENSURE_SUCCESS(rv, rv);
492 NS_ADDREF(*_connection = msc);
493 return NS_OK;
496 NS_IMETHODIMP
497 Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile,
498 mozIStorageConnection **_connection)
500 #ifdef NS_FUNCTION_TIMER
501 nsCString leafname;
502 (void)aDatabaseFile->GetNativeLeafName(leafname);
503 NS_TIME_FUNCTION_FMT("mozIStorageService::OpenUnsharedDatabase(%s)",
504 leafname.get());
505 #endif
507 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
508 // reasons.
509 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE |
510 SQLITE_OPEN_CREATE;
511 nsRefPtr<Connection> msc = new Connection(this, flags);
512 NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY);
514 nsresult rv = msc->initialize(aDatabaseFile);
515 NS_ENSURE_SUCCESS(rv, rv);
517 NS_ADDREF(*_connection = msc);
518 return NS_OK;
521 NS_IMETHODIMP
522 Service::BackupDatabaseFile(nsIFile *aDBFile,
523 const nsAString &aBackupFileName,
524 nsIFile *aBackupParentDirectory,
525 nsIFile **backup)
527 nsresult rv;
528 nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
529 if (!parentDir) {
530 // This argument is optional, and defaults to the same parent directory
531 // as the current file.
532 rv = aDBFile->GetParent(getter_AddRefs(parentDir));
533 NS_ENSURE_SUCCESS(rv, rv);
536 nsCOMPtr<nsIFile> backupDB;
537 rv = parentDir->Clone(getter_AddRefs(backupDB));
538 NS_ENSURE_SUCCESS(rv, rv);
540 rv = backupDB->Append(aBackupFileName);
541 NS_ENSURE_SUCCESS(rv, rv);
543 rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
544 NS_ENSURE_SUCCESS(rv, rv);
546 nsAutoString fileName;
547 rv = backupDB->GetLeafName(fileName);
548 NS_ENSURE_SUCCESS(rv, rv);
550 rv = backupDB->Remove(PR_FALSE);
551 NS_ENSURE_SUCCESS(rv, rv);
553 backupDB.forget(backup);
555 return aDBFile->CopyTo(parentDir, fileName);
558 ////////////////////////////////////////////////////////////////////////////////
559 //// nsIObserver
561 NS_IMETHODIMP
562 Service::Observe(nsISupports *, const char *aTopic, const PRUnichar *)
564 if (strcmp(aTopic, "xpcom-shutdown") == 0)
565 shutdown();
566 return NS_OK;
569 ////////////////////////////////////////////////////////////////////////////////
570 //// mozIStorageServiceQuotaManagement
572 NS_IMETHODIMP
573 Service::OpenDatabaseWithVFS(nsIFile *aDatabaseFile,
574 const nsACString &aVFSName,
575 mozIStorageConnection **_connection)
577 NS_ENSURE_ARG(aDatabaseFile);
579 #ifdef NS_FUNCTION_TIMER
580 nsCString leafname;
581 (void)aDatabaseFile->GetNativeLeafName(leafname);
582 NS_TIME_FUNCTION_FMT("mozIStorageService::OpenDatabaseWithVFS(%s)",
583 leafname.get());
584 #endif
586 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
587 // reasons.
588 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
589 SQLITE_OPEN_CREATE;
590 nsRefPtr<Connection> msc = new Connection(this, flags);
591 NS_ENSURE_TRUE(msc, NS_ERROR_OUT_OF_MEMORY);
593 nsresult rv = msc->initialize(aDatabaseFile,
594 PromiseFlatCString(aVFSName).get());
595 NS_ENSURE_SUCCESS(rv, rv);
597 NS_ADDREF(*_connection = msc);
598 return NS_OK;
601 NS_IMETHODIMP
602 Service::SetQuotaForFilenamePattern(const nsACString &aPattern,
603 PRInt64 aSizeLimit,
604 mozIStorageQuotaCallback *aCallback,
605 nsISupports *aUserData)
607 NS_ENSURE_FALSE(aPattern.IsEmpty(), NS_ERROR_INVALID_ARG);
609 nsAutoPtr<QuotaCallbackData> data;
610 if (aSizeLimit && aCallback) {
611 data = new QuotaCallbackData(aCallback, aUserData);
614 int rc = ::sqlite3_quota_set(PromiseFlatCString(aPattern).get(),
615 aSizeLimit, QuotaCallbackData::Callback,
616 data, QuotaCallbackData::Destroy);
617 NS_ENSURE_TRUE(rc == SQLITE_OK, convertResultCode(rc));
619 data.forget();
620 return NS_OK;
623 } // namespace storage
624 } // namespace mozilla