/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 2003 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Conrad Carlen * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsSharedPrefHandler.h" #include "nsPrefService.h" #include "nsServiceManagerUtils.h" #include "nsILocalFile.h" #include "nsAppDirectoryServiceDefs.h" #include "nsReadableUtils.h" #include "ipcMessageReader.h" #include "ipcMessageWriter.h" #if defined(PR_LOGGING) // set NSPR_LOG_MODULES=nsSharedPrefHandler:5 PRLogModuleInfo *gPrefsTransactionObserverLog = nsnull; #define LOG(args) PR_LOG(PrefsTransactionObserver, PR_LOG_DEBUG, args) #else #define LOG(args) #endif nsSharedPrefHandler *gSharedPrefHandler = nsnull; // Constants #define kPrefsTSQueueName NS_LITERAL_CSTRING("prefs") #define kExceptionListFileName NS_LITERAL_CSTRING("nonshared.txt") const char kExceptionListCommentChar = '#'; const PRUint32 kCurrentPrefsTransactionDataVersion = 1; // Static function prototypes static PRBool enumFind(void* aElement, void *aData); static PRBool enumFree(void* aElement, void *aData); static PRInt32 ReadLine(FILE* inStm, nsACString& destString); //**************************************************************************** // Transaction Format for kCurrentPrefsTransactionDataVersion //**************************************************************************** /* NOTE: All string data is null terminated. The data length includes the null character. PRUint32 dataVersion PRUint32 action SET or CLEAR PRUint32 prefNameLen char prefName[prefNameLen] PRUint32 prefValueKind PRUint32 prefValueLen char prefValue[prefValueLen] */ //***************************************************************************** // nsSharedPrefHandler //***************************************************************************** nsSharedPrefHandler::nsSharedPrefHandler() : mPrefService(nsnull), mPrefsTSQueueName("prefs"), mSessionActive(PR_FALSE), mReadingUserPrefs(PR_FALSE), mProcessingTransaction(PR_FALSE) { #if defined(PR_LOGGING) if (!gPrefsTransactionObserverLog) gPrefsTransactionObserverLog = PR_NewLogModule("nsSharedPrefHandler"); #endif } nsSharedPrefHandler::~nsSharedPrefHandler() { mExceptionList.EnumerateForwards(enumFree, nsnull); } nsresult nsSharedPrefHandler::OnSessionBegin() { nsresult rv = EnsureTransactionService(); NS_ENSURE_SUCCESS(rv, rv); // When the reply to this comes (synchronously), we'll actually // read in our data. The transaction service holds a lock on // our data file during our reply. rv = mTransService->Attach(kPrefsTSQueueName, this, PR_TRUE); NS_ASSERTION(NS_SUCCEEDED(rv), "ipcITransactionService::Attach() failed"); if (NS_SUCCEEDED(rv)) mSessionActive = PR_TRUE; return rv; } nsresult nsSharedPrefHandler::OnSessionEnd() { nsresult rv = EnsureTransactionService(); NS_ENSURE_SUCCESS(rv, rv); rv = mTransService->Detach(kPrefsTSQueueName); NS_ASSERTION(NS_SUCCEEDED(rv), "ipcITransactionService::Detach() failed"); mSessionActive = PR_FALSE; return rv; } nsresult nsSharedPrefHandler::OnSavePrefs() { nsresult rv = EnsureTransactionService(); NS_ENSURE_SUCCESS(rv, rv); // When the reply to this comes (synchronously), we'll actually // write out our data. The transaction service holds a lock on // our data file during our reply. rv = mTransService->Flush(kPrefsTSQueueName, PR_TRUE); NS_ASSERTION(NS_SUCCEEDED(rv), "ipcITransactionService::Flush() failed"); return NS_OK; } nsresult nsSharedPrefHandler::OnPrefChanged(PRBool defaultPref, PrefHashEntry* pref, PrefValue newValue) { if (!mSessionActive || defaultPref || !IsPrefShared(pref->key) || mReadingUserPrefs || mProcessingTransaction) return NS_OK; nsresult rv = EnsureTransactionService(); NS_ENSURE_SUCCESS(rv, rv); PRUint32 valueLen, prefNameLen = strlen(pref->key); ipcMessageWriter outMsg(256); outMsg.PutInt32(kCurrentPrefsTransactionDataVersion); outMsg.PutInt32(defaultPref); // XXX: Is not used? outMsg.PutInt32(prefNameLen + 1); outMsg.PutBytes(pref->key, prefNameLen + 1); switch (pref->flags & PREF_VALUETYPE_MASK) { case PREF_STRING: outMsg.PutInt32(PREF_STRING); valueLen = strlen(newValue.stringVal) + 1; outMsg.PutInt32(valueLen); outMsg.PutBytes(newValue.stringVal, valueLen); break; case PREF_INT: outMsg.PutInt32(PREF_INT); outMsg.PutInt32(sizeof(PRInt32)); outMsg.PutInt32(newValue.intVal); break; case PREF_BOOL: outMsg.PutInt32(PREF_BOOL); outMsg.PutInt32(sizeof(PRInt32)); outMsg.PutInt32(newValue.boolVal); break; default: return NS_ERROR_UNEXPECTED; } rv = outMsg.HasError() ? NS_ERROR_FAILURE : NS_OK; NS_ASSERTION(NS_SUCCEEDED(rv), "OnPrefChanged: outMsg failed"); if (NS_SUCCEEDED(rv)) { rv = mTransService->PostTransaction(kPrefsTSQueueName, outMsg.GetBuffer(), outMsg.GetSize()); NS_ASSERTION(NS_SUCCEEDED(rv), "ipcITransactionService::PostTransaction() failed"); } return rv; } PRBool nsSharedPrefHandler::IsPrefShared(const char* prefName) { if (!mExceptionList.Count()) // quick check for empty list return PR_TRUE; // returns PR_TRUE if we reached the end without finding it. return mExceptionList.EnumerateForwards(enumFind, const_cast(prefName)); } //***************************************************************************** // nsSharedPrefHandler Protected Methods //***************************************************************************** nsresult nsSharedPrefHandler::Init(nsPrefService* aOwner) { NS_ENSURE_ARG(aOwner); mPrefService = aOwner; (void)ReadExceptionFile(); return NS_OK; } nsresult nsSharedPrefHandler::ReadExceptionFile() { nsresult rv; nsCOMPtr directoryService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) { nsCOMPtr exceptionFile; rv = directoryService->Get(NS_APP_PREF_DEFAULTS_50_DIR, NS_GET_IID(nsILocalFile), getter_AddRefs(exceptionFile)); if (NS_SUCCEEDED(rv)) { rv = exceptionFile->AppendNative(kExceptionListFileName); if (NS_SUCCEEDED(rv)) { FILE *inStm; rv = exceptionFile->OpenANSIFileDesc("r", &inStm); if (NS_SUCCEEDED(rv)) { nsCAutoString lineStr; while (ReadLine(inStm, lineStr) != EOF) { lineStr.CompressWhitespace(); if (lineStr.IsEmpty() || lineStr.CharAt(0) == kExceptionListCommentChar) continue; char *rawStr = ToNewCString(lineStr); if (!rawStr) { rv = NS_ERROR_OUT_OF_MEMORY; break; } mExceptionList.AppendElement(rawStr); } fclose(inStm); } } } } return rv; } nsresult nsSharedPrefHandler::EnsureTransactionService() { if (mTransService) return NS_OK; nsresult rv; mTransService = do_GetService(IPC_TRANSACTIONSERVICE_CONTRACTID, &rv); return rv; } //***************************************************************************** // nsSharedPrefHandler::nsISupports //***************************************************************************** NS_IMPL_ISUPPORTS1(nsSharedPrefHandler, ipcITransactionObserver) //***************************************************************************** // nsSharedPrefHandler::ipcITransactionObserver //***************************************************************************** NS_IMETHODIMP nsSharedPrefHandler::OnTransactionAvailable(PRUint32 aQueueID, const PRUint8 *aData, PRUint32 aDataLen) { LOG(("nsSharedPrefHandler::OnTransactionAvailable [%s]\n", aData)); ipcMessageReader inMsg(aData, aDataLen); PRUint32 dataVersion, prefAction, dataLen, prefKind, tempInt32; const char *stringStart; dataVersion = inMsg.GetInt32(); NS_ENSURE_TRUE(dataVersion == kCurrentPrefsTransactionDataVersion, NS_ERROR_INVALID_ARG); prefAction = inMsg.GetInt32(); //XXX: Is not used? dataLen = inMsg.GetInt32(); // includes terminating null stringStart = (const char *)inMsg.GetPtr(); nsDependentCString prefNameStr(stringStart); inMsg.AdvancePtr(dataLen); prefKind = inMsg.GetInt32(); dataLen = inMsg.GetInt32(); mProcessingTransaction = PR_TRUE; // Don't generate transactions for these switch (prefKind) { case PREF_STRING: { stringStart = (const char *)inMsg.GetPtr(); nsDependentCString prefStrValueStr(stringStart); inMsg.AdvancePtr(dataLen); NS_ASSERTION(!inMsg.HasError(), "error in reading transaction"); if (!inMsg.HasError()) PREF_SetCharPref(prefNameStr.get(), prefStrValueStr.get()); } break; case PREF_INT: tempInt32 = inMsg.GetInt32(); NS_ASSERTION(!inMsg.HasError(), "error in reading transaction"); if (!inMsg.HasError()) PREF_SetIntPref(prefNameStr.get(), tempInt32); break; case PREF_BOOL: tempInt32 = inMsg.GetInt32(); NS_ASSERTION(!inMsg.HasError(), "error in reading transaction"); if (!inMsg.HasError()) PREF_SetBoolPref(prefNameStr.get(), tempInt32); break; } mProcessingTransaction = PR_FALSE; return NS_OK; } NS_IMETHODIMP nsSharedPrefHandler::OnAttachReply(PRUint32 aQueueID, PRUint32 aStatus) { LOG(("nsSharedPrefHandler::OnAttachReply [%d]\n", aStatus)); // The transaction service holds a lock on the file during this call. mPrefService->ResetUserPrefs(); mPrefService->ReadUserPrefs(nsnull); return NS_OK; } NS_IMETHODIMP nsSharedPrefHandler::OnDetachReply(PRUint32 aQueueID, PRUint32 aStatus) { LOG(("tmModuleTest: nsSharedPrefHandler::OnDetachReply [%d]\n", aStatus)); return NS_OK; } NS_IMETHODIMP nsSharedPrefHandler::OnFlushReply(PRUint32 aQueueID, PRUint32 aStatus) { LOG(("tmModuleTest: nsSharedPrefHandler::OnFlushReply [%d]\n", aStatus)); // Call the internal method to write immediately // The transaction service holds a lock on the file during this call. mPrefService->SavePrefFileInternal(nsnull); return NS_OK; } //***************************************************************************** // Static functions //***************************************************************************** static PRBool enumFind(void* aElement, void *aData) { char *elemStr = static_cast(aElement); char *searchStr = static_cast(aData); // return PR_FALSE for a match and to stop search return (strncmp(elemStr, searchStr, strlen(elemStr)) != 0); } static PRBool enumFree(void* aElement, void *aData) { if (aElement) nsMemory::Free(aElement); return PR_TRUE; } static PRInt32 ReadLine(FILE* inStm, nsACString& destString) { char stackBuf[512]; PRUint32 charsInBuf = 0; destString.Truncate(); int c; while (1) { c = getc(inStm); if (c == EOF) break; else if (c == '\r') { c = getc(inStm); if (c != '\n') ungetc(c, inStm); break; } else if (c == '\n') break; else { if (charsInBuf >= sizeof(stackBuf)) { destString.Append(stackBuf, charsInBuf); charsInBuf = 0; } stackBuf[charsInBuf++] = c; } } if (charsInBuf) destString.Append(stackBuf, charsInBuf); return (c == EOF && destString.IsEmpty()) ? EOF : 1; } //***************************************************************************** // NS_CreateSharedPrefHandler //***************************************************************************** nsresult NS_CreateSharedPrefHandler(nsPrefService *aOwner) { nsSharedPrefHandler *local = new nsSharedPrefHandler; if (!local) return NS_ERROR_OUT_OF_MEMORY; nsresult rv = local->Init(aOwner); if (NS_FAILED(rv)) { delete local; return rv; } NS_ADDREF(gSharedPrefHandler = local); return NS_OK; }