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
16 * The Original Code is Oracle Corporation code.
18 * The Initial Developer of the Original Code is
20 * Portions created by the Initial Developer are Copyright (C) 2004
21 * the Initial Developer. All Rights Reserved.
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"
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"
61 #include "test_quota.c"
63 #include "nsIPromptService.h"
64 #include "nsIMemoryReporter.h"
66 #include "mozilla/FunctionTimer.h"
70 class QuotaCallbackData
73 QuotaCallbackData(mozIStorageQuotaCallback
*aCallback
,
74 nsISupports
*aUserData
)
75 : callback(aCallback
), userData(aUserData
)
77 MOZ_COUNT_CTOR(QuotaCallbackData
);
82 MOZ_COUNT_DTOR(QuotaCallbackData
);
85 static void Callback(const char *zFilename
,
86 sqlite3_int64
*piLimit
,
90 NS_ASSERTION(zFilename
&& strlen(zFilename
), "Null or empty filename!");
91 NS_ASSERTION(piLimit
, "Null pointer!");
93 QuotaCallbackData
*data
= static_cast<QuotaCallbackData
*>(pArg
);
95 // No callback specified, return immediately.
99 NS_ASSERTION(data
->callback
, "Should never have a null callback!");
101 nsDependentCString
filename(zFilename
);
104 if (NS_SUCCEEDED(data
->callback
->QuotaExceeded(filename
, *piLimit
,
105 iSize
, data
->userData
,
111 static void Destroy(void *aUserData
)
113 delete static_cast<QuotaCallbackData
*>(aUserData
);
117 nsCOMPtr
<mozIStorageQuotaCallback
> callback
;
118 nsCOMPtr
<nsISupports
> userData
;
121 } // anonymous namespace
123 ////////////////////////////////////////////////////////////////////////////////
126 #define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous"
127 #define PREF_TS_SYNCHRONOUS_DEFAULT 1
132 ////////////////////////////////////////////////////////////////////////////////
133 //// Memory Reporting
136 GetStorageSQLitePageCacheMemoryUsed(void *)
139 int rc
= ::sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW
, ¤t
, &high
,
141 return rc
== SQLITE_OK
? current
: 0;
145 GetStorageSQLiteOtherMemoryUsed(void *)
147 int pageCacheCurrent
, pageCacheHigh
;
148 int rc
= ::sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW
, &pageCacheCurrent
,
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
,
159 NS_MEMORY_REPORTER_IMPLEMENT(StorageSQLiteOtherMemoryUsed
,
160 "storage/sqlite/other",
161 "Memory in use by SQLite for other various reasons",
162 GetStorageSQLiteOtherMemoryUsed
,
165 ////////////////////////////////////////////////////////////////////////////////
168 class ServiceMainThreadInitializer
: public nsRunnable
171 ServiceMainThreadInitializer(nsIObserver
*aObserver
,
172 nsIXPConnect
**aXPConnectPtr
,
173 PRInt32
*aSynchronousPrefValPtr
)
174 : mObserver(aObserver
)
175 , mXPConnectPtr(aXPConnectPtr
)
176 , mSynchronousPrefValPtr(aSynchronousPrefValPtr
)
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
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
;
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
));
220 nsIObserver
*mObserver
;
221 nsIXPConnect
**mXPConnectPtr
;
222 PRInt32
*mSynchronousPrefValPtr
;
225 ////////////////////////////////////////////////////////////////////////////////
228 NS_IMPL_THREADSAFE_ISUPPORTS3(
232 mozIStorageServiceQuotaManagement
235 Service
*Service::gService
= nsnull
;
238 Service::getSingleton()
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
248 if (SQLITE_VERSION_NUMBER
> ::sqlite3_libversion_number()) {
249 nsCOMPtr
<nsIPromptService
> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID
));
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 "
256 (void)ps
->Alert(nsnull
, title
.get(), message
.get());
261 gService
= new Service();
264 if (NS_FAILED(gService
->initialize()))
265 NS_RELEASE(gService
);
271 nsIXPConnect
*Service::sXPConnect
= nsnull
;
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
);
286 xpc
= do_GetService(nsIXPConnect::GetCID());
287 NS_ASSERTION(xpc
, "Could not get XPConnect!");
291 PRInt32
Service::sSynchronousPref
;
295 Service::getSynchronousPref()
297 return sSynchronousPref
;
301 : mMutex("Service::mMutex")
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();
311 NS_WARNING("sqlite3 did not shutdown cleanly.");
313 rc
= ::sqlite3_shutdown();
315 NS_WARNING("sqlite3 did not shutdown cleanly.");
317 bool shutdownObserved
= !sXPConnect
;
318 NS_ASSERTION(shutdownObserved
, "Shutdown was not observed!");
326 NS_IF_RELEASE(sXPConnect
);
330 Service::initialize()
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();
341 return convertResultCode(rc
);
343 rc
= ::sqlite3_quota_initialize(NULL
, 0);
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()) {
358 (void)::NS_DispatchToMainThread(event
);
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();
376 NS_ERROR("Storage service has no collation");
381 nsresult rv
= coll
->CompareString(aComparisonStrength
, aStr1
, aStr2
, &res
);
383 NS_ERROR("Collation compare string failed");
391 Service::getLocaleCollation()
393 mMutex
.AssertCurrentThreadOwns();
395 if (mLocaleCollation
)
396 return mLocaleCollation
;
398 nsCOMPtr
<nsILocaleService
> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID
));
400 NS_WARNING("Could not get locale service");
404 nsCOMPtr
<nsILocale
> appLocale
;
405 nsresult rv
= svc
->GetApplicationLocale(getter_AddRefs(appLocale
));
407 NS_WARNING("Could not get application locale");
411 nsCOMPtr
<nsICollationFactory
> collFact
=
412 do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID
);
414 NS_WARNING("Could not create collation factory");
418 rv
= collFact
->CreateCollation(appLocale
, getter_AddRefs(mLocaleCollation
));
420 NS_WARNING("Could not create collation");
424 return mLocaleCollation
;
427 ////////////////////////////////////////////////////////////////////////////////
428 //// mozIStorageService
430 #ifndef NS_APP_STORAGE_50_FILE
431 #define NS_APP_STORAGE_50_FILE "UStor"
435 Service::OpenSpecialDatabase(const char *aStorageKey
,
436 mozIStorageConnection
**_connection
)
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
);
452 storageFile
->GetPath(filename
);
453 nsCString filename8
= NS_ConvertUTF16toUTF8(filename
.get());
454 // fall through to DB initialization
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
);
471 Service::OpenDatabase(nsIFile
*aDatabaseFile
,
472 mozIStorageConnection
**_connection
)
474 NS_ENSURE_ARG(aDatabaseFile
);
476 #ifdef NS_FUNCTION_TIMER
478 (void)aDatabaseFile
->GetNativeLeafName(leafname
);
479 NS_TIME_FUNCTION_FMT("mozIStorageService::OpenDatabase(%s)", leafname
.get());
482 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
484 int flags
= SQLITE_OPEN_READWRITE
| SQLITE_OPEN_SHAREDCACHE
|
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
);
497 Service::OpenUnsharedDatabase(nsIFile
*aDatabaseFile
,
498 mozIStorageConnection
**_connection
)
500 #ifdef NS_FUNCTION_TIMER
502 (void)aDatabaseFile
->GetNativeLeafName(leafname
);
503 NS_TIME_FUNCTION_FMT("mozIStorageService::OpenUnsharedDatabase(%s)",
507 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
509 int flags
= SQLITE_OPEN_READWRITE
| SQLITE_OPEN_PRIVATECACHE
|
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
);
522 Service::BackupDatabaseFile(nsIFile
*aDBFile
,
523 const nsAString
&aBackupFileName
,
524 nsIFile
*aBackupParentDirectory
,
528 nsCOMPtr
<nsIFile
> parentDir
= aBackupParentDirectory
;
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 ////////////////////////////////////////////////////////////////////////////////
562 Service::Observe(nsISupports
*, const char *aTopic
, const PRUnichar
*)
564 if (strcmp(aTopic
, "xpcom-shutdown") == 0)
569 ////////////////////////////////////////////////////////////////////////////////
570 //// mozIStorageServiceQuotaManagement
573 Service::OpenDatabaseWithVFS(nsIFile
*aDatabaseFile
,
574 const nsACString
&aVFSName
,
575 mozIStorageConnection
**_connection
)
577 NS_ENSURE_ARG(aDatabaseFile
);
579 #ifdef NS_FUNCTION_TIMER
581 (void)aDatabaseFile
->GetNativeLeafName(leafname
);
582 NS_TIME_FUNCTION_FMT("mozIStorageService::OpenDatabaseWithVFS(%s)",
586 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
588 int flags
= SQLITE_OPEN_READWRITE
| SQLITE_OPEN_SHAREDCACHE
|
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
);
602 Service::SetQuotaForFilenamePattern(const nsACString
&aPattern
,
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
));
623 } // namespace storage
624 } // namespace mozilla