Back to index

lightning-sunbird  0.9+nobinonly
nsUrlClassifierDBService.cpp
Go to the documentation of this file.
00001 //* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Url Classifier code
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Google Inc.
00019  * Portions created by the Initial Developer are Copyright (C) 2006
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Tony Chang <tony@ponderer.org> (original author)
00024  *   Brett Wilson <brettw@gmail.com>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include "mozIStorageService.h"
00041 #include "mozIStorageConnection.h"
00042 #include "mozIStorageStatement.h"
00043 #include "mozStorageCID.h"
00044 #include "nsAppDirectoryServiceDefs.h"
00045 #include "nsAutoLock.h"
00046 #include "nsCOMPtr.h"
00047 #include "nsCRT.h"
00048 #include "nsIDirectoryService.h"
00049 #include "nsIEventQueueService.h"
00050 #include "nsIObserverService.h"
00051 #include "nsIProperties.h"
00052 #include "nsIProxyObjectManager.h"
00053 #include "nsToolkitCompsCID.h"
00054 #include "nsUrlClassifierDBService.h"
00055 #include "nsString.h"
00056 #include "nsTArray.h"
00057 #include "plevent.h"
00058 #include "prlog.h"
00059 #include "prmon.h"
00060 #include "prthread.h"
00061 #include "prprf.h"
00062 
00063 // NSPR_LOG_MODULES=UrlClassifierDbService:5
00064 #if defined(PR_LOGGING)
00065 static const PRLogModuleInfo *gUrlClassifierDbServiceLog = nsnull;
00066 #define LOG(args) PR_LOG(gUrlClassifierDbServiceLog, PR_LOG_DEBUG, args)
00067 #else
00068 #define LOG(args)
00069 #endif
00070 
00071 // Change filename each time we change the db schema
00072 #define DATABASE_FILENAME "urlclassifier2.sqlite"
00073 
00074 static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID);
00075 static NS_DEFINE_CID(kProxyObjectManagerCID, NS_PROXYEVENT_MANAGER_CID);
00076 
00077 // Singleton instance.
00078 static nsUrlClassifierDBService* sUrlClassifierDBService;
00079 
00080 // The event queue used to pass around messages between threads.
00081 static nsIEventQueue* gEventQ = nsnull;
00082 
00083 // Used to ensure that the event queue is initialized before
00084 static PRMonitor *gMonitor = nsnull;
00085 
00086 // Thread that we do the updates on.
00087 static PRThread* gDbBackgroundThread = nsnull;
00088 
00089 // Once we've committed to shutting down, don't do work in the background
00090 // thread.
00091 static PRBool gShuttingDownThread = PR_FALSE;
00092 
00093 static const char* kNEW_TABLE_SUFFIX = "_new";
00094 
00095 
00096 // The flag that tells us it's time to stop the background thread.
00097 static PRBool gKeepRunning = PR_TRUE;
00098 
00099 // The background thread event loop.  Creates an nsIEventQueue and processes
00100 // jobs as they come in.
00101 PR_STATIC_CALLBACK(void) EventLoop(void *arg);
00102 
00103 
00104 // This maps A-M to N-Z and N-Z to A-M.  All other characters are left alone.
00105 // Copied from mailnews/mime/src/mimetext.cpp
00106 static const unsigned char kRot13Table[256] = {
00107   0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
00108   21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
00109   40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58,
00110   59, 60, 61, 62, 63, 64, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
00111   65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 91, 92, 93, 94, 95, 96,
00112   110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 97, 98,
00113   99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 123, 124, 125, 126,
00114   127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141,
00115   142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156,
00116   157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171,
00117   172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186,
00118   187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201,
00119   202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216,
00120   217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231,
00121   232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246,
00122   247, 248, 249, 250, 251, 252, 253, 254, 255 };
00123 
00124 // Does an in place rotation of the line
00125 static void
00126 Rot13Line(nsCString &line)
00127 {
00128   nsCString::iterator start, end;
00129   line.BeginWriting(start);
00130   line.EndWriting(end);
00131   while (start != end) {
00132     *start = kRot13Table[NS_STATIC_CAST(PRInt32, *start)];
00133     ++start;
00134   }
00135 }
00136 
00137 
00138 // -------------------------------------------------------------------------
00139 // Wrapper for JS-implemented nsIUrlClassifierCallback that protects against
00140 // bug 337492.  We should be able to remove this code once that bug is fixed.
00141 
00142 #include "nsProxyRelease.h"
00143 #include "nsEventQueueUtils.h"
00144 
00145 class nsUrlClassifierCallbackWrapper : public nsIUrlClassifierCallback
00146 {
00147 public:
00148   NS_DECL_ISUPPORTS
00149   NS_FORWARD_NSIURLCLASSIFIERCALLBACK(mInner->)
00150 
00151   nsUrlClassifierCallbackWrapper(nsIUrlClassifierCallback *inner)
00152     : mInner(inner)
00153   {
00154     NS_ADDREF(mInner);
00155   }
00156 
00157   ~nsUrlClassifierCallbackWrapper()
00158   {
00159     nsCOMPtr<nsIEventQueue> mainEventQ;
00160     NS_GetMainEventQ(getter_AddRefs(mainEventQ));
00161     if (mainEventQ) {
00162       NS_ProxyRelease(mainEventQ, mInner);
00163     } else {
00164       NS_WARNING("unable to get main event queue");
00165     }
00166   }
00167 
00168 private:
00169   nsIUrlClassifierCallback *mInner;
00170 };
00171 
00172 NS_IMPL_THREADSAFE_ISUPPORTS1(nsUrlClassifierCallbackWrapper,
00173                               nsIUrlClassifierCallback)
00174 
00175 // -------------------------------------------------------------------------
00176 // Actual worker implemenatation
00177 class nsUrlClassifierDBServiceWorker : public nsIUrlClassifierDBServiceWorker
00178 {
00179 public:
00180   nsUrlClassifierDBServiceWorker();
00181 
00182   NS_DECL_ISUPPORTS
00183   NS_DECL_NSIURLCLASSIFIERDBSERVICE
00184   NS_DECL_NSIURLCLASSIFIERDBSERVICEWORKER
00185 
00186 private:
00187   // No subclassing
00188   ~nsUrlClassifierDBServiceWorker();
00189 
00190   // Disallow copy constructor
00191   nsUrlClassifierDBServiceWorker(nsUrlClassifierDBServiceWorker&);
00192 
00193   // Table names have hyphens in them, which SQL doesn't allow,
00194   // so we convert them to underscores.
00195   void GetDbTableName(const nsACString& aTableName, nsCString* aDbTableName);
00196 
00197   // Try to open the db, DATABASE_FILENAME.
00198   nsresult OpenDb();
00199 
00200   // Create a table in the db if it doesn't exist.
00201   nsresult MaybeCreateTable(const nsCString& aTableName);
00202 
00203   // Drop a table if it exists.
00204   nsresult MaybeDropTable(const nsCString& aTableName);
00205 
00206   // If this is not an update request, swap the new table
00207   // in for the old table.
00208   nsresult MaybeSwapTables(const nsCString& aVersionLine);
00209 
00210   // Parse a version string of the form [table-name #.###] or
00211   // [table-name #.### update] and return the table name and
00212   // whether or not it's an update.
00213   nsresult ParseVersionString(const nsCSubstring& aLine,
00214                               nsCString* aTableName,
00215                               PRBool* aIsUpdate);
00216 
00217   // Handle a new table line of the form [table-name #.####].  We create the
00218   // table if it doesn't exist and set the aTableName, aUpdateStatement,
00219   // and aDeleteStatement.
00220   nsresult ProcessNewTable(const nsCSubstring& aLine,
00221                            nsCString* aTableName,
00222                            mozIStorageStatement** aUpdateStatement,
00223                            mozIStorageStatement** aDeleteStatement);
00224 
00225   // Handle an add or remove line.  We execute additional update or delete
00226   // statements.
00227   nsresult ProcessUpdateTable(const nsCSubstring& aLine,
00228                               const nsCString& aTableName,
00229                               mozIStorageStatement* aUpdateStatement,
00230                               mozIStorageStatement* aDeleteStatement);
00231 
00232   // Holds a connection to the Db.  We lazily initialize this because it has
00233   // to be created in the background thread (currently mozStorageConnection
00234   // isn't thread safe).
00235   mozIStorageConnection* mConnection;
00236 
00237   // True if we're in the middle of a streaming update.
00238   PRBool mHasPendingUpdate;
00239 
00240   // For incremental updates, keep track of tables that have been updated.
00241   // When finish() is called, we go ahead and pass these update lines to
00242   // the callback.
00243   nsTArray<nsCString> mTableUpdateLines;
00244 
00245   // We receive data in small chunks that may be broken in the middle of
00246   // a line.  So we save the last partial line here.
00247   nsCString mPendingStreamUpdate;
00248 };
00249 
00250 NS_IMPL_THREADSAFE_ISUPPORTS1(nsUrlClassifierDBServiceWorker,
00251                               nsIUrlClassifierDBServiceWorker)
00252 
00253 nsUrlClassifierDBServiceWorker::nsUrlClassifierDBServiceWorker()
00254   : mConnection(nsnull), mHasPendingUpdate(PR_FALSE), mTableUpdateLines()
00255 {
00256 }
00257 nsUrlClassifierDBServiceWorker::~nsUrlClassifierDBServiceWorker()
00258 {
00259   NS_ASSERTION(mConnection == nsnull,
00260                "Db connection not closed, leaking memory!  Call CloseDb "
00261                "to close the connection.");
00262 }
00263 
00264 
00265 // Lookup a key in the db.
00266 NS_IMETHODIMP
00267 nsUrlClassifierDBServiceWorker::Exists(const nsACString& tableName,
00268                                        const nsACString& key,
00269                                        nsIUrlClassifierCallback *c)
00270 {
00271   if (gShuttingDownThread)
00272     return NS_ERROR_NOT_INITIALIZED;
00273 
00274   nsresult rv = OpenDb();
00275   if (NS_FAILED(rv)) {
00276     NS_ERROR("Unable to open database");
00277     return NS_ERROR_FAILURE;
00278   }
00279 
00280   nsCAutoString dbTableName;
00281   GetDbTableName(tableName, &dbTableName);
00282 
00283   nsCOMPtr<mozIStorageStatement> selectStatement;
00284   nsCAutoString statement;
00285   statement.AssignLiteral("SELECT value FROM ");
00286   statement.Append(dbTableName);
00287   statement.AppendLiteral(" WHERE key = ?1");
00288 
00289   rv = mConnection->CreateStatement(statement,
00290                                     getter_AddRefs(selectStatement));
00291 
00292   nsAutoString value;
00293   // If CreateStatment failed, this probably means the table doesn't exist.
00294   // That's ok, we just return an emptry string.
00295   if (NS_SUCCEEDED(rv)) {
00296     nsCString keyROT13(key);
00297     Rot13Line(keyROT13);
00298     rv = selectStatement->BindUTF8StringParameter(0, keyROT13);
00299     NS_ENSURE_SUCCESS(rv, rv);
00300 
00301     PRBool hasMore = PR_FALSE;
00302     rv = selectStatement->ExecuteStep(&hasMore);
00303     // If the table has any columns, take the first value.
00304     if (NS_SUCCEEDED(rv) && hasMore) {
00305       selectStatement->GetString(0, value);
00306     }
00307   }
00308 
00309   c->HandleEvent(NS_ConvertUTF16toUTF8(value));
00310   return NS_OK;
00311 }
00312 
00313 // We get a comma separated list of table names.  For each table that doesn't
00314 // exist, we return it in a comma separated list via the callback.
00315 NS_IMETHODIMP
00316 nsUrlClassifierDBServiceWorker::CheckTables(const nsACString & tableNames,
00317                                             nsIUrlClassifierCallback *c)
00318 {
00319   if (gShuttingDownThread)
00320     return NS_ERROR_NOT_INITIALIZED;
00321 
00322   nsresult rv = OpenDb();
00323   if (NS_FAILED(rv)) {
00324     NS_ERROR("Unable to open database");
00325     return NS_ERROR_FAILURE;
00326   }
00327 
00328   nsCAutoString changedTables;
00329 
00330   // tablesNames is a comma separated list, so get each table name out for
00331   // checking.
00332   PRUint32 cur = 0;
00333   PRInt32 next;
00334   while (cur < tableNames.Length()) {
00335     next = tableNames.FindChar(',', cur);
00336     if (kNotFound == next) {
00337       next = tableNames.Length();
00338     }
00339     const nsCSubstring &tableName = Substring(tableNames, cur, next - cur);
00340     cur = next + 1;
00341 
00342     nsCString dbTableName;
00343     GetDbTableName(tableName, &dbTableName);
00344     PRBool exists;
00345     nsresult rv = mConnection->TableExists(dbTableName, &exists);
00346     NS_ENSURE_SUCCESS(rv, rv);
00347     if (!exists) {
00348       if (changedTables.Length() > 0)
00349         changedTables.Append(",");
00350       changedTables.Append(tableName);
00351     }
00352   }
00353 
00354   c->HandleEvent(changedTables);
00355   return NS_OK;
00356 }
00357 
00358 // Do a batch update of the database.  After we complete processing a table,
00359 // we call the callback with the table line.
00360 NS_IMETHODIMP
00361 nsUrlClassifierDBServiceWorker::UpdateTables(const nsACString& updateString,
00362                                              nsIUrlClassifierCallback *c)
00363 {
00364   if (gShuttingDownThread)
00365     return NS_ERROR_NOT_INITIALIZED;
00366 
00367   LOG(("Updating tables\n"));
00368 
00369   nsresult rv = OpenDb();
00370   if (NS_FAILED(rv)) {
00371     NS_ERROR("Unable to open database");
00372     return NS_ERROR_FAILURE;
00373   }
00374 
00375   rv = mConnection->BeginTransaction();
00376   NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to begin transaction");
00377 
00378   // Split the update string into lines
00379   PRUint32 cur = 0;
00380   PRInt32 next;
00381   PRInt32 count = 0;
00382   nsCAutoString dbTableName;
00383   nsCAutoString lastTableLine;
00384   nsCOMPtr<mozIStorageStatement> updateStatement;
00385   nsCOMPtr<mozIStorageStatement> deleteStatement;
00386   while(cur < updateString.Length() &&
00387         (next = updateString.FindChar('\n', cur)) != kNotFound) {
00388     const nsCSubstring &line = Substring(updateString, cur, next - cur);
00389     cur = next + 1; // prepare for next run
00390 
00391     // Skip blank lines
00392     if (line.Length() == 0)
00393       continue;
00394 
00395     count++;
00396 
00397     if ('[' == line[0]) {
00398       rv = ProcessNewTable(line, &dbTableName,
00399                            getter_AddRefs(updateStatement),
00400                            getter_AddRefs(deleteStatement));
00401       NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "malformed table line");
00402       if (NS_SUCCEEDED(rv)) {
00403         // If it's a new table, we may have completed a table.
00404         // Go ahead and post the completion to the UI thread and db.
00405         if (lastTableLine.Length() > 0) {
00406           // If it was a new table, we need to swap in the new table.
00407           rv = MaybeSwapTables(lastTableLine);
00408           if (NS_SUCCEEDED(rv)) {
00409             mConnection->CommitTransaction();
00410             c->HandleEvent(lastTableLine);
00411           } else {
00412             // failed to swap, rollback
00413             mConnection->RollbackTransaction();
00414           }
00415           mConnection->BeginTransaction();
00416         }
00417         lastTableLine.Assign(line);
00418       }
00419     } else {
00420       rv = ProcessUpdateTable(line, dbTableName, updateStatement,
00421                               deleteStatement);
00422       NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "malformed update line");
00423     }
00424   }
00425   LOG(("Num update lines: %d\n", count));
00426 
00427   rv = MaybeSwapTables(lastTableLine);
00428   if (NS_SUCCEEDED(rv)) {
00429     mConnection->CommitTransaction();
00430     c->HandleEvent(lastTableLine);
00431   } else {
00432     // failed to swap, rollback
00433     mConnection->RollbackTransaction();
00434   }
00435 
00436   LOG(("Finishing table update\n"));
00437   return NS_OK;
00438 }
00439 
00440 NS_IMETHODIMP
00441 nsUrlClassifierDBServiceWorker::Update(const nsACString& chunk)
00442 {
00443   LOG(("Update from Stream."));
00444   nsresult rv = OpenDb();
00445   if (NS_FAILED(rv)) {
00446     NS_ERROR("Unable to open database");
00447     return NS_ERROR_FAILURE;
00448   }
00449 
00450   nsCAutoString updateString(mPendingStreamUpdate);
00451   updateString.Append(chunk);
00452   
00453   nsCOMPtr<mozIStorageStatement> updateStatement;
00454   nsCOMPtr<mozIStorageStatement> deleteStatement;
00455   nsCAutoString dbTableName;
00456 
00457   // If we're not in the middle of an update, we start a new transaction.
00458   // Otherwise, we need to pick up where we left off.
00459   if (!mHasPendingUpdate) {
00460     mConnection->BeginTransaction();
00461     mHasPendingUpdate = PR_TRUE;
00462   } else {
00463     PRUint32 numTables = mTableUpdateLines.Length();
00464     if (numTables > 0) {
00465       const nsCSubstring &line = Substring(
00466               mTableUpdateLines[numTables - 1], 0);
00467 
00468       rv = ProcessNewTable(line, &dbTableName,
00469                            getter_AddRefs(updateStatement),
00470                            getter_AddRefs(deleteStatement));
00471       NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "malformed table line");
00472     }
00473   }
00474 
00475   PRUint32 cur = 0;
00476   PRInt32 next;
00477   while(cur < updateString.Length() &&
00478         (next = updateString.FindChar('\n', cur)) != kNotFound) {
00479     const nsCSubstring &line = Substring(updateString, cur, next - cur);
00480     cur = next + 1; // prepare for next run
00481 
00482     // Skip blank lines
00483     if (line.Length() == 0)
00484       continue;
00485 
00486     if ('[' == line[0]) {
00487       rv = ProcessNewTable(line, &dbTableName,
00488                            getter_AddRefs(updateStatement),
00489                            getter_AddRefs(deleteStatement));
00490       NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "malformed table line");
00491       if (NS_SUCCEEDED(rv)) {
00492         // Add the line to our array of table lines.
00493         mTableUpdateLines.AppendElement(line);
00494       }
00495     } else {
00496       rv = ProcessUpdateTable(line, dbTableName, updateStatement,
00497                               deleteStatement);
00498       NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "malformed update line");
00499     }
00500   }
00501   // Save the remaining string fragment.
00502   mPendingStreamUpdate = Substring(updateString, cur);
00503   LOG(("pending stream update: %s", mPendingStreamUpdate.get()));
00504 
00505   return NS_OK;
00506 }
00507 
00508 NS_IMETHODIMP
00509 nsUrlClassifierDBServiceWorker::Finish(nsIUrlClassifierCallback *c)
00510 {
00511   if (!mHasPendingUpdate)
00512     return NS_OK;
00513 
00514   if (gShuttingDownThread) {
00515     mConnection->RollbackTransaction();
00516     return NS_ERROR_NOT_INITIALIZED;
00517   }
00518 
00519   nsresult rv = NS_OK;
00520   for (PRUint32 i = 0; i < mTableUpdateLines.Length(); ++i) {
00521     rv = MaybeSwapTables(mTableUpdateLines[i]);
00522     if (NS_FAILED(rv)) {
00523       break;
00524     }
00525   }
00526   
00527   if (NS_SUCCEEDED(rv)) {
00528     LOG(("Finish, committing transaction"));
00529     mConnection->CommitTransaction();
00530 
00531     // Send update information to main thread.
00532     for (PRUint32 i = 0; i < mTableUpdateLines.Length(); ++i) {
00533       c->HandleEvent(mTableUpdateLines[i]);
00534     }
00535   } else {
00536     LOG(("Finish failed (swap table error?), rolling back transaction"));
00537     mConnection->RollbackTransaction();
00538   }
00539 
00540   mTableUpdateLines.Clear();
00541   mPendingStreamUpdate.Truncate();
00542   mHasPendingUpdate = PR_FALSE;
00543   return NS_OK;
00544 }
00545 
00546 NS_IMETHODIMP
00547 nsUrlClassifierDBServiceWorker::CancelStream()
00548 {
00549   if (!mHasPendingUpdate)
00550     return NS_OK;
00551 
00552   LOG(("CancelStream, rolling back transaction"));
00553   mConnection->RollbackTransaction();
00554 
00555   mTableUpdateLines.Clear();
00556   mPendingStreamUpdate.Truncate();
00557   mHasPendingUpdate = PR_FALSE;
00558   
00559   return NS_OK;
00560 }
00561 
00562 // Allows the main thread to delete the connection which may be in
00563 // a background thread.
00564 // XXX This could be turned into a single shutdown event so the logic
00565 // is simpler in nsUrlClassifierDBService::Shutdown.
00566 NS_IMETHODIMP
00567 nsUrlClassifierDBServiceWorker::CloseDb()
00568 {
00569   if (mConnection != nsnull) {
00570     NS_RELEASE(mConnection);
00571     LOG(("urlclassifier db closed\n"));
00572   }
00573   return NS_OK;
00574 }
00575 
00576 nsresult
00577 nsUrlClassifierDBServiceWorker::ProcessNewTable(
00578                                     const nsCSubstring& aLine,
00579                                     nsCString* aDbTableName,
00580                                     mozIStorageStatement** aUpdateStatement,
00581                                     mozIStorageStatement** aDeleteStatement)
00582 {
00583   // The line format is "[table-name #.####]" or "[table-name #.#### update]"
00584   // The additional "update" in the header means that this is a diff.
00585   // Otherwise, we should blow away the old table and start afresh.
00586   PRBool isUpdate = PR_FALSE;
00587 
00588   // If the version string is bad, give up.
00589   nsresult rv = ParseVersionString(aLine, aDbTableName, &isUpdate);
00590   NS_ENSURE_SUCCESS(rv, rv);
00591   
00592   // If it's not an update, we dump the values into a new table.  Once we're
00593   // done with the table, we drop the original table and copy over the values
00594   // from the old table into the new table.
00595   if (!isUpdate)
00596     aDbTableName->Append(kNEW_TABLE_SUFFIX);
00597 
00598   // Create the table
00599   rv = MaybeCreateTable(*aDbTableName);
00600   if (NS_FAILED(rv))
00601     return rv;
00602 
00603   // insert statement
00604   nsCAutoString statement;
00605   statement.AssignLiteral("INSERT OR REPLACE INTO ");
00606   statement.Append(*aDbTableName);
00607   statement.AppendLiteral(" VALUES (?1, ?2)");
00608   rv = mConnection->CreateStatement(statement, aUpdateStatement);
00609   NS_ENSURE_SUCCESS(rv, rv);
00610 
00611   // delete statement
00612   statement.AssignLiteral("DELETE FROM ");
00613   statement.Append(*aDbTableName);
00614   statement.AppendLiteral(" WHERE key = ?1");
00615   rv = mConnection->CreateStatement(statement, aDeleteStatement);
00616   NS_ENSURE_SUCCESS(rv, rv);
00617 
00618   return NS_OK;
00619 }
00620 
00621 nsresult
00622 nsUrlClassifierDBServiceWorker::ProcessUpdateTable(
00623                                    const nsCSubstring& aLine,
00624                                    const nsCString& aTableName,
00625                                    mozIStorageStatement* aUpdateStatement,
00626                                    mozIStorageStatement* aDeleteStatement)
00627 {
00628   // We should have seen a table name line by now.
00629   if (aTableName.Length() == 0)
00630     return NS_ERROR_FAILURE;
00631 
00632   if (!aUpdateStatement || !aDeleteStatement) {
00633     NS_NOTREACHED("Statements NULL but table is not");
00634     return NS_ERROR_FAILURE;
00635   }
00636   // There should at least be an op char and a key
00637   if (aLine.Length() < 2)
00638     return NS_ERROR_FAILURE;
00639 
00640   char op = aLine[0];
00641   PRInt32 spacePos = aLine.FindChar('\t');
00642   nsresult rv = NS_ERROR_FAILURE;
00643 
00644   if ('+' == op && spacePos != kNotFound) {
00645     // Insert operation of the form "+KEY\tVALUE"
00646     const nsCSubstring &key = Substring(aLine, 1, spacePos - 1);
00647     const nsCSubstring &value = Substring(aLine, spacePos + 1);
00648 
00649     // We use ROT13 versions of keys to avoid antivirus utilities from
00650     // flagging us as a virus.
00651     nsCString keyROT13(key);
00652     Rot13Line(keyROT13);
00653     
00654     aUpdateStatement->BindUTF8StringParameter(0, keyROT13);
00655     aUpdateStatement->BindUTF8StringParameter(1, value);
00656 
00657     rv = aUpdateStatement->Execute();
00658     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to update");
00659   } else if ('-' == op) {
00660     // Remove operation of the form "-KEY"
00661     nsCString keyROT13;
00662     if (spacePos == kNotFound) {
00663       // No trailing tab
00664       const nsCSubstring &key = Substring(aLine, 1);
00665       keyROT13.Assign(key);
00666     } else {
00667       // With trailing tab
00668       const nsCSubstring &key = Substring(aLine, 1, spacePos - 1);
00669       keyROT13.Assign(key);
00670     }
00671     Rot13Line(keyROT13);
00672     aDeleteStatement->BindUTF8StringParameter(0, keyROT13);
00673 
00674     rv = aDeleteStatement->Execute();
00675     NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to delete");
00676   }
00677 
00678   return rv;
00679 }
00680 
00681 nsresult
00682 nsUrlClassifierDBServiceWorker::OpenDb()
00683 {
00684   // Connection already open, don't do anything.
00685   if (mConnection != nsnull)
00686     return NS_OK;
00687 
00688   LOG(("Opening db\n"));
00689   // Compute database filename
00690   nsCOMPtr<nsIFile> dbFile;
00691 
00692   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
00693                                        getter_AddRefs(dbFile));
00694   NS_ENSURE_SUCCESS(rv, rv);
00695   rv = dbFile->Append(NS_LITERAL_STRING(DATABASE_FILENAME));
00696   NS_ENSURE_SUCCESS(rv, rv);
00697 
00698   // open the connection
00699   nsCOMPtr<mozIStorageService> storageService =
00700     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
00701   NS_ENSURE_SUCCESS(rv, rv);
00702   rv = storageService->OpenDatabase(dbFile, &mConnection);
00703   if (rv == NS_ERROR_FILE_CORRUPTED) {
00704     // delete the db and try opening again
00705     rv = dbFile->Remove(PR_FALSE);
00706     NS_ENSURE_SUCCESS(rv, rv);
00707     rv = storageService->OpenDatabase(dbFile, &mConnection);
00708   }
00709   return rv;
00710 }
00711 
00712 nsresult
00713 nsUrlClassifierDBServiceWorker::MaybeCreateTable(const nsCString& aTableName)
00714 {
00715   LOG(("MaybeCreateTable %s\n", aTableName.get()));
00716 
00717   nsCOMPtr<mozIStorageStatement> createStatement;
00718   nsCString statement;
00719   statement.Assign("CREATE TABLE IF NOT EXISTS ");
00720   statement.Append(aTableName);
00721   statement.Append(" (key TEXT PRIMARY KEY, value TEXT)");
00722   nsresult rv = mConnection->CreateStatement(statement,
00723                                              getter_AddRefs(createStatement));
00724   NS_ENSURE_SUCCESS(rv, rv);
00725 
00726   return createStatement->Execute();
00727 }
00728 
00729 nsresult
00730 nsUrlClassifierDBServiceWorker::MaybeDropTable(const nsCString& aTableName)
00731 {
00732   LOG(("MaybeDropTable %s\n", aTableName.get()));
00733   nsCAutoString statement("DROP TABLE IF EXISTS ");
00734   statement.Append(aTableName);
00735   return mConnection->ExecuteSimpleSQL(statement);
00736 }
00737 
00738 nsresult
00739 nsUrlClassifierDBServiceWorker::MaybeSwapTables(const nsCString& aVersionLine)
00740 {
00741   if (aVersionLine.Length() == 0)
00742     return NS_ERROR_FAILURE;
00743 
00744   // Check to see if this was a full table update or not.
00745   nsCAutoString tableName;
00746   PRBool isUpdate;
00747   nsresult rv = ParseVersionString(aVersionLine, &tableName, &isUpdate);
00748   NS_ENSURE_SUCCESS(rv, rv);
00749 
00750   // Updates don't require any fancy logic.
00751   if (isUpdate)
00752     return NS_OK;
00753 
00754   // Not an update, so we need to swap tables by dropping the original table
00755   // and copying in the values from the new table.
00756   rv = MaybeDropTable(tableName);
00757   NS_ENSURE_SUCCESS(rv, rv);
00758 
00759   nsCAutoString newTableName(tableName);
00760   newTableName.Append(kNEW_TABLE_SUFFIX);
00761 
00762   // Bring over new table
00763   nsCAutoString sql("ALTER TABLE ");
00764   sql.Append(newTableName);
00765   sql.Append(" RENAME TO ");
00766   sql.Append(tableName);
00767   rv = mConnection->ExecuteSimpleSQL(sql);
00768   NS_ENSURE_SUCCESS(rv, rv);
00769 
00770   LOG(("tables swapped (%s)\n", tableName.get()));
00771 
00772   return NS_OK;
00773 }
00774 
00775 // The line format is "[table-name #.####]" or "[table-name #.#### update]".
00776 nsresult
00777 nsUrlClassifierDBServiceWorker::ParseVersionString(const nsCSubstring& aLine,
00778                                                    nsCString* aTableName,
00779                                                    PRBool* aIsUpdate)
00780 {
00781   // Blank lines are not valid
00782   if (aLine.Length() == 0)
00783     return NS_ERROR_FAILURE;
00784 
00785   // Max size for an update line (so we don't buffer overflow when sscanf'ing).
00786   const PRUint32 MAX_LENGTH = 2048;
00787   if (aLine.Length() > MAX_LENGTH)
00788     return NS_ERROR_FAILURE;
00789 
00790   nsCAutoString lineData(aLine);
00791   char tableNameBuf[MAX_LENGTH], endChar = ' ';
00792   PRInt32 majorVersion, minorVersion, numConverted;
00793   // Use trailing endChar to make sure the update token gets parsed.
00794   numConverted = PR_sscanf(lineData.get(), "[%s %d.%d update%c", tableNameBuf,
00795                            &majorVersion, &minorVersion, &endChar);
00796   if (numConverted != 4 || endChar != ']') {
00797     // Check to see if it's not an update request
00798     numConverted = PR_sscanf(lineData.get(), "[%s %d.%d%c", tableNameBuf,
00799                              &majorVersion, &minorVersion, &endChar);
00800     if (numConverted != 4 || endChar != ']')
00801       return NS_ERROR_FAILURE;
00802     *aIsUpdate = PR_FALSE;
00803   } else {
00804     // First sscanf worked, so it's an update string.
00805     *aIsUpdate = PR_TRUE;
00806   }
00807 
00808   LOG(("Is update? %d\n", *aIsUpdate));
00809 
00810   // Table header looks valid, go ahead and copy over the table name into the
00811   // return variable.
00812   GetDbTableName(nsCAutoString(tableNameBuf), aTableName);
00813   return NS_OK;
00814 }
00815 
00816 void
00817 nsUrlClassifierDBServiceWorker::GetDbTableName(const nsACString& aTableName,
00818                                                nsCString* aDbTableName)
00819 {
00820   aDbTableName->Assign(aTableName);
00821   aDbTableName->ReplaceChar('-', '_');
00822 }
00823 
00824 // -------------------------------------------------------------------------
00825 // Proxy class implementation
00826 
00827 NS_IMPL_THREADSAFE_ISUPPORTS2(nsUrlClassifierDBService,
00828                               nsIUrlClassifierDBService,
00829                               nsIObserver)
00830 
00831 /* static */ nsUrlClassifierDBService*
00832 nsUrlClassifierDBService::GetInstance()
00833 {
00834   if (!sUrlClassifierDBService) {
00835     sUrlClassifierDBService = new nsUrlClassifierDBService();
00836     if (!sUrlClassifierDBService)
00837       return nsnull;
00838 
00839     NS_ADDREF(sUrlClassifierDBService);   // addref the global
00840 
00841     if (NS_FAILED(sUrlClassifierDBService->Init())) {
00842       NS_RELEASE(sUrlClassifierDBService);
00843       return nsnull;
00844     }
00845   } else {
00846     // Already exists, just add a ref
00847     NS_ADDREF(sUrlClassifierDBService);   // addref the return result
00848   }
00849   return sUrlClassifierDBService;
00850 }
00851 
00852 
00853 nsUrlClassifierDBService::nsUrlClassifierDBService()
00854 {
00855 }
00856 
00857 // Callback functions for event used in destructor.
00858 PR_STATIC_CALLBACK(void *) EventHandler(PLEvent *ev);
00859 PR_STATIC_CALLBACK(void) DestroyHandler(PLEvent *ev);
00860 
00861 nsUrlClassifierDBService::~nsUrlClassifierDBService()
00862 {
00863   sUrlClassifierDBService = nsnull;
00864   if (gMonitor) {
00865     PR_DestroyMonitor(gMonitor);
00866     gMonitor = nsnull;
00867   }
00868 }
00869 
00870 nsresult
00871 nsUrlClassifierDBService::Init()
00872 {
00873 #if defined(PR_LOGGING)
00874   if (!gUrlClassifierDbServiceLog)
00875     gUrlClassifierDbServiceLog = PR_NewLogModule("UrlClassifierDbService");
00876 #endif
00877 
00878   // Force the storage service to be created on the main thread.
00879   nsresult rv;
00880   nsCOMPtr<mozIStorageService> storageService =
00881     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
00882   NS_ENSURE_SUCCESS(rv, rv);
00883 
00884   gMonitor = PR_NewMonitor();
00885   // Start the background thread.
00886   gDbBackgroundThread = PR_CreateThread(PR_USER_THREAD,
00887                                         EventLoop,
00888                                         nsnull,
00889                                         PR_PRIORITY_NORMAL,
00890                                         PR_GLOBAL_THREAD,
00891                                         PR_JOINABLE_THREAD,
00892                                         0);
00893   if (!gDbBackgroundThread)
00894     return NS_ERROR_OUT_OF_MEMORY;
00895 
00896   mWorker = new nsUrlClassifierDBServiceWorker();
00897   if (!mWorker)
00898     return NS_ERROR_OUT_OF_MEMORY;
00899 
00900   // Add an observer for shutdown
00901   nsCOMPtr<nsIObserverService> observerService =
00902       do_GetService("@mozilla.org/observer-service;1");
00903   if (!observerService)
00904     return NS_ERROR_FAILURE;
00905 
00906   observerService->AddObserver(this, "profile-before-change", PR_FALSE);
00907   observerService->AddObserver(this, "xpcom-shutdown", PR_FALSE);
00908 
00909   return NS_OK;
00910 }
00911 
00912 NS_IMETHODIMP
00913 nsUrlClassifierDBService::Exists(const nsACString& tableName,
00914                                  const nsACString& key,
00915                                  nsIUrlClassifierCallback *c)
00916 {
00917   nsresult rv = EnsureThreadStarted();
00918   NS_ENSURE_SUCCESS(rv, rv);
00919 
00920   nsCOMPtr<nsIUrlClassifierCallback> wrapper =
00921       new nsUrlClassifierCallbackWrapper(c);
00922   NS_ENSURE_TRUE(wrapper, NS_ERROR_OUT_OF_MEMORY);
00923 
00924   // The proxy callback uses the current thread.
00925   nsCOMPtr<nsIUrlClassifierCallback> proxyCallback;
00926   rv = NS_GetProxyForObject(NS_CURRENT_EVENTQ,
00927                             NS_GET_IID(nsIUrlClassifierCallback),
00928                             wrapper,
00929                             PROXY_ASYNC,
00930                             getter_AddRefs(proxyCallback));
00931   NS_ENSURE_SUCCESS(rv, rv);
00932 
00933   // The actual worker uses the background thread.
00934   nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
00935   rv = NS_GetProxyForObject(gEventQ,
00936                             NS_GET_IID(nsIUrlClassifierDBServiceWorker),
00937                             mWorker,
00938                             PROXY_ASYNC,
00939                             getter_AddRefs(proxy));
00940   NS_ENSURE_SUCCESS(rv, rv);
00941 
00942   return proxy->Exists(tableName, key, proxyCallback);
00943 }
00944 
00945 NS_IMETHODIMP
00946 nsUrlClassifierDBService::CheckTables(const nsACString & tableNames,
00947                                       nsIUrlClassifierCallback *c)
00948 {
00949   nsresult rv = EnsureThreadStarted();
00950   NS_ENSURE_SUCCESS(rv, rv);
00951 
00952   nsCOMPtr<nsIUrlClassifierCallback> wrapper =
00953       new nsUrlClassifierCallbackWrapper(c);
00954   NS_ENSURE_TRUE(wrapper, NS_ERROR_OUT_OF_MEMORY);
00955 
00956   // The proxy callback uses the current thread.
00957   nsCOMPtr<nsIUrlClassifierCallback> proxyCallback;
00958   rv = NS_GetProxyForObject(NS_CURRENT_EVENTQ,
00959                             NS_GET_IID(nsIUrlClassifierCallback),
00960                             wrapper,
00961                             PROXY_ASYNC,
00962                             getter_AddRefs(proxyCallback));
00963   NS_ENSURE_SUCCESS(rv, rv);
00964 
00965   // The actual worker uses the background thread.
00966   nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
00967   rv = NS_GetProxyForObject(gEventQ,
00968                             NS_GET_IID(nsIUrlClassifierDBServiceWorker),
00969                             mWorker,
00970                             PROXY_ASYNC,
00971                             getter_AddRefs(proxy));
00972   NS_ENSURE_SUCCESS(rv, rv);
00973 
00974   return proxy->CheckTables(tableNames, proxyCallback);
00975 }
00976 
00977 NS_IMETHODIMP
00978 nsUrlClassifierDBService::UpdateTables(const nsACString& updateString,
00979                                        nsIUrlClassifierCallback *c)
00980 {
00981   nsresult rv = EnsureThreadStarted();
00982   NS_ENSURE_SUCCESS(rv, rv);
00983 
00984   nsCOMPtr<nsIUrlClassifierCallback> wrapper =
00985       new nsUrlClassifierCallbackWrapper(c);
00986   NS_ENSURE_TRUE(wrapper, NS_ERROR_OUT_OF_MEMORY);
00987 
00988   // The proxy callback uses the current thread.
00989   nsCOMPtr<nsIUrlClassifierCallback> proxyCallback;
00990   rv = NS_GetProxyForObject(NS_CURRENT_EVENTQ,
00991                             NS_GET_IID(nsIUrlClassifierCallback),
00992                             wrapper,
00993                             PROXY_ASYNC,
00994                             getter_AddRefs(proxyCallback));
00995   NS_ENSURE_SUCCESS(rv, rv);
00996 
00997   // The actual worker uses the background thread.
00998   nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
00999   rv = NS_GetProxyForObject(gEventQ,
01000                             NS_GET_IID(nsIUrlClassifierDBServiceWorker),
01001                             mWorker,
01002                             PROXY_ASYNC,
01003                             getter_AddRefs(proxy));
01004   NS_ENSURE_SUCCESS(rv, rv);
01005 
01006   return proxy->UpdateTables(updateString, proxyCallback);
01007 }
01008 
01009 NS_IMETHODIMP
01010 nsUrlClassifierDBService::Update(const nsACString& aUpdateChunk)
01011 {
01012   nsresult rv = EnsureThreadStarted();
01013   NS_ENSURE_SUCCESS(rv, rv);
01014 
01015   // The actual worker uses the background thread.
01016   nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
01017   rv = NS_GetProxyForObject(gEventQ,
01018                             NS_GET_IID(nsIUrlClassifierDBServiceWorker),
01019                             mWorker,
01020                             PROXY_ASYNC,
01021                             getter_AddRefs(proxy));
01022   NS_ENSURE_SUCCESS(rv, rv);
01023 
01024   return proxy->Update(aUpdateChunk);
01025 }
01026 
01027 NS_IMETHODIMP
01028 nsUrlClassifierDBService::Finish(nsIUrlClassifierCallback *c)
01029 {
01030   nsresult rv = EnsureThreadStarted();
01031   NS_ENSURE_SUCCESS(rv, rv);
01032 
01033   nsCOMPtr<nsIUrlClassifierCallback> wrapper =
01034       new nsUrlClassifierCallbackWrapper(c);
01035   NS_ENSURE_TRUE(wrapper, NS_ERROR_OUT_OF_MEMORY);
01036 
01037   // The proxy callback uses the current thread.
01038   nsCOMPtr<nsIUrlClassifierCallback> proxyCallback;
01039   rv = NS_GetProxyForObject(NS_CURRENT_EVENTQ,
01040                             NS_GET_IID(nsIUrlClassifierCallback),
01041                             wrapper,
01042                             PROXY_ASYNC,
01043                             getter_AddRefs(proxyCallback));
01044   NS_ENSURE_SUCCESS(rv, rv);
01045 
01046   // The actual worker uses the background thread.
01047   nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
01048   rv = NS_GetProxyForObject(gEventQ,
01049                             NS_GET_IID(nsIUrlClassifierDBServiceWorker),
01050                             mWorker,
01051                             PROXY_ASYNC,
01052                             getter_AddRefs(proxy));
01053   NS_ENSURE_SUCCESS(rv, rv);
01054 
01055   return proxy->Finish(proxyCallback);
01056 }
01057 
01058 NS_IMETHODIMP
01059 nsUrlClassifierDBService::CancelStream()
01060 {
01061   nsresult rv = EnsureThreadStarted();
01062   NS_ENSURE_SUCCESS(rv, rv);
01063 
01064   // The actual worker uses the background thread.
01065   nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
01066   rv = NS_GetProxyForObject(gEventQ,
01067                             NS_GET_IID(nsIUrlClassifierDBServiceWorker),
01068                             mWorker,
01069                             PROXY_ASYNC,
01070                             getter_AddRefs(proxy));
01071   NS_ENSURE_SUCCESS(rv, rv);
01072 
01073   return proxy->CancelStream();
01074 }
01075 
01076 NS_IMETHODIMP
01077 nsUrlClassifierDBService::Observe(nsISupports *aSubject, const char *aTopic,
01078                                   const PRUnichar *aData)
01079 {
01080   NS_ASSERTION(strcmp(aTopic, "profile-before-change") == 0 ||
01081                strcmp(aTopic, "xpcom-shutdown") == 0,
01082                "Unexpected observer topic");
01083 
01084   Shutdown();
01085 
01086   return NS_OK;
01087 }
01088 
01089 // Make sure the event queue is intialized before we use it.
01090 nsresult
01091 nsUrlClassifierDBService::EnsureThreadStarted()
01092 {
01093   // xpcom-shutdown has triggered, the thread is already gone.
01094   if (!gKeepRunning) {
01095     return NS_ERROR_FAILURE;
01096   }
01097 
01098   nsAutoMonitor mon(gMonitor);
01099   while (!gEventQ)
01100     mon.Wait();
01101 
01102   return NS_OK;
01103 }
01104 
01105 // Join the background thread if it exists.
01106 nsresult
01107 nsUrlClassifierDBService::Shutdown()
01108 {
01109   LOG(("shutting down db service\n"));
01110 
01111   if (!gDbBackgroundThread)
01112     return NS_OK;
01113 
01114   nsresult rv;
01115   
01116   EnsureThreadStarted();
01117   
01118   // First close the db connection.
01119   if (mWorker) {
01120     LOG(("Sending close request\n"));
01121     nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
01122     rv = NS_GetProxyForObject(gEventQ,
01123                               NS_GET_IID(nsIUrlClassifierDBServiceWorker),
01124                               mWorker,
01125                               PROXY_ASYNC,
01126                               getter_AddRefs(proxy));
01127     if (NS_SUCCEEDED(rv)) {
01128       rv = proxy->CloseDb();
01129       NS_ASSERTION(NS_SUCCEEDED(rv), "failed to post close db event");
01130     }
01131   }
01132 
01133   PLEvent* ev = new PLEvent;
01134   PL_InitEvent(ev, nsnull, EventHandler, DestroyHandler);
01135 
01136   if (NS_FAILED(gEventQ->PostEvent(ev))) {
01137     PL_DestroyEvent(ev);
01138   }
01139   LOG(("joining background thread"));
01140 
01141   gShuttingDownThread = PR_TRUE;
01142   PRStatus ok = PR_JoinThread(gDbBackgroundThread);
01143   NS_ASSERTION(ok == PR_SUCCESS, "failed to join background thread");
01144 
01145   gDbBackgroundThread = nsnull;
01146   return NS_OK;
01147 }
01148 
01149 PR_STATIC_CALLBACK(void)
01150 EventLoop(void *arg)
01151 {
01152   nsresult rv;
01153   LOG(("Starting background thread.\n"));
01154 
01155   nsCOMPtr<nsIEventQueueService> eventQService =
01156       do_GetService(kEventQueueServiceCID, &rv);
01157   NS_ASSERTION(NS_SUCCEEDED(rv), "do_GetService(EventQueueService)");
01158 
01159   rv = eventQService->CreateMonitoredThreadEventQueue();
01160   NS_ASSERTION(NS_SUCCEEDED(rv), "CreateMonitoredThreadEventQueue");
01161 
01162   {
01163     nsAutoMonitor mon(gMonitor);
01164     rv = eventQService->GetSpecialEventQueue(
01165                     nsIEventQueueService::CURRENT_THREAD_EVENT_QUEUE,
01166                     &gEventQ);
01167     NS_ASSERTION(NS_SUCCEEDED(rv), "GetSpecialEventQueue");
01168 
01169     PR_Notify(gMonitor);
01170   }
01171 
01172   while (gKeepRunning) {
01173     PLEvent* ev;
01174     if (NS_SUCCEEDED(gEventQ->WaitForEvent(&ev))) {
01175       gEventQ->HandleEvent(ev);
01176     }
01177   }
01178 
01179   rv = gEventQ->ProcessPendingEvents();
01180 
01181   // Have mWorker release the hold on the event queue.
01182   eventQService->DestroyThreadEventQueue();
01183 
01184   // Release the event queue for clean up.
01185   NS_RELEASE(gEventQ);
01186   gEventQ = nsnull;
01187 
01188   LOG(("Exiting background thread.\n"));
01189 }
01190 
01191 // Shutdown event
01192 PR_STATIC_CALLBACK(void *)
01193 EventHandler(PLEvent *ev)
01194 {
01195   gKeepRunning = PR_FALSE;
01196   return nsnull;
01197 }
01198 
01199 // Clean up shutdown event
01200 PR_STATIC_CALLBACK(void)
01201 DestroyHandler(PLEvent *ev)
01202 {
01203   delete ev;
01204 }