1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Alec Flett <alecf@netscape.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsPrefService.h"
40 #include "nsAppDirectoryServiceDefs.h"
41 #include "nsDirectoryServiceDefs.h"
42 #include "nsICategoryManager.h"
43 #include "nsCategoryManagerUtils.h"
44 #include "nsNetUtil.h"
46 #include "nsILocalFile.h"
47 #include "nsIObserverService.h"
48 #include "nsPrefBranch.h"
49 #include "nsXPIDLString.h"
51 #include "nsCOMArray.h"
52 #include "nsXPCOMCID.h"
53 #include "nsAutoPtr.h"
55 #include "nsQuickSort.h"
61 #include "prefapi_private_data.h"
63 #include "nsITimelineService.h"
66 #define INITIAL_PREF_FILES 10
69 static nsresult
openPrefFile(nsIFile
* aFile
);
70 static nsresult
pref_InitInitialObjects(void);
72 //-----------------------------------------------------------------------------
75 * Constructor/Destructor
78 nsPrefService::nsPrefService()
79 : mDontWriteUserPrefs(PR_FALSE
)
83 nsPrefService::~nsPrefService()
90 * nsISupports Implementation
93 NS_IMPL_THREADSAFE_ADDREF(nsPrefService
)
94 NS_IMPL_THREADSAFE_RELEASE(nsPrefService
)
96 NS_INTERFACE_MAP_BEGIN(nsPrefService
)
97 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsIPrefService
)
98 NS_INTERFACE_MAP_ENTRY(nsIPrefService
)
99 NS_INTERFACE_MAP_ENTRY(nsIObserver
)
100 NS_INTERFACE_MAP_ENTRY(nsIPrefBranch
)
101 NS_INTERFACE_MAP_ENTRY(nsIPrefBranch2
)
102 NS_INTERFACE_MAP_ENTRY(nsIPrefBranchInternal
)
103 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference
)
108 * nsIPrefService Implementation
111 nsresult
nsPrefService::Init()
113 nsPrefBranch
*rootBranch
= new nsPrefBranch("", PR_FALSE
);
115 return NS_ERROR_OUT_OF_MEMORY
;
117 mRootBranch
= (nsIPrefBranch2
*)rootBranch
;
119 nsXPIDLCString lockFileName
;
123 NS_ENSURE_SUCCESS(rv
, rv
);
125 rv
= pref_InitInitialObjects();
126 NS_ENSURE_SUCCESS(rv
, rv
);
129 * The following is a small hack which will allow us to only load the library
130 * which supports the netscape.cfg file if the preference is defined. We
131 * test for the existence of the pref, set in the all.js (mozilla) or
132 * all-ns.js (netscape 6), and if it exists we startup the pref config
133 * category which will do the rest.
136 rv
= mRootBranch
->GetCharPref("general.config.filename", getter_Copies(lockFileName
));
137 if (NS_SUCCEEDED(rv
))
138 NS_CreateServicesFromCategory("pref-config-startup",
139 static_cast<nsISupports
*>(static_cast<void *>(this)),
140 "pref-config-startup");
142 nsCOMPtr
<nsIObserverService
> observerService
=
143 do_GetService("@mozilla.org/observer-service;1", &rv
);
144 if (observerService
) {
145 rv
= observerService
->AddObserver(this, "profile-before-change", PR_TRUE
);
146 if (NS_SUCCEEDED(rv
)) {
147 rv
= observerService
->AddObserver(this, "profile-do-change", PR_TRUE
);
154 NS_IMETHODIMP
nsPrefService::Observe(nsISupports
*aSubject
, const char *aTopic
, const PRUnichar
*someData
)
158 if (!nsCRT::strcmp(aTopic
, "profile-before-change")) {
159 if (!nsCRT::strcmp(someData
, NS_LITERAL_STRING("shutdown-cleanse").get())) {
161 mCurrentFile
->Remove(PR_FALSE
);
162 mCurrentFile
= nsnull
;
165 rv
= SavePrefFile(nsnull
);
167 } else if (!nsCRT::strcmp(aTopic
, "profile-do-change")) {
169 rv
= ReadUserPrefs(nsnull
);
170 } else if (!nsCRT::strcmp(aTopic
, "reload-default-prefs")) {
171 // Reload the default prefs from file.
172 pref_InitInitialObjects();
178 NS_IMETHODIMP
nsPrefService::ReadUserPrefs(nsIFile
*aFile
)
182 if (nsnull
== aFile
) {
183 rv
= UseDefaultPrefFile();
186 NotifyServiceObservers(NS_PREFSERVICE_READ_TOPIC_ID
);
189 rv
= ReadAndOwnUserPrefFile(aFile
);
194 NS_IMETHODIMP
nsPrefService::ResetPrefs()
196 NotifyServiceObservers(NS_PREFSERVICE_RESET_TOPIC_ID
);
199 nsresult rv
= PREF_Init();
200 NS_ENSURE_SUCCESS(rv
, rv
);
202 return pref_InitInitialObjects();
205 NS_IMETHODIMP
nsPrefService::ResetUserPrefs()
207 PREF_ClearAllUserPrefs();
211 NS_IMETHODIMP
nsPrefService::SavePrefFile(nsIFile
*aFile
)
213 return SavePrefFileInternal(aFile
);
216 NS_IMETHODIMP
nsPrefService::GetBranch(const char *aPrefRoot
, nsIPrefBranch
**_retval
)
220 if ((nsnull
!= aPrefRoot
) && (*aPrefRoot
!= '\0')) {
221 // TODO: - cache this stuff and allow consumers to share branches (hold weak references I think)
222 nsPrefBranch
* prefBranch
= new nsPrefBranch(aPrefRoot
, PR_FALSE
);
224 return NS_ERROR_OUT_OF_MEMORY
;
226 rv
= CallQueryInterface(prefBranch
, _retval
);
228 // special case caching the default root
229 rv
= CallQueryInterface(mRootBranch
, _retval
);
234 NS_IMETHODIMP
nsPrefService::GetDefaultBranch(const char *aPrefRoot
, nsIPrefBranch
**_retval
)
238 // TODO: - cache this stuff and allow consumers to share branches (hold weak references I think)
239 nsPrefBranch
* prefBranch
= new nsPrefBranch(aPrefRoot
, PR_TRUE
);
241 return NS_ERROR_OUT_OF_MEMORY
;
243 rv
= CallQueryInterface(prefBranch
, _retval
);
248 nsresult
nsPrefService::NotifyServiceObservers(const char *aTopic
)
251 nsCOMPtr
<nsIObserverService
> observerService
=
252 do_GetService("@mozilla.org/observer-service;1", &rv
);
254 if (NS_FAILED(rv
) || !observerService
)
257 nsISupports
*subject
= (nsISupports
*)((nsIPrefService
*)this);
258 observerService
->NotifyObservers(subject
, aTopic
, nsnull
);
263 nsresult
nsPrefService::UseDefaultPrefFile()
266 nsCOMPtr
<nsIFile
> aFile
;
268 rv
= NS_GetSpecialDirectory(NS_APP_PREFS_50_FILE
, getter_AddRefs(aFile
));
269 if (NS_SUCCEEDED(rv
)) {
270 rv
= ReadAndOwnUserPrefFile(aFile
);
271 // Most likely cause of failure here is that the file didn't
272 // exist, so save a new one. mUserPrefReadFailed will be
273 // used to catch an error in actually reading the file.
275 rv2
= SavePrefFileInternal(aFile
);
276 NS_ASSERTION(NS_SUCCEEDED(rv2
), "Failed to save new shared pref file");
283 nsresult
nsPrefService::UseUserPrefFile()
286 nsCOMPtr
<nsIFile
> aFile
;
287 nsDependentCString
prefsDirProp(NS_APP_PREFS_50_DIR
);
289 rv
= NS_GetSpecialDirectory(prefsDirProp
.get(), getter_AddRefs(aFile
));
290 if (NS_SUCCEEDED(rv
) && aFile
) {
291 rv
= aFile
->AppendNative(NS_LITERAL_CSTRING("user.js"));
292 if (NS_SUCCEEDED(rv
)) {
293 PRBool exists
= PR_FALSE
;
294 aFile
->Exists(&exists
);
296 rv
= openPrefFile(aFile
);
298 rv
= NS_ERROR_FILE_NOT_FOUND
;
305 nsresult
nsPrefService::MakeBackupPrefFile(nsIFile
*aFile
)
307 // Example: this copies "prefs.js" to "Invalidprefs.js" in the same directory.
308 nsAutoString newFilename
;
309 nsresult rv
= aFile
->GetLeafName(newFilename
);
310 NS_ENSURE_SUCCESS(rv
, rv
);
311 newFilename
.Insert(NS_LITERAL_STRING("Invalid"), 0);
312 rv
= aFile
->CopyTo(nsnull
, newFilename
);
313 NS_ENSURE_SUCCESS(rv
, rv
);
317 nsresult
nsPrefService::ReadAndOwnUserPrefFile(nsIFile
*aFile
)
319 NS_ENSURE_ARG(aFile
);
321 if (mCurrentFile
== aFile
)
323 mCurrentFile
= aFile
;
326 PRBool exists
= PR_FALSE
;
327 mCurrentFile
->Exists(&exists
);
329 rv
= openPrefFile(mCurrentFile
);
331 mDontWriteUserPrefs
= NS_FAILED(MakeBackupPrefFile(mCurrentFile
));
334 rv
= NS_ERROR_FILE_NOT_FOUND
;
340 nsresult
nsPrefService::SavePrefFileInternal(nsIFile
*aFile
)
342 if (nsnull
== aFile
) {
343 // the gDirty flag tells us if we should write to mCurrentFile
344 // we only check this flag when the caller wants to write to the default
348 // It's possible that we never got a prefs file.
351 rv
= WritePrefFile(mCurrentFile
);
355 return WritePrefFile(aFile
);
359 nsresult
nsPrefService::WritePrefFile(nsIFile
* aFile
)
361 const char outHeader
[] =
362 "# Mozilla User Preferences"
365 "/* Do not edit this file."
369 " * If you make changes to this file while the application is running,"
371 " * the changes will be overwritten when the application exits."
375 " * To make a manual change to preferences, you can visit the URL about:config"
377 " * For more information, see http://www.mozilla.org/unix/customizing.html#prefs"
383 nsCOMPtr
<nsIOutputStream
> outStreamSink
;
384 nsCOMPtr
<nsIOutputStream
> outStream
;
385 PRUint32 writeAmount
;
389 return NS_ERROR_NOT_INITIALIZED
;
391 // Don't save user prefs if there was an error reading them and we failed
392 // to make a backup copy, since all prefs from the error line to the end of
393 // the file would be lost (bug 361102).
394 if (mDontWriteUserPrefs
&& aFile
== mCurrentFile
)
397 // execute a "safe" save by saving through a tempfile
398 rv
= NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink
),
404 rv
= NS_NewBufferedOutputStream(getter_AddRefs(outStream
), outStreamSink
, 4096);
408 char** valueArray
= (char **)PR_Calloc(sizeof(char *), gHashTable
.entryCount
);
410 return NS_ERROR_OUT_OF_MEMORY
;
412 pref_saveArgs saveArgs
;
413 saveArgs
.prefArray
= valueArray
;
414 saveArgs
.saveTypes
= SAVE_ALL
;
416 // get the lines that we're supposed to be writing to the file
417 PL_DHashTableEnumerate(&gHashTable
, pref_savePref
, &saveArgs
);
419 /* Sort the preferences to make a readable file on disk */
420 NS_QuickSort(valueArray
, gHashTable
.entryCount
, sizeof(char *), pref_CompareStrings
, NULL
);
422 // write out the file header
423 outStream
->Write(outHeader
, sizeof(outHeader
) - 1, &writeAmount
);
425 char** walker
= valueArray
;
426 for (PRUint32 valueIdx
= 0; valueIdx
< gHashTable
.entryCount
; valueIdx
++, walker
++) {
428 outStream
->Write(*walker
, strlen(*walker
), &writeAmount
);
429 outStream
->Write(NS_LINEBREAK
, NS_LINEBREAK_LEN
, &writeAmount
);
435 // tell the safe output stream to overwrite the real prefs file
436 // (it'll abort if there were any errors during writing)
437 nsCOMPtr
<nsISafeOutputStream
> safeStream
= do_QueryInterface(outStream
);
438 NS_ASSERTION(safeStream
, "expected a safe output stream!");
440 rv
= safeStream
->Finish();
442 NS_WARNING("failed to save prefs file! possible dataloss");
451 static nsresult
openPrefFile(nsIFile
* aFile
)
453 nsCOMPtr
<nsIInputStream
> inStr
;
458 aFile
->GetNativePath(str
);
459 NS_TIMELINE_MARK_FUNCTION1("load pref file", str
.get());
463 nsresult rv
= NS_NewLocalFileInputStream(getter_AddRefs(inStr
), aFile
);
468 rv
= aFile
->GetFileSize(&fileSize
);
472 nsAutoArrayPtr
<char> fileBuffer(new char[fileSize
]);
473 if (fileBuffer
== nsnull
)
474 return NS_ERROR_OUT_OF_MEMORY
;
477 PREF_InitParseState(&ps
, PREF_ReaderCallback
, NULL
);
479 // Read is not guaranteed to return a buf the size of fileSize,
481 nsresult rv2
= NS_OK
;
483 PRUint32 amtRead
= 0;
484 rv
= inStr
->Read((char*)fileBuffer
, fileSize
, &amtRead
);
485 if (NS_FAILED(rv
) || amtRead
== 0)
487 if (!PREF_ParseBuf(&ps
, fileBuffer
, amtRead
))
488 rv2
= NS_ERROR_FILE_CORRUPTED
;
491 PREF_FinalizeParseState(&ps
);
493 return NS_FAILED(rv
) ? rv
: rv2
;
497 * some stuff that gets called from Pref_Init()
501 pref_CompareFileNames(nsIFile
* aFile1
, nsIFile
* aFile2
, void* /*unused*/)
503 nsCAutoString filename1
, filename2
;
504 aFile1
->GetNativeLeafName(filename1
);
505 aFile2
->GetNativeLeafName(filename2
);
507 return Compare(filename2
, filename1
);
511 * Load default pref files from a directory. The files in the
512 * directory are sorted reverse-alphabetically; a set of "special file
513 * names" may be specified which are loaded after all the others.
516 pref_LoadPrefsInDir(nsIFile
* aDir
, char const *const *aSpecialFiles
, PRUint32 aSpecialFilesCount
)
519 PRBool hasMoreElements
;
521 nsCOMPtr
<nsISimpleEnumerator
> dirIterator
;
523 // this may fail in some normal cases, such as embedders who do not use a GRE
524 rv
= aDir
->GetDirectoryEntries(getter_AddRefs(dirIterator
));
526 // If the directory doesn't exist, then we have no reason to complain. We
527 // loaded everything (and nothing) successfully.
528 if (rv
== NS_ERROR_FILE_NOT_FOUND
)
533 rv
= dirIterator
->HasMoreElements(&hasMoreElements
);
534 NS_ENSURE_SUCCESS(rv
, rv
);
536 nsCOMArray
<nsIFile
> prefFiles(INITIAL_PREF_FILES
);
537 nsCOMArray
<nsIFile
> specialFiles(aSpecialFilesCount
);
538 nsCOMPtr
<nsIFile
> prefFile
;
540 while (hasMoreElements
&& NS_SUCCEEDED(rv
)) {
541 nsCAutoString leafName
;
543 rv
= dirIterator
->GetNext(getter_AddRefs(prefFile
));
548 prefFile
->GetNativeLeafName(leafName
);
549 NS_ASSERTION(!leafName
.IsEmpty(), "Failure in default prefs: directory enumerator returned empty file?");
552 if (StringEndsWith(leafName
, NS_LITERAL_CSTRING(".js"),
553 nsCaseInsensitiveCStringComparator())) {
554 PRBool shouldParse
= PR_TRUE
;
555 // separate out special files
556 for (PRUint32 i
= 0; i
< aSpecialFilesCount
; ++i
) {
557 if (leafName
.Equals(nsDependentCString(aSpecialFiles
[i
]))) {
558 shouldParse
= PR_FALSE
;
559 // special files should be process in order; we put them into
560 // the array by index; this can make the array sparse
561 specialFiles
.ReplaceObjectAt(prefFile
, i
);
566 prefFiles
.AppendObject(prefFile
);
570 rv
= dirIterator
->HasMoreElements(&hasMoreElements
);
573 if (prefFiles
.Count() + specialFiles
.Count() == 0) {
574 NS_WARNING("No default pref files found.");
575 if (NS_SUCCEEDED(rv
)) {
576 rv
= NS_SUCCESS_FILE_DIRECTORY_EMPTY
;
581 prefFiles
.Sort(pref_CompareFileNames
, nsnull
);
583 PRUint32 arrayCount
= prefFiles
.Count();
585 for (i
= 0; i
< arrayCount
; ++i
) {
586 rv2
= openPrefFile(prefFiles
[i
]);
587 if (NS_FAILED(rv2
)) {
588 NS_ERROR("Default pref file not parsed successfully.");
593 arrayCount
= specialFiles
.Count();
594 for (i
= 0; i
< arrayCount
; ++i
) {
595 // this may be a sparse array; test before parsing
596 nsIFile
* file
= specialFiles
[i
];
598 rv2
= openPrefFile(file
);
599 if (NS_FAILED(rv2
)) {
600 NS_ERROR("Special default pref file not parsed successfully.");
609 static nsresult
pref_LoadPrefsInDirList(const char *listId
)
612 nsCOMPtr
<nsIProperties
> dirSvc(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID
, &rv
));
613 if (NS_FAILED(rv
)) return rv
;
615 nsCOMPtr
<nsISimpleEnumerator
> dirList
;
617 NS_GET_IID(nsISimpleEnumerator
),
618 getter_AddRefs(dirList
));
621 while (NS_SUCCEEDED(dirList
->HasMoreElements(&hasMore
)) && hasMore
) {
622 nsCOMPtr
<nsISupports
> elem
;
623 dirList
->GetNext(getter_AddRefs(elem
));
625 nsCOMPtr
<nsIFile
> dir
= do_QueryInterface(elem
);
627 // Do we care if a file provided by this process fails to load?
628 pref_LoadPrefsInDir(dir
, nsnull
, 0);
636 //----------------------------------------------------------------------------------------
637 // Initialize default preference JavaScript buffers from
638 // appropriate TEXT resources
639 //----------------------------------------------------------------------------------------
640 static nsresult
pref_InitInitialObjects()
642 nsCOMPtr
<nsIFile
> aFile
;
643 nsCOMPtr
<nsIFile
> defaultPrefDir
;
646 // first we parse the GRE default prefs. This also works if we're not using a GRE,
648 rv
= NS_GetSpecialDirectory(NS_GRE_DIR
, getter_AddRefs(defaultPrefDir
));
649 NS_ENSURE_SUCCESS(rv
, rv
);
651 rv
= defaultPrefDir
->AppendNative(NS_LITERAL_CSTRING("greprefs"));
652 NS_ENSURE_SUCCESS(rv
, rv
);
654 rv
= pref_LoadPrefsInDir(defaultPrefDir
, nsnull
, 0);
656 NS_WARNING("Error parsing GRE default preferences. Is this an old-style embedding app?");
659 // now parse the "application" default preferences
660 rv
= NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR
, getter_AddRefs(defaultPrefDir
));
661 NS_ENSURE_SUCCESS(rv
, rv
);
663 /* these pref file names should not be used: we process them after all other application pref files for backwards compatibility */
664 static const char* specialFiles
[] = {
665 #if defined(XP_MAC) || defined(XP_MACOSX)
667 #elif defined(XP_WIN)
669 #elif defined(XP_UNIX)
676 #if defined(MOZ_WIDGET_PHOTON)
679 #elif defined(XP_OS2)
681 #elif defined(XP_BEOS)
686 rv
= pref_LoadPrefsInDir(defaultPrefDir
, specialFiles
, NS_ARRAY_LENGTH(specialFiles
));
688 NS_WARNING("Error parsing application default preferences.");
691 rv
= pref_LoadPrefsInDirList(NS_APP_PREFS_DEFAULTS_DIR_LIST
);
692 NS_ENSURE_SUCCESS(rv
, rv
);
694 NS_CreateServicesFromCategory(NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID
,
695 nsnull
, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID
);
697 nsCOMPtr
<nsIObserverService
> observerService
=
698 do_GetService("@mozilla.org/observer-service;1", &rv
);
700 if (NS_FAILED(rv
) || !observerService
)
703 observerService
->NotifyObservers(nsnull
, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID
, nsnull
);
705 return pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST
);