Back to index

lightning-sunbird  0.9+nobinonly
nsGlobalHistory.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 Mozilla Communicator client code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Chris Waterson <waterson@netscape.com>
00024  *   Pierre Phaneuf <pp@ludusdesign.com>
00025  *   Joe Hewitt <hewitt@netscape.com>
00026  *   Blake Ross <blaker@netscape.com>
00027  *   Chris Sears <cbsears_sf@yahoo.com>
00028  *   Michael Lowe <michael.lowe@bigfoot.com>
00029  *
00030  * Alternatively, the contents of this file may be used under the terms of
00031  * either of the GNU General Public License Version 2 or later (the "GPL"),
00032  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00033  * in which case the provisions of the GPL or the LGPL are applicable instead
00034  * of those above. If you wish to allow use of your version of this file only
00035  * under the terms of either the GPL or the LGPL, and not to allow others to
00036  * use your version of this file under the terms of the MPL, indicate your
00037  * decision by deleting the provisions above and replace them with the notice
00038  * and other provisions required by the GPL or the LGPL. If you do not delete
00039  * the provisions above, a recipient may use your version of this file under
00040  * the terms of any one of the MPL, the GPL or the LGPL.
00041  *
00042  * ***** END LICENSE BLOCK ***** */
00043 
00044 /*
00045 
00046   A global browser history implementation that also supports the RDF
00047   datasource interface.
00048 
00049   TODO
00050 
00051   1) Hook up Assert() etc. so that we can delete stuff.
00052 
00053 */
00054 #include "nsNetUtil.h"
00055 #include "nsGlobalHistory.h"
00056 #include "nsCRT.h"
00057 #include "nsIEnumerator.h"
00058 #include "nsIServiceManager.h"
00059 #include "nsEnumeratorUtils.h"
00060 #include "nsRDFCID.h"
00061 #include "nsIDirectoryService.h"
00062 #include "nsAppDirectoryServiceDefs.h"
00063 #include "nsString.h"
00064 #include "nsReadableUtils.h"
00065 #include "nsUnicharUtils.h"
00066 #include "nsXPIDLString.h"
00067 #include "plhash.h"
00068 #include "plstr.h"
00069 #include "prprf.h"
00070 #include "prtime.h"
00071 #include "rdf.h"
00072 #include "nsQuickSort.h"
00073 #include "nsIIOService.h"
00074 #include "nsILocalFile.h"
00075 
00076 #include "nsIURL.h"
00077 #include "nsNetCID.h"
00078 
00079 #include "nsInt64.h"
00080 #include "nsMorkCID.h"
00081 #include "nsIMdbFactoryFactory.h"
00082 
00083 #include "nsIPrefService.h"
00084 #include "nsIPrefBranch2.h"
00085 
00086 #include "nsIObserverService.h"
00087 #include "nsITextToSubURI.h"
00088 
00089 PRInt32 nsGlobalHistory::gRefCnt;
00090 nsIRDFService* nsGlobalHistory::gRDFService;
00091 nsIRDFResource* nsGlobalHistory::kNC_Page;
00092 nsIRDFResource* nsGlobalHistory::kNC_Date;
00093 nsIRDFResource* nsGlobalHistory::kNC_FirstVisitDate;
00094 nsIRDFResource* nsGlobalHistory::kNC_VisitCount;
00095 nsIRDFResource* nsGlobalHistory::kNC_AgeInDays;
00096 nsIRDFResource* nsGlobalHistory::kNC_Name;
00097 nsIRDFResource* nsGlobalHistory::kNC_NameSort;
00098 nsIRDFResource* nsGlobalHistory::kNC_Hostname;
00099 nsIRDFResource* nsGlobalHistory::kNC_Referrer;
00100 nsIRDFResource* nsGlobalHistory::kNC_child;
00101 nsIRDFResource* nsGlobalHistory::kNC_URL;
00102 nsIRDFResource* nsGlobalHistory::kNC_HistoryRoot;
00103 nsIRDFResource* nsGlobalHistory::kNC_HistoryByDate;
00104 nsIMdbFactory* nsGlobalHistory::gMdbFactory = nsnull;
00105 nsIPrefBranch* nsGlobalHistory::gPrefBranch = nsnull;
00106 
00107 #define PREF_BRANCH_BASE                        "browser."
00108 #define PREF_BROWSER_HISTORY_EXPIRE_DAYS        "history_expire_days"
00109 #define PREF_AUTOCOMPLETE_ONLY_TYPED            "urlbar.matchOnlyTyped"
00110 #define PREF_AUTOCOMPLETE_ENABLED               "urlbar.autocomplete.enabled"
00111 
00112 #define FIND_BY_AGEINDAYS_PREFIX "find:datasource=history&match=AgeInDays&method="
00113 
00114 // see bug #319004 -- clamp title and URL to generously-large but not too large
00115 // length
00116 #define HISTORY_URI_LENGTH_MAX 65536
00117 #define HISTORY_TITLE_LENGTH_MAX 4096
00118 
00119 // sync history every 10 seconds
00120 #define HISTORY_SYNC_TIMEOUT (10 * PR_MSEC_PER_SEC)
00121 //#define HISTORY_SYNC_TIMEOUT 3000 // every 3 seconds - testing only!
00122 
00123 // the value of mLastNow expires every 3 seconds
00124 #define HISTORY_EXPIRE_NOW_TIMEOUT (3 * PR_MSEC_PER_SEC)
00125 
00126 static const PRInt64 MSECS_PER_DAY = LL_INIT(20, 500654080);  // (1000000LL * 60 * 60 * 24)
00127 
00128 //----------------------------------------------------------------------
00129 //
00130 // CIDs
00131 
00132 static NS_DEFINE_CID(kRDFServiceCID,        NS_RDFSERVICE_CID);
00133 static NS_DEFINE_CID(kStringBundleServiceCID, NS_STRINGBUNDLESERVICE_CID);
00134 
00135 // closure structures for RemoveMatchingRows
00136 struct matchExpiration_t {
00137   PRTime *expirationDate;
00138   nsGlobalHistory *history;
00139 };
00140 
00141 struct matchHost_t {
00142   const char *host;
00143   PRBool entireDomain;          // should we delete the entire domain?
00144   nsGlobalHistory *history;
00145 };
00146 
00147 struct matchSearchTerm_t {
00148   nsIMdbEnv *env;
00149   nsIMdbStore *store;
00150   
00151   searchTerm *term;
00152   PRBool haveClosure;           // are the rest of the fields valid?
00153   PRInt32 intValue;
00154   nsGlobalHistory* globalHist;
00155 };
00156 
00157 struct matchQuery_t {
00158   searchQuery* query;
00159   nsGlobalHistory* history;
00160 };
00161 
00162 // simple token/value struct
00163 class tokenPair {
00164 public:
00165   tokenPair(const char *aName, PRUint32 aNameLen,
00166             const char *aValue, PRUint32 aValueLen) :
00167     tokenName(aName), tokenNameLength(aNameLen),
00168     tokenValue(aValue), tokenValueLength(aValueLen) { MOZ_COUNT_CTOR(tokenPair); }
00169   ~tokenPair() { MOZ_COUNT_DTOR(tokenPair); }
00170   const char* tokenName;
00171   PRUint32 tokenNameLength;
00172   const char* tokenValue;
00173   PRUint32 tokenValueLength;
00174 };
00175 
00176 // individual search term, pulled from token/value structs
00177 class searchTerm {
00178 public:
00179   searchTerm(const char* aDatasource, PRUint32 aDatasourceLen,
00180              const char *aProperty, PRUint32 aPropertyLen,
00181              const char* aMethod, PRUint32 aMethodLen,
00182              const char* aText, PRUint32 aTextLen):
00183     datasource(aDatasource, aDatasource+aDatasourceLen),
00184     property(aProperty, aProperty+aPropertyLen),
00185     method(aMethod, aMethod+aMethodLen)
00186   {
00187     MOZ_COUNT_CTOR(searchTerm);
00188     nsresult rv;
00189     nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
00190     if (NS_SUCCEEDED(rv))
00191       textToSubURI->UnEscapeAndConvert("UTF-8",  PromiseFlatCString(Substring(aText, aText + aTextLen)).get(), getter_Copies(text));
00192   }
00193   ~searchTerm() {
00194     MOZ_COUNT_DTOR(searchTerm);
00195   }
00196   
00197   nsDependentCSubstring datasource;  // should always be "history" ?
00198   nsDependentCSubstring property;    // AgeInDays, Hostname, etc
00199   nsDependentCSubstring method;      // is, isgreater, isless
00200   nsXPIDLString text;          // text to match
00201   rowMatchCallback match;      // matching callback if needed
00202 };
00203 
00204 // list of terms, plus an optional groupby column
00205 struct searchQuery {
00206   nsVoidArray terms;            // array of searchTerms
00207   mdb_column groupBy;           // column to group by
00208 };
00209 
00210 static PRBool HasCell(nsIMdbEnv *aEnv, nsIMdbRow* aRow, mdb_column aCol)
00211 {
00212   mdbYarn yarn;
00213   mdb_err err = aRow->AliasCellYarn(aEnv, aCol, &yarn);
00214 
00215   // no cell
00216   if (err != 0)
00217     return PR_FALSE;
00218 
00219   // if we have the cell, make sure it has a value??
00220   return (yarn.mYarn_Fill != 0);
00221 }
00222 
00223 PRTime
00224 nsGlobalHistory::NormalizeTime(PRTime aTime)
00225 {
00226   // we can optimize this by converting the time to local time, rounding
00227   // down to the previous day boundary, and then converting back to UTC.
00228   // This avoids two costly calls to localtime()
00229 
00230   // we calculate (gmtTime - (gmtTime % MSECS_PER_DAY)) - mCachedGMTOffset
00231   PRTime gmtTime;
00232   LL_ADD(gmtTime, aTime, mCachedGMTOffset);
00233   PRInt64 curDayUSec;
00234   LL_MOD(curDayUSec, gmtTime, MSECS_PER_DAY);
00235   PRTime gmtMidnight;
00236   LL_SUB(gmtMidnight, gmtTime, curDayUSec);
00237   PRTime localMidnight;
00238   LL_SUB(localMidnight, gmtMidnight, mCachedGMTOffset);
00239   
00240   return localMidnight;
00241 }
00242 
00243 PRInt32
00244 nsGlobalHistory::GetAgeInDays(PRTime aDate)
00245 {
00246   PRTime timeNow      = GetNow();
00247   PRTime dateMidnight = NormalizeTime(aDate);
00248 
00249   PRTime diff;
00250   LL_SUB(diff, timeNow, dateMidnight);
00251   PRInt64 ageInDays;
00252   LL_DIV(ageInDays, diff, MSECS_PER_DAY);
00253   PRInt32 retval;
00254   LL_L2I(retval, ageInDays);
00255 
00256   return retval;
00257 }
00258 
00259 PRBool
00260 nsGlobalHistory::MatchExpiration(nsIMdbRow *row, PRTime* expirationDate)
00261 {
00262   nsresult rv;
00263   
00264   // hidden and typed urls always match because they're invalid,
00265   // so we want to expire them asap.  (if they were valid, they'd
00266   // have been unhidden -- see AddExistingPageToDatabase)
00267   if (HasCell(mEnv, row, kToken_HiddenColumn) && HasCell(mEnv, row, kToken_TypedColumn))
00268     return PR_TRUE;
00269 
00270   PRTime lastVisitedTime;
00271   rv = GetRowValue(row, kToken_LastVisitDateColumn, &lastVisitedTime);
00272 
00273   if (NS_FAILED(rv)) 
00274     return PR_FALSE;
00275   
00276   return LL_CMP(lastVisitedTime, <, *expirationDate);
00277 }
00278 
00279 static PRBool
00280 matchAgeInDaysCallback(nsIMdbRow *row, void *aClosure)
00281 {
00282   matchSearchTerm_t *matchSearchTerm = (matchSearchTerm_t*)aClosure;
00283   const searchTerm *term = matchSearchTerm->term;
00284   nsIMdbEnv *env = matchSearchTerm->env;
00285   nsIMdbStore *store = matchSearchTerm->store;
00286   
00287   // fill in the rest of the closure if it's not filled in yet
00288   // this saves us from recalculating this stuff on every row
00289   if (!matchSearchTerm->haveClosure) {
00290     PRInt32 err;
00291     // Need to create an nsAutoString to use ToInteger
00292     matchSearchTerm->intValue =  nsAutoString(term->text).ToInteger(&err);
00293     if (err != 0) return PR_FALSE;
00294     matchSearchTerm->haveClosure = PR_TRUE;
00295   }
00296   
00297   // XXX convert the property to a column, get the column value
00298 
00299   mdb_column column;
00300   mdb_err err = store->StringToToken(env, "LastVisitDate", &column);
00301   if (err != 0) return PR_FALSE;
00302 
00303   mdbYarn yarn;
00304   err = row->AliasCellYarn(env, column, &yarn);
00305   if (err != 0) return PR_FALSE;
00306   
00307   PRTime rowDate;
00308   PR_sscanf((const char*)yarn.mYarn_Buf, "%lld", &rowDate);
00309 
00310   PRInt32 days = matchSearchTerm->globalHist->GetAgeInDays(rowDate);
00311   
00312   if (term->method.Equals("is"))
00313     return (days == matchSearchTerm->intValue);
00314   else if (term->method.Equals("isgreater"))
00315     return (days >  matchSearchTerm->intValue);
00316   else if (term->method.Equals("isless"))
00317     return (days <  matchSearchTerm->intValue);
00318   
00319   return PR_FALSE;
00320 }
00321 
00322 static PRBool
00323 matchExpirationCallback(nsIMdbRow *row, void *aClosure)
00324 {
00325   matchExpiration_t *expires = (matchExpiration_t*)aClosure;
00326   return expires->history->MatchExpiration(row, expires->expirationDate);
00327 }
00328 
00329 static PRBool
00330 matchAllCallback(nsIMdbRow *row, void *aClosure)
00331 {
00332   return PR_TRUE;
00333 }
00334 
00335 static PRBool
00336 matchHostCallback(nsIMdbRow *row, void *aClosure)
00337 {
00338   matchHost_t *hostInfo = (matchHost_t*)aClosure;
00339   return hostInfo->history->MatchHost(row, hostInfo);
00340 }
00341 
00342 static PRBool
00343 matchQueryCallback(nsIMdbRow *row, void *aClosure)
00344 {
00345   matchQuery_t *query = (matchQuery_t*)aClosure;
00346   return query->history->RowMatches(row, query->query);
00347 }
00348 //----------------------------------------------------------------------
00349 
00350 nsMdbTableEnumerator::nsMdbTableEnumerator()
00351   : mEnv(nsnull),
00352     mTable(nsnull),
00353     mCursor(nsnull),
00354     mCurrent(nsnull)
00355 {
00356 }
00357 
00358 
00359 nsresult
00360 nsMdbTableEnumerator::Init(nsIMdbEnv* aEnv,
00361                            nsIMdbTable* aTable)
00362 {
00363   NS_PRECONDITION(aEnv != nsnull, "null ptr");
00364   if (! aEnv)
00365     return NS_ERROR_NULL_POINTER;
00366 
00367   NS_PRECONDITION(aTable != nsnull, "null ptr");
00368   if (! aTable)
00369     return NS_ERROR_NULL_POINTER;
00370 
00371   mEnv = aEnv;
00372   NS_ADDREF(mEnv);
00373 
00374   mTable = aTable;
00375   NS_ADDREF(mTable);
00376 
00377   mdb_err err;
00378   err = mTable->GetTableRowCursor(mEnv, -1, &mCursor);
00379   if (err != 0) return NS_ERROR_FAILURE;
00380   
00381   return NS_OK;
00382 }
00383 
00384 
00385 nsMdbTableEnumerator::~nsMdbTableEnumerator()
00386 {
00387   NS_IF_RELEASE(mCurrent);
00388 
00389   NS_IF_RELEASE(mCursor);
00390 
00391   NS_IF_RELEASE(mTable);
00392 
00393   NS_IF_RELEASE(mEnv);
00394 }
00395 
00396 
00397 NS_IMPL_ISUPPORTS1(nsMdbTableEnumerator, nsISimpleEnumerator)
00398 
00399 NS_IMETHODIMP
00400 nsMdbTableEnumerator::HasMoreElements(PRBool* _result)
00401 {
00402   if (! mCurrent) {
00403     mdb_err err;
00404 
00405     while (1) {
00406       mdb_pos pos;
00407       err = mCursor->NextRow(mEnv, &mCurrent, &pos);
00408       if (err != 0) return NS_ERROR_FAILURE;
00409 
00410       // If there are no more rows, then bail.
00411       if (! mCurrent)
00412         break;
00413 
00414       // If this is a result, the stop.
00415       if (IsResult(mCurrent))
00416         break;
00417 
00418       // Otherwise, drop the ref to the row we retrieved, and continue
00419       // on to the next one.
00420       NS_RELEASE(mCurrent);
00421       mCurrent = nsnull;
00422     }
00423   }
00424 
00425   *_result = (mCurrent != nsnull);
00426   return NS_OK;
00427 }
00428 
00429 
00430 NS_IMETHODIMP
00431 nsMdbTableEnumerator::GetNext(nsISupports** _result)
00432 {
00433   nsresult rv;
00434 
00435   PRBool hasMore;
00436   rv = HasMoreElements(&hasMore);
00437   if (NS_FAILED(rv)) return rv;
00438 
00439   if (! hasMore)
00440     return NS_ERROR_UNEXPECTED;
00441 
00442   rv = ConvertToISupports(mCurrent, _result);
00443 
00444   NS_RELEASE(mCurrent);
00445   mCurrent = nsnull;
00446 
00447   return rv;
00448 }
00449 
00450 
00451 //----------------------------------------------------------------------
00452 //
00453 // nsGlobalHistory
00454 //
00455 //   ctor dtor etc.
00456 //
00457 
00458 
00459 nsGlobalHistory::nsGlobalHistory()
00460   : mExpireDays(9), // make default be nine days
00461     mAutocompleteOnlyTyped(PR_FALSE),
00462     mBatchesInProgress(0),
00463     mNowValid(PR_FALSE),
00464     mDirty(PR_FALSE),
00465     mEnv(nsnull),
00466     mStore(nsnull),
00467     mTable(nsnull)
00468 {
00469   LL_I2L(mFileSizeOnDisk, 0);
00470   
00471   // commonly used prefixes that should be chopped off all 
00472   // history and input urls before comparison
00473 
00474   mIgnoreSchemes.AppendString(NS_LITERAL_STRING("http://"));
00475   mIgnoreSchemes.AppendString(NS_LITERAL_STRING("https://"));
00476   mIgnoreSchemes.AppendString(NS_LITERAL_STRING("ftp://"));
00477   mIgnoreHostnames.AppendString(NS_LITERAL_STRING("www."));
00478   mIgnoreHostnames.AppendString(NS_LITERAL_STRING("ftp."));
00479 
00480   mTypedHiddenURIs.Init(3);
00481 }
00482 
00483 nsGlobalHistory::~nsGlobalHistory()
00484 {
00485   gRDFService->UnregisterDataSource(this);
00486 
00487   nsresult rv;
00488   rv = CloseDB();
00489 
00490   NS_IF_RELEASE(mTable);
00491   NS_IF_RELEASE(mStore);
00492   
00493   if (--gRefCnt == 0) {
00494     NS_IF_RELEASE(gRDFService);
00495 
00496     NS_IF_RELEASE(kNC_Page);
00497     NS_IF_RELEASE(kNC_Date);
00498     NS_IF_RELEASE(kNC_FirstVisitDate);
00499     NS_IF_RELEASE(kNC_VisitCount);
00500     NS_IF_RELEASE(kNC_AgeInDays);
00501     NS_IF_RELEASE(kNC_Name);
00502     NS_IF_RELEASE(kNC_NameSort);
00503     NS_IF_RELEASE(kNC_Hostname);
00504     NS_IF_RELEASE(kNC_Referrer);
00505     NS_IF_RELEASE(kNC_child);
00506     NS_IF_RELEASE(kNC_URL);
00507     NS_IF_RELEASE(kNC_HistoryRoot);
00508     NS_IF_RELEASE(kNC_HistoryByDate);
00509     
00510     NS_IF_RELEASE(gMdbFactory);
00511     NS_IF_RELEASE(gPrefBranch);
00512   }
00513 
00514   NS_IF_RELEASE(mEnv);
00515   if (mSyncTimer)
00516     mSyncTimer->Cancel();
00517 
00518   if (mExpireNowTimer)
00519     mExpireNowTimer->Cancel();
00520 
00521 }
00522 
00523 
00524 
00525 //----------------------------------------------------------------------
00526 //
00527 // nsGlobalHistory
00528 //
00529 //   nsISupports methods
00530 
00531 NS_IMPL_ISUPPORTS7(nsGlobalHistory,
00532                    nsIGlobalHistory2,
00533                    nsIBrowserHistory,
00534                    nsIObserver,
00535                    nsISupportsWeakReference,
00536                    nsIRDFDataSource,
00537                    nsIRDFRemoteDataSource,
00538                    nsIAutoCompleteSession)
00539 
00540 //----------------------------------------------------------------------
00541 //
00542 // nsGlobalHistory
00543 //
00544 //   nsIGlobalHistory2 methods
00545 //
00546 
00547 
00548 NS_IMETHODIMP
00549 nsGlobalHistory::AddURI(nsIURI *aURI, PRBool aRedirect, PRBool aTopLevel, nsIURI *aReferrer)
00550 {
00551   nsresult rv;
00552   NS_ENSURE_ARG_POINTER(aURI);
00553 
00554   // If history is set to expire after 0 days,
00555   // then it's technically disabled. Don't even
00556   // bother adding the page
00557   if (mExpireDays == 0)
00558     return NS_OK;
00559 
00560   // filter out unwanted URIs such as chrome: mailbox: etc
00561   // The model is really if we don't know differently then add which basically
00562   // means we are suppose to try all the things we know not to allow in and
00563   // then if we don't bail go on and allow it in.  But here lets compare
00564   // against the most common case we know to allow in and go on and say yes
00565   // to it.
00566 
00567   PRBool isHTTP = PR_FALSE;
00568   PRBool isHTTPS = PR_FALSE;
00569 
00570   NS_ENSURE_SUCCESS(rv = aURI->SchemeIs("http", &isHTTP), rv);
00571   NS_ENSURE_SUCCESS(rv = aURI->SchemeIs("https", &isHTTPS), rv);
00572 
00573   if (!isHTTP && !isHTTPS) {
00574     PRBool isAbout, isImap, isNews, isMailbox, isViewSource, isChrome, isData;
00575 
00576     rv = aURI->SchemeIs("about", &isAbout);
00577     rv |= aURI->SchemeIs("imap", &isImap);
00578     rv |= aURI->SchemeIs("news", &isNews);
00579     rv |= aURI->SchemeIs("mailbox", &isMailbox);
00580     rv |= aURI->SchemeIs("view-source", &isViewSource);
00581     rv |= aURI->SchemeIs("chrome", &isChrome);
00582     rv |= aURI->SchemeIs("data", &isData);
00583     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
00584 
00585     if (isAbout || isImap || isNews || isMailbox || isViewSource || isChrome || isData) {
00586       return NS_OK;
00587     }
00588   }
00589 
00590   rv = OpenDB();
00591   NS_ENSURE_SUCCESS(rv, rv);
00592 
00593   nsCAutoString URISpec;
00594   rv = aURI->GetSpec(URISpec);
00595   NS_ENSURE_SUCCESS(rv, rv);
00596 
00597   if (URISpec.Length() > HISTORY_URI_LENGTH_MAX)
00598      return NS_OK;
00599 
00600   nsCAutoString referrerSpec;
00601   if (aReferrer) {
00602     rv = aReferrer->GetSpec(referrerSpec);
00603     NS_ENSURE_SUCCESS(rv, rv);
00604   }
00605 
00606   PRTime now = GetNow();
00607 
00608   // For notifying observers, later...
00609   nsCOMPtr<nsIRDFResource> url;
00610   rv = gRDFService->GetResource(URISpec, getter_AddRefs(url));
00611   if (NS_FAILED(rv)) return rv;
00612 
00613   nsCOMPtr<nsIRDFDate> date;
00614   rv = gRDFService->GetDateLiteral(now, getter_AddRefs(date));
00615   if (NS_FAILED(rv)) return rv;
00616 
00617   PRBool isJavascript;
00618   rv = aURI->SchemeIs("javascript", &isJavascript);
00619   NS_ENSURE_SUCCESS(rv, rv);
00620 
00621   nsCOMPtr<nsIMdbRow> row;
00622   rv = FindRow(kToken_URLColumn, URISpec.get(), getter_AddRefs(row));
00623 
00624   if (NS_SUCCEEDED(rv)) {
00625 
00626     // If this is not a JS url, not a redirected URI and not in a frame, 
00627     // unhide it since URIs are added hidden if they are redirected, in a 
00628     // frame or typed.
00629     PRBool wasTyped = HasCell(mEnv, row, kToken_TypedColumn);
00630     if (wasTyped) {
00631       mTypedHiddenURIs.Remove(URISpec);
00632     }
00633     if ((!isJavascript && !aRedirect && aTopLevel) || wasTyped) {
00634       row->CutColumn(mEnv, kToken_HiddenColumn);
00635     }
00636 
00637     // update the database, and get the old info back
00638     PRTime oldDate;
00639     PRInt32 oldCount;
00640     rv = AddExistingPageToDatabase(row, now, referrerSpec.get(), &oldDate, &oldCount);
00641     NS_ASSERTION(NS_SUCCEEDED(rv), "AddExistingPageToDatabase failed; see bug 88961");
00642     if (NS_FAILED(rv)) return rv;
00643     
00644     // Notify observers
00645     
00646     // visit date
00647     nsCOMPtr<nsIRDFDate> oldDateLiteral;
00648     rv = gRDFService->GetDateLiteral(oldDate, getter_AddRefs(oldDateLiteral));
00649     if (NS_FAILED(rv)) return rv;
00650     
00651     rv = NotifyChange(url, kNC_Date, oldDateLiteral, date);
00652     if (NS_FAILED(rv)) return rv;
00653 
00654     // visit count
00655     nsCOMPtr<nsIRDFInt> oldCountLiteral;
00656     rv = gRDFService->GetIntLiteral(oldCount, getter_AddRefs(oldCountLiteral));
00657     if (NS_FAILED(rv)) return rv;
00658 
00659     nsCOMPtr<nsIRDFInt> newCountLiteral;
00660     rv = gRDFService->GetIntLiteral(oldCount+1,
00661                                     getter_AddRefs(newCountLiteral));
00662     if (NS_FAILED(rv)) return rv;
00663 
00664     rv = NotifyChange(url, kNC_VisitCount, oldCountLiteral, newCountLiteral);
00665     if (NS_FAILED(rv)) return rv;
00666     
00667   }
00668   else {
00669     rv = AddNewPageToDatabase(URISpec.get(), now, referrerSpec.get(), getter_AddRefs(row));
00670     NS_ASSERTION(NS_SUCCEEDED(rv), "AddNewPageToDatabase failed; see bug 88961");
00671     if (NS_FAILED(rv)) return rv;
00672     
00673     if (isJavascript || aRedirect || !aTopLevel) {
00674       // if this is a JS url, or a redirected URI or in a frame, hide it in
00675       // global history so that it doesn't show up in the autocomplete
00676       // dropdown. We'll unhide non-JS urls later if we visit the URI not as 
00677       // part of a redirect and not in a frame. See bug 197127, bug 161531 and
00678       // bug 322106 for details.
00679       rv = SetRowValue(row, kToken_HiddenColumn, 1);
00680       NS_ENSURE_SUCCESS(rv, rv);
00681     }
00682     else {
00683       // Notify observers
00684       rv = NotifyAssert(url, kNC_Date, date);
00685       if (NS_FAILED(rv)) return rv;
00686 
00687       rv = NotifyAssert(kNC_HistoryRoot, kNC_child, url);
00688       if (NS_FAILED(rv)) return rv;
00689     
00690       NotifyFindAssertions(url, row);
00691     }
00692   }
00693 
00694   // Store last visited page if we have the pref set accordingly
00695   if (aTopLevel) {
00696     PRInt32 choice = 0;
00697     if (NS_SUCCEEDED(gPrefBranch->GetIntPref("startup.page", &choice))) {
00698       if (choice != 2) {
00699         if (NS_SUCCEEDED(gPrefBranch->GetIntPref("windows.loadOnNewWindow", &choice))) {
00700           if (choice != 2) {
00701             gPrefBranch->GetIntPref("tabs.loadOnNewTab", &choice);
00702           }
00703         }
00704       }
00705     }
00706     if (choice == 2) {
00707       NS_ENSURE_STATE(mMetaRow);
00708 
00709       SetRowValue(mMetaRow, kToken_LastPageVisited, URISpec.get());
00710     }
00711   }
00712  
00713   SetDirty();
00714   
00715   return NS_OK;
00716 }
00717 
00718 nsresult
00719 nsGlobalHistory::AddExistingPageToDatabase(nsIMdbRow *row,
00720                                            PRTime aDate,
00721                                            const char *aReferrer,
00722                                            PRTime *aOldDate,
00723                                            PRInt32 *aOldCount)
00724 {
00725   nsresult rv;
00726   nsCAutoString oldReferrer;
00727   
00728   // Update last visit date.
00729   // First get the old date so we can update observers...
00730   rv = GetRowValue(row, kToken_LastVisitDateColumn, aOldDate);
00731   if (NS_FAILED(rv)) return rv;
00732 
00733   // get the old count, so we can update it
00734   rv = GetRowValue(row, kToken_VisitCountColumn, aOldCount);
00735   if (NS_FAILED(rv) || *aOldCount < 1)
00736     *aOldCount = 1;             // assume we've visited at least once
00737 
00738   // ...now set the new date.
00739   SetRowValue(row, kToken_LastVisitDateColumn, aDate);
00740   SetRowValue(row, kToken_VisitCountColumn, (*aOldCount) + 1);
00741 
00742   if (aReferrer && *aReferrer) {
00743     rv = GetRowValue(row, kToken_ReferrerColumn, oldReferrer);
00744     // No referrer? Now there is!
00745     if (NS_FAILED(rv) || oldReferrer.IsEmpty())
00746       SetRowValue(row, kToken_ReferrerColumn, aReferrer);
00747   }
00748 
00749   return NS_OK;
00750 }
00751 
00752 nsresult
00753 nsGlobalHistory::AddNewPageToDatabase(const char *aURL,
00754                                       PRTime aDate,
00755                                       const char *aReferrer,
00756                                       nsIMdbRow **aResult)
00757 {
00758   mdb_err err;
00759   
00760   // Create a new row
00761   mdbOid rowId;
00762   rowId.mOid_Scope = kToken_HistoryRowScope;
00763   rowId.mOid_Id    = mdb_id(-1);
00764   
00765   NS_PRECONDITION(mTable != nsnull, "not initialized");
00766   if (! mTable)
00767     return NS_ERROR_NOT_INITIALIZED;
00768 
00769   nsCOMPtr<nsIMdbRow> row;
00770   err = mTable->NewRow(mEnv, &rowId, getter_AddRefs(row));
00771   if (err != 0) return NS_ERROR_FAILURE;
00772 
00773   // Set the URL
00774   SetRowValue(row, kToken_URLColumn, aURL);
00775   
00776   // Set the date.
00777   SetRowValue(row, kToken_LastVisitDateColumn, aDate);
00778   SetRowValue(row, kToken_FirstVisitDateColumn, aDate);
00779 
00780   // Set the referrer if there is one.
00781   if (aReferrer && *aReferrer)
00782     SetRowValue(row, kToken_ReferrerColumn, aReferrer);
00783 
00784   nsCOMPtr<nsIURI> uri;
00785   NS_NewURI(getter_AddRefs(uri), nsDependentCString(aURL), nsnull, nsnull);
00786   nsCAutoString hostname;
00787   if (uri)
00788       uri->GetHost(hostname);
00789 
00790   SetRowValue(row, kToken_HostnameColumn, hostname.get());
00791 
00792   *aResult = row;
00793   NS_ADDREF(*aResult);
00794 
00795   return NS_OK;
00796 }
00797 
00798 nsresult
00799 nsGlobalHistory::RemovePageInternal(const char *aSpec)
00800 {
00801   if (!mTable) return NS_ERROR_NOT_INITIALIZED;
00802   // find the old row, ignore it if we don't have it
00803   nsCOMPtr<nsIMdbRow> row;
00804   nsresult rv = FindRow(kToken_URLColumn, aSpec, getter_AddRefs(row));
00805   if (NS_FAILED(rv)) return NS_OK;
00806 
00807   // remove the row
00808   mdb_err err = mTable->CutRow(mEnv, row);
00809   NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE);
00810 
00811   // if there are batches in progress, we don't want to notify
00812   // observers that we're deleting items. the caller promises
00813   // to handle whatever UI updating is necessary when we're finished.  
00814   if (!mBatchesInProgress) {
00815     // get the resource so we can do the notification
00816     nsCOMPtr<nsIRDFResource> oldRowResource;
00817     gRDFService->GetResource(nsDependentCString(aSpec), getter_AddRefs(oldRowResource));
00818     NotifyFindUnassertions(oldRowResource, row);
00819   }
00820 
00821   // not a fatal error if we can't cut all column
00822   err = row->CutAllColumns(mEnv);
00823   NS_ASSERTION(err == 0, "couldn't cut all columns");
00824 
00825   return Commit(kCompressCommit);
00826 }
00827 
00828 nsresult
00829 nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRTime& aValue)
00830 {
00831   mdb_err err;
00832   nsCAutoString val;
00833   val.AppendInt(aValue);
00834 
00835   mdbYarn yarn = { (void *)val.get(), val.Length(), val.Length(), 0, 0, nsnull };
00836   
00837   err = aRow->AddColumn(mEnv, aCol, &yarn);
00838 
00839   if ( err != 0 ) return NS_ERROR_FAILURE;
00840   
00841   return NS_OK;
00842 }
00843 
00844 nsresult
00845 nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol,
00846                              const PRUnichar* aValue)
00847 {
00848   mdb_err err;
00849 
00850   PRInt32 len = (nsCRT::strlen(aValue) * sizeof(PRUnichar));
00851   PRUnichar *swapval = nsnull;
00852 
00853   // eventually turn this on when we're confident in mork's abilitiy
00854   // to handle yarn forms properly
00855 #if 0
00856   NS_ConvertUCS2toUTF8 utf8Value(aValue);
00857   printf("Storing utf8 value %s\n", utf8Value.get());
00858   mdbYarn yarn = { (void *)utf8Value.get(), utf8Value.Length(), utf8Value.Length(), 0, 1, nsnull };
00859 #else
00860 
00861   if (mReverseByteOrder) {
00862     // The file is other-endian.  Byte-swap the value.
00863     swapval = (PRUnichar *)malloc(len);
00864     if (!swapval)
00865       return NS_ERROR_OUT_OF_MEMORY;
00866     SwapBytes(aValue, swapval, len / sizeof(PRUnichar));
00867     aValue = swapval;
00868   }
00869   mdbYarn yarn = { (void *)aValue, len, len, 0, 0, nsnull };
00870   
00871 #endif
00872   err = aRow->AddColumn(mEnv, aCol, &yarn);
00873   if (swapval)
00874     free(swapval);
00875   if (err != 0) return NS_ERROR_FAILURE;
00876   return NS_OK;
00877 }
00878 
00879 nsresult
00880 nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol,
00881                              const char* aValue)
00882 {
00883   mdb_err err;
00884   PRInt32 len = PL_strlen(aValue);
00885   mdbYarn yarn = { (void*) aValue, len, len, 0, 0, nsnull };
00886   err = aRow->AddColumn(mEnv, aCol, &yarn);
00887   if (err != 0) return NS_ERROR_FAILURE;
00888 
00889   return NS_OK;
00890 }
00891 
00892 nsresult
00893 nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRInt32 aValue)
00894 {
00895   mdb_err err;
00896   
00897   nsCAutoString buf; buf.AppendInt(aValue);
00898   mdbYarn yarn = { (void *)buf.get(), buf.Length(), buf.Length(), 0, 0, nsnull };
00899 
00900   err = aRow->AddColumn(mEnv, aCol, &yarn);
00901   
00902   if (err != 0) return NS_ERROR_FAILURE;
00903 
00904   return NS_OK;
00905 }
00906 
00907 nsresult
00908 nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol,
00909                              nsAString& aResult)
00910 {
00911   mdb_err err;
00912   
00913   mdbYarn yarn;
00914   err = aRow->AliasCellYarn(mEnv, aCol, &yarn);
00915   if (err != 0) return NS_ERROR_FAILURE;
00916 
00917   aResult.Truncate(0);
00918   if (!yarn.mYarn_Fill)
00919     return NS_OK;
00920   
00921   switch (yarn.mYarn_Form) {
00922   case 0:                       // unicode
00923     if (mReverseByteOrder) {
00924       // The file is other-endian; we must byte-swap the result.
00925       PRUnichar *swapval;
00926       int len = yarn.mYarn_Fill / sizeof(PRUnichar);
00927       swapval = (PRUnichar *)malloc(yarn.mYarn_Fill);
00928       if (!swapval)
00929         return NS_ERROR_OUT_OF_MEMORY;
00930       SwapBytes((const PRUnichar *)yarn.mYarn_Buf, swapval, len);
00931       aResult.Assign(swapval, len);
00932       free(swapval);
00933     }
00934     else
00935       aResult.Assign((const PRUnichar *)yarn.mYarn_Buf, yarn.mYarn_Fill/sizeof(PRUnichar));
00936     break;
00937 
00938     // eventually we'll be supporting this in SetRowValue()
00939   case 1:                       // UTF8
00940     CopyUTF8toUTF16(Substring((const char*)yarn.mYarn_Buf,
00941                               (const char*)yarn.mYarn_Buf + yarn.mYarn_Fill),
00942                     aResult);
00943     break;
00944 
00945   default:
00946     return NS_ERROR_UNEXPECTED;
00947   }
00948   return NS_OK;
00949 }
00950 
00951 // Copy an array of 16-bit values, reversing the byte order.
00952 void
00953 nsGlobalHistory::SwapBytes(const PRUnichar *source, PRUnichar *dest,
00954                            PRInt32 aLen)
00955 {
00956   PRUint16 c;
00957   const PRUnichar *inp;
00958   PRUnichar *outp;
00959   PRInt32 i;
00960 
00961   inp = source;
00962   outp = dest;
00963   for (i = 0; i < aLen; i++) {
00964     c = *inp++;
00965     *outp++ = (((c >> 8) & 0xff) | (c << 8));
00966   }
00967   return;
00968 }
00969       
00970 nsresult
00971 nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol,
00972                              PRTime *aResult)
00973 {
00974   mdb_err err;
00975   
00976   mdbYarn yarn;
00977   err = aRow->AliasCellYarn(mEnv, aCol, &yarn);
00978   if (err != 0) return NS_ERROR_FAILURE;
00979 
00980   *aResult = LL_ZERO;
00981   
00982   if (!yarn.mYarn_Fill || !yarn.mYarn_Buf)
00983     return NS_OK;
00984 
00985   PR_sscanf((const char*)yarn.mYarn_Buf, "%lld", aResult);
00986 
00987   return NS_OK;
00988 }
00989 
00990 nsresult
00991 nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol,
00992                              PRInt32 *aResult)
00993 {
00994   mdb_err err;
00995   
00996   mdbYarn yarn;
00997   err = aRow->AliasCellYarn(mEnv, aCol, &yarn);
00998   if (err != 0) return NS_ERROR_FAILURE;
00999 
01000   if (yarn.mYarn_Buf)
01001     *aResult = atoi((char *)yarn.mYarn_Buf);
01002   else
01003     *aResult = 0;
01004   
01005   return NS_OK;
01006 }
01007 
01008 nsresult
01009 nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol,
01010                              nsACString& aResult)
01011 {
01012   mdb_err err;
01013   
01014   mdbYarn yarn;
01015   err = aRow->AliasCellYarn(mEnv, aCol, &yarn);
01016   if (err != 0) return NS_ERROR_FAILURE;
01017 
01018   const char* startPtr = (const char*)yarn.mYarn_Buf;
01019   if (startPtr)
01020     aResult.Assign(Substring(startPtr, startPtr + yarn.mYarn_Fill));
01021   else
01022     aResult.Truncate();
01023   
01024   return NS_OK;
01025 }
01026 
01027 NS_IMETHODIMP
01028 nsGlobalHistory::GetCount(PRUint32* aCount)
01029 {
01030   NS_ENSURE_ARG_POINTER(aCount);
01031   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
01032   if (!mTable) return NS_ERROR_FAILURE;
01033 
01034   mdb_err err = mTable->GetCount(mEnv, aCount);
01035   return (err == 0) ? NS_OK : NS_ERROR_FAILURE;
01036 }
01037 
01038 NS_IMETHODIMP
01039 nsGlobalHistory::SetPageTitle(nsIURI *aURI, const nsAString& aTitle)
01040 {
01041   nsresult rv;
01042   NS_ENSURE_ARG_POINTER(aURI);
01043 
01044   nsAutoString titleString(StringHead(aTitle, HISTORY_TITLE_LENGTH_MAX));
01045 
01046   // skip about: URIs to avoid reading in the db (about:blank, especially)
01047   PRBool isAbout;
01048   rv = aURI->SchemeIs("about", &isAbout);
01049   NS_ENSURE_SUCCESS(rv, rv);
01050   if (isAbout) return NS_OK;
01051 
01052   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
01053   
01054   nsCAutoString URISpec;
01055   rv = aURI->GetSpec(URISpec);
01056   NS_ENSURE_SUCCESS(rv, rv);
01057 
01058   nsCOMPtr<nsIMdbRow> row;
01059   rv = FindRow(kToken_URLColumn, URISpec.get(), getter_AddRefs(row));
01060 
01061   // if the row doesn't exist, we silently succeed
01062   if (rv == NS_ERROR_NOT_AVAILABLE) return NS_OK;
01063   NS_ENSURE_SUCCESS(rv, rv);
01064 
01065   // Get the old title so we can notify observers
01066   nsAutoString oldtitle;
01067   rv = GetRowValue(row, kToken_NameColumn, oldtitle);
01068   if (NS_FAILED(rv)) return rv;
01069 
01070   nsCOMPtr<nsIRDFLiteral> oldname;
01071   if (!oldtitle.IsEmpty()) {
01072     rv = gRDFService->GetLiteral(oldtitle.get(), getter_AddRefs(oldname));
01073     if (NS_FAILED(rv)) return rv;
01074   }
01075 
01076   SetRowValue(row, kToken_NameColumn, titleString.get());
01077 
01078   // ...and update observers
01079   nsCOMPtr<nsIRDFResource> url;
01080   rv = gRDFService->GetResource(URISpec, getter_AddRefs(url));
01081   if (NS_FAILED(rv)) return rv;
01082 
01083   nsCOMPtr<nsIRDFLiteral> name;
01084   rv = gRDFService->GetLiteral(titleString.get(), getter_AddRefs(name));
01085   if (NS_FAILED(rv)) return rv;
01086 
01087   if (oldname) {
01088     rv = NotifyChange(url, kNC_Name, oldname, name);
01089   }
01090   else {
01091     rv = NotifyAssert(url, kNC_Name, name);
01092   }
01093 
01094   return rv;
01095 }
01096 
01097 
01098 NS_IMETHODIMP
01099 nsGlobalHistory::RemovePage(nsIURI *aURI)
01100 {
01101   nsCAutoString spec;
01102   nsresult rv = aURI->GetSpec(spec);
01103   if (NS_SUCCEEDED(rv))
01104     rv = RemovePageInternal(spec.get());
01105   return rv;
01106 }
01107 
01108 NS_IMETHODIMP
01109 nsGlobalHistory::RemovePagesFromHost(const nsACString &aHost, PRBool aEntireDomain)
01110 {
01111   const nsCString &host = PromiseFlatCString(aHost);
01112 
01113   matchHost_t hostInfo;
01114   hostInfo.history = this;
01115   hostInfo.entireDomain = aEntireDomain;
01116   hostInfo.host = host.get();
01117   
01118   nsresult rv = RemoveMatchingRows(matchHostCallback, (void *)&hostInfo, PR_TRUE);
01119   if (NS_FAILED(rv)) return rv;
01120 
01121   return Commit(kCompressCommit);
01122 }
01123 
01124 PRBool
01125 nsGlobalHistory::MatchHost(nsIMdbRow *aRow,
01126                            matchHost_t *hostInfo)
01127 {
01128   mdb_err err;
01129   nsresult rv;
01130 
01131   mdbYarn yarn;
01132   err = aRow->AliasCellYarn(mEnv, kToken_URLColumn, &yarn);
01133   if (err != 0) return PR_FALSE;
01134 
01135   nsCOMPtr<nsIURI> uri;
01136   // do smart zero-termination
01137   const char* startPtr = (const char *)yarn.mYarn_Buf;
01138   rv = NS_NewURI(getter_AddRefs(uri),
01139                  Substring(startPtr, startPtr + yarn.mYarn_Fill));
01140   if (NS_FAILED(rv)) return PR_FALSE;
01141 
01142   nsCAutoString urlHost;
01143   rv = uri->GetHost(urlHost);
01144   if (NS_FAILED(rv)) return PR_FALSE;
01145 
01146   if (PL_strcmp(urlHost.get(), hostInfo->host) == 0)
01147     return PR_TRUE;
01148 
01149   // now try for a domain match, if necessary
01150   if (hostInfo->entireDomain) {
01151     // do a reverse-search to match the end of the string
01152     const char *domain = PL_strrstr(urlHost.get(), hostInfo->host);
01153     
01154     // now verify that we're matching EXACTLY the domain, and
01155     // not some random string inside the hostname
01156     if (domain && (PL_strcmp(domain, hostInfo->host) == 0))
01157       return PR_TRUE;
01158   }
01159   
01160   return PR_FALSE;
01161 }
01162 
01163 NS_IMETHODIMP
01164 nsGlobalHistory::RemoveAllPages()
01165 {
01166   nsresult rv;
01167 
01168   rv = RemoveMatchingRows(matchAllCallback, nsnull, PR_TRUE);
01169   if (NS_FAILED(rv)) return rv;
01170   
01171   // Reset the file byte order.
01172   rv = InitByteOrder(PR_TRUE);
01173   if (NS_FAILED(rv)) return rv;
01174 
01175   return Commit(kCompressCommit);
01176 }
01177 
01178 nsresult
01179 nsGlobalHistory::RemoveMatchingRows(rowMatchCallback aMatchFunc,
01180                                     void *aClosure,
01181                                     PRBool notify)
01182 {
01183   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
01184   nsresult rv;
01185   if (!mTable) return NS_OK;
01186 
01187   mdb_err err;
01188   mdb_count count;
01189   err = mTable->GetCount(mEnv, &count);
01190   if (err != 0) return NS_ERROR_FAILURE;
01191 
01192   BeginUpdateBatch();
01193 
01194   // Begin the batch.
01195   int marker;
01196   err = mTable->StartBatchChangeHint(mEnv, &marker);
01197   NS_ASSERTION(err == 0, "unable to start batch");
01198   if (err != 0) return NS_ERROR_FAILURE;
01199 
01200   nsCOMPtr<nsIRDFResource> resource;
01201   // XXX from here until end batch, no early returns!
01202   for (mdb_pos pos = count - 1; pos >= 0; --pos) {
01203     nsCOMPtr<nsIMdbRow> row;
01204     err = mTable->PosToRow(mEnv, pos, getter_AddRefs(row));
01205     NS_ASSERTION(err == 0, "unable to get row");
01206     if (err != 0)
01207       break;
01208 
01209     NS_ASSERTION(row != nsnull, "no row");
01210     if (! row)
01211       continue;
01212 
01213     // now we actually do the match. If this row doesn't match, loop again
01214     if (!(aMatchFunc)(row, aClosure))
01215       continue;
01216 
01217     if (notify) {
01218       // What's the URL? We need to know to properly notify our RDF
01219       // observers.
01220       mdbYarn yarn;
01221       err = row->AliasCellYarn(mEnv, kToken_URLColumn, &yarn);
01222       if (err != 0)
01223         continue;
01224       
01225       const char* startPtr = (const char*) yarn.mYarn_Buf;
01226       nsCAutoString uri(Substring(startPtr, startPtr+yarn.mYarn_Fill));
01227       rv = gRDFService->GetResource(uri, getter_AddRefs(resource));
01228       NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get resource");
01229       if (NS_FAILED(rv))
01230         continue;
01231     }
01232     // Officially cut the row *now*, before notifying any observers:
01233     // that way, any re-entrant calls won't find the row.
01234     err = mTable->CutRow(mEnv, row);
01235     NS_ASSERTION(err == 0, "couldn't cut row");
01236     if (err != 0)
01237       continue;
01238   
01239     // possibly avoid leakage
01240     err = row->CutAllColumns(mEnv);
01241     NS_ASSERTION(err == 0, "couldn't cut all columns");
01242     // we'll notify regardless of whether we could successfully
01243     // CutAllColumns or not.
01244     
01245     
01246   }
01247   
01248   // Finish the batch.
01249   err = mTable->EndBatchChangeHint(mEnv, &marker);
01250   NS_ASSERTION(err == 0, "error ending batch");
01251 
01252   EndUpdateBatch();
01253 
01254   return ( err == 0) ? NS_OK : NS_ERROR_FAILURE;
01255 }
01256 
01257 NS_IMETHODIMP
01258 nsGlobalHistory::IsVisited(nsIURI* aURI, PRBool *_retval)
01259 {
01260   NS_ENSURE_ARG_POINTER(aURI);
01261 
01262   nsresult rv;
01263   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_NOT_INITIALIZED);
01264 
01265   nsCAutoString URISpec;
01266   rv = aURI->GetSpec(URISpec);
01267   NS_ENSURE_SUCCESS(rv, rv);
01268 
01269   rv = FindRow(kToken_URLColumn, URISpec.get(), nsnull);
01270   *_retval = NS_SUCCEEDED(rv);
01271   
01272   // Hidden, typed URIs haven't really been visited yet. They've only
01273   // been typed in and the actual load hasn't happened yet. We maintain
01274   // the list of hidden+typed URIs in memory in mTypedHiddenURIs because
01275   // the list will usually be small and checking the actual Mork row
01276   // would require several dynamic memory allocations.
01277   if (*_retval && mTypedHiddenURIs.Contains(URISpec))
01278   {
01279     *_retval = PR_FALSE;
01280   }
01281   
01282   return NS_OK;
01283 }
01284 
01285 NS_IMETHODIMP
01286 nsGlobalHistory::GetLastPageVisited(nsACString& _retval)
01287 { 
01288   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
01289 
01290   NS_ENSURE_STATE(mMetaRow);
01291 
01292   mdb_err err = GetRowValue(mMetaRow, kToken_LastPageVisited, _retval);
01293   NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE);
01294 
01295   return NS_OK;
01296 }
01297 
01298 // Set the byte order in the history file.  The given string value should
01299 // be either "BE" (big-endian) or "LE" (little-endian).
01300 nsresult
01301 nsGlobalHistory::SaveByteOrder(const char *aByteOrder)
01302 {
01303   if (PL_strcmp(aByteOrder, "BE") != 0 && PL_strcmp(aByteOrder, "LE") != 0) {
01304     NS_WARNING("Invalid byte order argument.");
01305     return NS_ERROR_INVALID_ARG;
01306   }
01307   NS_ENSURE_STATE(mMetaRow);
01308 
01309   mdb_err err = SetRowValue(mMetaRow, kToken_ByteOrder, aByteOrder);
01310   NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE);
01311 
01312   return NS_OK;
01313 }
01314 
01315 // Get the file byte order.
01316 nsresult
01317 nsGlobalHistory::GetByteOrder(char **_retval)
01318 { 
01319   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
01320 
01321   NS_ENSURE_ARG_POINTER(_retval);
01322   NS_ENSURE_STATE(mMetaRow);
01323 
01324   nsCAutoString byteOrder;
01325   mdb_err err = GetRowValue(mMetaRow, kToken_ByteOrder, byteOrder);
01326   NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE);
01327 
01328   *_retval = ToNewCString(byteOrder);
01329   NS_ENSURE_TRUE(*_retval, NS_ERROR_OUT_OF_MEMORY);
01330 
01331   return NS_OK;
01332 }
01333 
01334 NS_IMETHODIMP
01335 nsGlobalHistory::HidePage(nsIURI *aURI)
01336 {
01337   nsresult rv;
01338   NS_ENSURE_ARG_POINTER(aURI);
01339 
01340   nsCAutoString URISpec;
01341   rv = aURI->GetSpec(URISpec);
01342   NS_ENSURE_SUCCESS(rv, rv);
01343   
01344   if (URISpec.Length() > HISTORY_URI_LENGTH_MAX)
01345      return NS_OK;
01346 
01347   nsCOMPtr<nsIMdbRow> row;
01348 
01349   rv = FindRow(kToken_URLColumn, URISpec.get(), getter_AddRefs(row));
01350 
01351   if (NS_FAILED(rv)) {
01352     // it hasn't been visited yet, but if one ever comes in, we need
01353     // to hide it when it is visited
01354     rv = AddURI(aURI, PR_FALSE, PR_FALSE, nsnull);
01355     if (NS_FAILED(rv)) return rv;
01356     
01357     rv = FindRow(kToken_URLColumn, URISpec.get(), getter_AddRefs(row));
01358     if (NS_FAILED(rv)) return rv;
01359   }
01360 
01361   rv = SetRowValue(row, kToken_HiddenColumn, 1);
01362   if (NS_FAILED(rv)) return rv;
01363 
01364   // now pretend as if this row was deleted
01365   // HasAssertion() correctly checks the Hidden column to show that
01366   // the row is hidden
01367   nsCOMPtr<nsIRDFResource> urlResource;
01368   rv = gRDFService->GetResource(URISpec, getter_AddRefs(urlResource));
01369   if (NS_FAILED(rv)) return rv;
01370   return NotifyFindUnassertions(urlResource, row);
01371 }
01372 
01373 NS_IMETHODIMP
01374 nsGlobalHistory::MarkPageAsTyped(nsIURI *aURI)
01375 {
01376   nsCAutoString spec;
01377   nsresult rv = aURI->GetSpec(spec);
01378   if (NS_FAILED(rv)) return rv;
01379 
01380   if (spec.Length() > HISTORY_URI_LENGTH_MAX)
01381      return NS_OK;
01382 
01383   nsCOMPtr<nsIMdbRow> row;
01384   rv = FindRow(kToken_URLColumn, spec.get(), getter_AddRefs(row));
01385   if (NS_FAILED(rv)) {
01386     rv = AddNewPageToDatabase(spec.get(), GetNow(), nsnull, getter_AddRefs(row));
01387     NS_ENSURE_SUCCESS(rv, rv);
01388 
01389     // We don't know if this is a valid URI yet. Hide it until it finishes
01390     // loading.
01391     SetRowValue(row, kToken_HiddenColumn, 1);
01392     mTypedHiddenURIs.Put(spec);
01393   }
01394   
01395   return SetRowValue(row, kToken_TypedColumn, 1);
01396 }
01397 
01398 
01399 //----------------------------------------------------------------------
01400 //
01401 // nsGlobalHistory
01402 //
01403 //   nsIRDFDataSource methods
01404 
01405 NS_IMETHODIMP
01406 nsGlobalHistory::GetURI(char* *aURI)
01407 {
01408   NS_PRECONDITION(aURI != nsnull, "null ptr");
01409   if (! aURI)
01410     return NS_ERROR_NULL_POINTER;
01411 
01412   *aURI = nsCRT::strdup("rdf:history");
01413   if (! *aURI)
01414     return NS_ERROR_OUT_OF_MEMORY;
01415 
01416   return NS_OK;
01417 }
01418 
01419 
01420 NS_IMETHODIMP
01421 nsGlobalHistory::GetSource(nsIRDFResource* aProperty,
01422                            nsIRDFNode* aTarget,
01423                            PRBool aTruthValue,
01424                            nsIRDFResource** aSource)
01425 {
01426   NS_PRECONDITION(aProperty != nsnull, "null ptr");
01427   if (! aProperty)
01428     return NS_ERROR_NULL_POINTER;
01429 
01430   NS_PRECONDITION(aTarget != nsnull, "null ptr");
01431   if (! aTarget)
01432     return NS_ERROR_NULL_POINTER;
01433 
01434   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
01435   nsresult rv;
01436 
01437   *aSource = nsnull;
01438 
01439   if (aProperty == kNC_URL) {
01440     // See if we have the row...
01441 
01442     // XXX We could be more forgiving here, and check for literal
01443     // values as well.
01444     nsCOMPtr<nsIRDFResource> target = do_QueryInterface(aTarget);
01445     if (target && IsURLInHistory(target))
01446       return CallQueryInterface(aTarget, aSource);
01447     
01448   }
01449   else if ((aProperty == kNC_Date) ||
01450            (aProperty == kNC_FirstVisitDate) ||
01451            (aProperty == kNC_VisitCount) ||
01452            (aProperty == kNC_Name) ||
01453            (aProperty == kNC_Hostname) ||
01454            (aProperty == kNC_Referrer)) {
01455     // Call GetSources() and return the first one we find.
01456     nsCOMPtr<nsISimpleEnumerator> sources;
01457     rv = GetSources(aProperty, aTarget, aTruthValue, getter_AddRefs(sources));
01458     if (NS_FAILED(rv)) return rv;
01459 
01460     PRBool hasMore;
01461     rv = sources->HasMoreElements(&hasMore);
01462     if (NS_FAILED(rv)) return rv;
01463 
01464     if (hasMore) {
01465       nsCOMPtr<nsISupports> isupports;
01466       rv = sources->GetNext(getter_AddRefs(isupports));
01467       if (NS_FAILED(rv)) return rv;
01468 
01469       return CallQueryInterface(isupports, aSource);
01470     }
01471   }
01472 
01473   return NS_RDF_NO_VALUE;  
01474 }
01475 
01476 NS_IMETHODIMP
01477 nsGlobalHistory::GetSources(nsIRDFResource* aProperty,
01478                             nsIRDFNode* aTarget,
01479                             PRBool aTruthValue,
01480                             nsISimpleEnumerator** aSources)
01481 {
01482   // XXX TODO: make sure each URL in history is connected back to
01483   // NC:HistoryRoot.
01484   NS_PRECONDITION(aProperty != nsnull, "null ptr");
01485   if (! aProperty)
01486     return NS_ERROR_NULL_POINTER;
01487 
01488   NS_PRECONDITION(aTarget != nsnull, "null ptr");
01489   if (! aTarget)
01490     return NS_ERROR_NULL_POINTER;
01491 
01492   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
01493   nsresult rv;
01494 
01495   if (aProperty == kNC_URL) {
01496     // Call GetSource() and return a singleton enumerator for the URL.
01497     nsCOMPtr<nsIRDFResource> source;
01498     rv = GetSource(aProperty, aTarget, aTruthValue, getter_AddRefs(source));
01499     if (NS_FAILED(rv)) return rv;
01500 
01501     return NS_NewSingletonEnumerator(aSources, source);
01502   }
01503   else {
01504     // See if aProperty is something we understand, and create an
01505     // URLEnumerator to select URLs with the appropriate value.
01506 
01507     mdb_column col = 0; // == "not a property that I grok"
01508     void* value = nsnull;
01509     PRInt32 len = 0;
01510 
01511     // PRInt64/date properties
01512     if (aProperty == kNC_Date ||
01513         aProperty == kNC_FirstVisitDate) {
01514       nsCOMPtr<nsIRDFDate> date = do_QueryInterface(aTarget);
01515       if (date) {
01516         PRInt64 n;
01517 
01518         rv = date->GetValue(&n);
01519         if (NS_FAILED(rv)) return rv;
01520 
01521         nsCAutoString valueStr;
01522         valueStr.AppendInt(n);
01523         
01524         value = (void *)ToNewCString(valueStr);
01525         if (aProperty == kNC_Date)
01526           col = kToken_LastVisitDateColumn;
01527         else
01528           col = kToken_FirstVisitDateColumn;
01529       }
01530     }
01531     // PRInt32 properties
01532     else if (aProperty == kNC_VisitCount) {
01533       nsCOMPtr<nsIRDFInt> countLiteral = do_QueryInterface(aTarget);
01534       if (countLiteral) {
01535         PRInt32 intValue;
01536         rv = countLiteral->GetValue(&intValue);
01537         if (NS_FAILED(rv)) return rv;
01538 
01539         nsAutoString valueStr; valueStr.AppendInt(intValue);
01540         value = ToNewUnicode(valueStr);
01541         len = valueStr.Length() * sizeof(PRUnichar);
01542         col = kToken_VisitCountColumn;
01543       }
01544       
01545     }
01546     // PRUnichar* properties
01547     else if (aProperty == kNC_Name) {
01548       nsCOMPtr<nsIRDFLiteral> name = do_QueryInterface(aTarget);
01549       if (name) {
01550         PRUnichar* p;
01551         rv = name->GetValue(&p);
01552         if (NS_FAILED(rv)) return rv;
01553 
01554         len = nsCRT::strlen(p) * sizeof(PRUnichar);
01555         value = p;
01556 
01557         col = kToken_NameColumn;
01558       }
01559     }
01560 
01561     // char* properties
01562     else if (aProperty == kNC_Hostname ||
01563              aProperty == kNC_Referrer) {
01564       col = kToken_ReferrerColumn;
01565       nsCOMPtr<nsIRDFResource> referrer = do_QueryInterface(aTarget);
01566       if (referrer) {
01567         char* p;
01568         rv = referrer->GetValue(&p);
01569         if (NS_FAILED(rv)) return rv;
01570 
01571         len = PL_strlen(p);
01572         value = p;
01573 
01574         if (aProperty == kNC_Hostname)
01575           col = kToken_HostnameColumn;
01576         else if (aProperty == kNC_Referrer)
01577           col = kToken_ReferrerColumn;
01578       }
01579     }
01580 
01581     if (col) {
01582       // The URLEnumerator takes ownership of the bytes allocated in |value|.
01583       URLEnumerator* result = new URLEnumerator(kToken_URLColumn, col,
01584                                                 kToken_HiddenColumn,
01585                                                 value, len);
01586       if (! result)
01587         return NS_ERROR_OUT_OF_MEMORY;
01588 
01589       rv = result->Init(mEnv, mTable);
01590       if (NS_FAILED(rv)) return rv;
01591 
01592       *aSources = result;
01593       NS_ADDREF(*aSources);
01594       return NS_OK;
01595     }
01596   }
01597 
01598   return NS_NewEmptyEnumerator(aSources);
01599 }
01600 
01601 NS_IMETHODIMP
01602 nsGlobalHistory::GetTarget(nsIRDFResource* aSource,
01603                            nsIRDFResource* aProperty,
01604                            PRBool aTruthValue,
01605                            nsIRDFNode** aTarget)
01606 {
01607   
01608   NS_PRECONDITION(aSource != nsnull, "null ptr");
01609   if (! aSource)
01610     return NS_ERROR_NULL_POINTER;
01611 
01612   NS_PRECONDITION(aProperty != nsnull, "null ptr");
01613   if (! aProperty)
01614     return NS_ERROR_NULL_POINTER;
01615 
01616   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
01617   nsresult rv;
01618 
01619   // Initialize return value.
01620   *aTarget = nsnull;
01621 
01622   // Only "positive" assertions here.
01623   if (! aTruthValue)
01624     return NS_RDF_NO_VALUE;
01625 
01626     // XXX eventually use IsFindResource to simply return the first
01627     // matching row?
01628   if (aProperty == kNC_child &&
01629       (aSource == kNC_HistoryRoot ||
01630        aSource == kNC_HistoryByDate ||
01631        IsFindResource(aSource))) {
01632       
01633     // If they're asking for all the children of the HistoryRoot, call
01634     // through to GetTargets() and return the first one.
01635     nsCOMPtr<nsISimpleEnumerator> targets;
01636     rv = GetTargets(aSource, aProperty, aTruthValue, getter_AddRefs(targets));
01637     if (NS_FAILED(rv)) return rv;
01638     
01639     PRBool hasMore;
01640     rv = targets->HasMoreElements(&hasMore);
01641     if (NS_FAILED(rv)) return rv;
01642     
01643     if (! hasMore) return NS_RDF_NO_VALUE;
01644     
01645     nsCOMPtr<nsISupports> isupports;
01646     rv = targets->GetNext(getter_AddRefs(isupports));
01647     if (NS_FAILED(rv)) return rv;
01648     
01649     return CallQueryInterface(isupports, aTarget);
01650   }
01651   else if ((aProperty == kNC_Date) ||
01652            (aProperty == kNC_FirstVisitDate) ||
01653            (aProperty == kNC_VisitCount) ||
01654            (aProperty == kNC_AgeInDays) ||
01655            (aProperty == kNC_Name) ||
01656            (aProperty == kNC_NameSort) ||
01657            (aProperty == kNC_Hostname) ||
01658            (aProperty == kNC_Referrer) ||
01659            (aProperty == kNC_URL)) {
01660 
01661     const char* uri;
01662     rv = aSource->GetValueConst(&uri);
01663     if (NS_FAILED(rv)) return rv;
01664 
01665     // url is self-referential, so we'll always just return itself
01666     // however, don't return the URLs of find resources
01667     if (aProperty == kNC_URL && !IsFindResource(aSource)) {
01668       
01669       nsCOMPtr<nsIRDFLiteral> uriLiteral;
01670       rv = gRDFService->GetLiteral(NS_ConvertUTF8toUCS2(uri).get(), getter_AddRefs(uriLiteral));
01671       if (NS_FAILED(rv))    return(rv);
01672       *aTarget = uriLiteral;
01673       NS_ADDREF(*aTarget);
01674       return NS_OK;
01675     }
01676 
01677     // find URIs are special
01678     if (((aProperty == kNC_Name) || (aProperty == kNC_NameSort)) &&
01679         IsFindResource(aSource)) {
01680       
01681       // for sorting, we sort by uri, so just return the URI as a literal
01682       if (aProperty == kNC_NameSort) {
01683         nsCOMPtr<nsIRDFLiteral> uriLiteral;
01684         rv = gRDFService->GetLiteral(NS_ConvertUTF8toUCS2(uri).get(),
01685                                      getter_AddRefs(uriLiteral));
01686         if (NS_FAILED(rv))    return(rv);
01687         
01688         *aTarget = uriLiteral;
01689         NS_ADDREF(*aTarget);
01690         return NS_OK;
01691       }
01692       else 
01693         return GetFindUriName(uri, aTarget);
01694     }
01695     // ok, we got this far, so we have to retrieve something from
01696     // the row in the database
01697     nsCOMPtr<nsIMdbRow> row;
01698     rv = FindRow(kToken_URLColumn, uri, getter_AddRefs(row));
01699     if (NS_FAILED(rv)) return NS_RDF_NO_VALUE;
01700 
01701     mdb_err err;
01702     // ...and then depending on the property they want, we'll pull the
01703     // cell they want out of it.
01704     if (aProperty == kNC_Date  ||
01705         aProperty == kNC_FirstVisitDate) {
01706       // Last visit date
01707       PRTime i;
01708       if (aProperty == kNC_Date)
01709         rv = GetRowValue(row, kToken_LastVisitDateColumn, &i);
01710       else
01711         rv = GetRowValue(row, kToken_FirstVisitDateColumn, &i);
01712 
01713       if (NS_FAILED(rv)) return rv;
01714 
01715       nsCOMPtr<nsIRDFDate> date;
01716       rv = gRDFService->GetDateLiteral(i, getter_AddRefs(date));
01717       if (NS_FAILED(rv)) return rv;
01718 
01719       return CallQueryInterface(date, aTarget);
01720     }
01721     else if (aProperty == kNC_VisitCount) {
01722       mdbYarn yarn;
01723       err = row->AliasCellYarn(mEnv, kToken_VisitCountColumn, &yarn);
01724       if (err != 0) return NS_ERROR_FAILURE;
01725 
01726       PRInt32 visitCount = 0;
01727       rv = GetRowValue(row, kToken_VisitCountColumn, &visitCount);
01728       if (NS_FAILED(rv) || visitCount <1)
01729         visitCount = 1;         // assume we've visited at least once
01730 
01731       nsCOMPtr<nsIRDFInt> visitCountLiteral;
01732       rv = gRDFService->GetIntLiteral(visitCount,
01733                                       getter_AddRefs(visitCountLiteral));
01734       if (NS_FAILED(rv)) return rv;
01735 
01736       return CallQueryInterface(visitCountLiteral, aTarget);
01737     }
01738     else if (aProperty == kNC_AgeInDays) {
01739       PRTime lastVisitDate;
01740       rv = GetRowValue(row, kToken_LastVisitDateColumn, &lastVisitDate);
01741       if (NS_FAILED(rv)) return rv;
01742       
01743       PRInt32 days = GetAgeInDays(lastVisitDate);
01744 
01745       nsCOMPtr<nsIRDFInt> ageLiteral;
01746       rv = gRDFService->GetIntLiteral(days, getter_AddRefs(ageLiteral));
01747       if (NS_FAILED(rv)) return rv;
01748 
01749       *aTarget = ageLiteral;
01750       NS_ADDREF(*aTarget);
01751       return NS_OK;
01752     }
01753     else if (aProperty == kNC_Name ||
01754              aProperty == kNC_NameSort) {
01755       // Site name (i.e., page title)
01756       nsAutoString title;
01757       rv = GetRowValue(row, kToken_NameColumn, title);
01758       if (NS_FAILED(rv) || title.IsEmpty()) {
01759         // yank out the filename from the url, use that
01760         nsCOMPtr<nsIURI> aUri;
01761         rv = NS_NewURI(getter_AddRefs(aUri), uri);
01762         if (NS_FAILED(rv)) return rv;
01763         nsCOMPtr<nsIURL> urlObj(do_QueryInterface(aUri));
01764         if (!urlObj)
01765             return NS_ERROR_FAILURE;
01766 
01767         nsCAutoString filename;
01768         rv = urlObj->GetFileName(filename);
01769         if (NS_FAILED(rv) || filename.IsEmpty()) {
01770           // ok fine. we'll use the file path. then we give up!
01771           rv = urlObj->GetPath(filename);
01772           if (strcmp(filename.get(), "/") == 0) {
01773             // if the top of a site does not have a title
01774             // (common for redirections) then return the hostname
01775             rv = GetRowValue(row, kToken_HostnameColumn, filename);
01776           }
01777         }
01778 
01779         if (NS_FAILED(rv)) return rv;
01780         
01781         // assume the url is in UTF8
01782         AppendUTF8toUTF16(filename, title);
01783       }
01784       if (NS_FAILED(rv)) return rv;
01785 
01786       nsCOMPtr<nsIRDFLiteral> name;
01787       rv = gRDFService->GetLiteral(title.get(), getter_AddRefs(name));
01788       if (NS_FAILED(rv)) return rv;
01789 
01790       return CallQueryInterface(name, aTarget);
01791     }
01792     else if (aProperty == kNC_Hostname ||
01793              aProperty == kNC_Referrer) {
01794       
01795       nsCAutoString str;
01796       if (aProperty == kNC_Hostname)
01797         rv = GetRowValue(row, kToken_HostnameColumn, str);
01798       else if (aProperty == kNC_Referrer)
01799         rv = GetRowValue(row, kToken_ReferrerColumn, str);
01800       
01801       if (NS_FAILED(rv)) return rv;
01802       // Avoid trying to create a resource from an empty string, which
01803       // will raise an exception
01804       if (str.IsEmpty()) return NS_RDF_NO_VALUE;
01805       
01806       nsCOMPtr<nsIRDFResource> resource;
01807       rv = gRDFService->GetResource(str,
01808                                     getter_AddRefs(resource));
01809       if (NS_FAILED(rv)) return rv;
01810 
01811       return CallQueryInterface(resource, aTarget);
01812     }
01813 
01814     else {
01815       NS_NOTREACHED("huh, how'd I get here?");
01816     }
01817   }
01818   return NS_RDF_NO_VALUE;
01819 }
01820 
01821 void
01822 nsGlobalHistory::Sync()
01823 {
01824   if (mDirty)
01825     Flush();
01826   
01827   mDirty = PR_FALSE;
01828   mSyncTimer = nsnull;
01829 }
01830 
01831 void
01832 nsGlobalHistory::ExpireNow()
01833 {
01834   mNowValid = PR_FALSE;
01835   mExpireNowTimer = nsnull;
01836 }
01837 
01838 // when we're dirty, we want to make sure we sync again soon,
01839 // but make sure that we don't keep syncing if someone is surfing
01840 // a lot, so cancel the existing timer if any is still waiting to fire
01841 nsresult
01842 nsGlobalHistory::SetDirty()
01843 {
01844   nsresult rv;
01845 
01846   if (mSyncTimer)
01847     mSyncTimer->Cancel();
01848 
01849   if (!mSyncTimer) {
01850     mSyncTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
01851     if (NS_FAILED(rv)) return rv;
01852   }
01853   
01854   mDirty = PR_TRUE;
01855   mSyncTimer->InitWithFuncCallback(fireSyncTimer, this, HISTORY_SYNC_TIMEOUT,
01856                                    nsITimer::TYPE_ONE_SHOT);
01857   
01858 
01859   return NS_OK;
01860 }
01861 
01862 // hack to avoid calling PR_Now() too often, as is the case when
01863 // we're asked the ageindays of many history entries in a row
01864 PRTime
01865 nsGlobalHistory::GetNow()
01866 {
01867   if (!mNowValid) {             // not dirty, mLastNow is crufty
01868     mLastNow = PR_Now();
01869 
01870     // we also cache our offset from GMT, to optimize NormalizeTime()
01871     // note that this cache is only valid if GetNow() is called before
01872     // NormalizeTime(), but that is always the case here.
01873     PRExplodedTime explodedNow;
01874     PR_ExplodeTime(mLastNow, PR_LocalTimeParameters, &explodedNow);
01875     mCachedGMTOffset = nsInt64(explodedNow.tm_params.tp_gmt_offset) * nsInt64((PRUint32)PR_USEC_PER_SEC) +
01876                        nsInt64(explodedNow.tm_params.tp_dst_offset) * nsInt64((PRUint32)PR_USEC_PER_SEC);
01877 
01878     mNowValid = PR_TRUE;
01879     if (!mExpireNowTimer)
01880       mExpireNowTimer = do_CreateInstance("@mozilla.org/timer;1");
01881 
01882     if (mExpireNowTimer)
01883       mExpireNowTimer->InitWithFuncCallback(expireNowTimer, this, HISTORY_EXPIRE_NOW_TIMEOUT,
01884                                             nsITimer::TYPE_ONE_SHOT);
01885   }
01886   
01887   return mLastNow;
01888 }
01889 
01890 NS_IMETHODIMP
01891 nsGlobalHistory::GetTargets(nsIRDFResource* aSource,
01892                             nsIRDFResource* aProperty,
01893                             PRBool aTruthValue,
01894                             nsISimpleEnumerator** aTargets)
01895 {
01896   NS_PRECONDITION(aSource != nsnull, "null ptr");
01897   if (! aSource)
01898     return NS_ERROR_NULL_POINTER;
01899 
01900   NS_PRECONDITION(aProperty != nsnull, "null ptr");
01901   if (! aProperty)
01902     return NS_ERROR_NULL_POINTER;
01903 
01904   if (!aTruthValue)
01905     return NS_NewEmptyEnumerator(aTargets);
01906 
01907   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
01908   
01909   // list all URLs off the root
01910   if ((aSource == kNC_HistoryRoot) &&
01911       (aProperty == kNC_child)) {
01912     URLEnumerator* result = new URLEnumerator(kToken_URLColumn,
01913                                               kToken_HiddenColumn);
01914     if (! result)
01915       return NS_ERROR_OUT_OF_MEMORY;
01916     
01917     nsresult rv;
01918     rv = result->Init(mEnv, mTable);
01919     if (NS_FAILED(rv)) return rv;
01920     
01921     *aTargets = result;
01922     NS_ADDREF(*aTargets);
01923     return NS_OK;
01924   }
01925   else if ((aSource == kNC_HistoryByDate) &&
01926            (aProperty == kNC_child)) {
01927 
01928     return GetRootDayQueries(aTargets);
01929   }
01930   else if (aProperty == kNC_child &&
01931            IsFindResource(aSource)) {
01932     return CreateFindEnumerator(aSource, aTargets);
01933   }
01934   
01935   else if ((aProperty == kNC_Date) ||
01936            (aProperty == kNC_FirstVisitDate) ||
01937            (aProperty == kNC_VisitCount) ||
01938            (aProperty == kNC_AgeInDays) ||
01939            (aProperty == kNC_Name) ||
01940            (aProperty == kNC_Hostname) ||
01941            (aProperty == kNC_Referrer)) {
01942     nsresult rv;
01943     
01944     nsCOMPtr<nsIRDFNode> target;
01945     rv = GetTarget(aSource, aProperty, aTruthValue, getter_AddRefs(target));
01946     if (NS_FAILED(rv)) return rv;
01947     
01948     if (rv == NS_OK) {
01949       return NS_NewSingletonEnumerator(aTargets, target);
01950     }
01951   }
01952 
01953   // we've already answered the queries from the root, so we must be
01954   // looking for real entries
01955 
01956   return NS_NewEmptyEnumerator(aTargets);
01957 }
01958 
01959 NS_IMETHODIMP
01960 nsGlobalHistory::Assert(nsIRDFResource* aSource, 
01961                         nsIRDFResource* aProperty, 
01962                         nsIRDFNode* aTarget,
01963                         PRBool aTruthValue)
01964 {
01965   // History cannot be modified
01966   return NS_RDF_ASSERTION_REJECTED;
01967 }
01968 
01969 NS_IMETHODIMP
01970 nsGlobalHistory::Unassert(nsIRDFResource* aSource,
01971                           nsIRDFResource* aProperty,
01972                           nsIRDFNode* aTarget)
01973 {
01974   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
01975   // translate into an appropriate removehistory call
01976   nsresult rv;
01977   if ((aSource == kNC_HistoryRoot || aSource == kNC_HistoryByDate || IsFindResource(aSource)) &&
01978       aProperty == kNC_child) {
01979 
01980     nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(aTarget, &rv);
01981     if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED; 
01982 
01983     const char* targetUrl;
01984     rv = resource->GetValueConst(&targetUrl);
01985     if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED;
01986 
01987     if (IsFindResource(resource)) {
01988       // convert uri to a query
01989       searchQuery query;
01990       rv = FindUrlToSearchQuery(targetUrl, query);
01991       if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED;
01992  
01993       matchQuery_t matchQuery;
01994       matchQuery.history = this;
01995       matchQuery.query = &query;
01996       rv = RemoveMatchingRows(matchQueryCallback, (void*)&matchQuery, PR_TRUE); 
01997       FreeSearchQuery(query);
01998       if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED;
01999 
02000       // if there are batches in progress, we don't want to notify
02001       // observers that we're deleting items. the caller promises
02002       // to handle whatever UI updating is necessary when we're finished.
02003       if (!mBatchesInProgress)
02004         NotifyUnassert(aSource, aProperty, aTarget);
02005 
02006       return NS_OK;
02007     }
02008 
02009     // ignore any error
02010     rv = RemovePageInternal(targetUrl);
02011     if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED;
02012 
02013     return NS_OK;
02014   }
02015   
02016   return NS_RDF_ASSERTION_REJECTED;
02017 }
02018 
02019 NS_IMETHODIMP
02020 nsGlobalHistory::Change(nsIRDFResource* aSource,
02021                         nsIRDFResource* aProperty,
02022                         nsIRDFNode* aOldTarget,
02023                         nsIRDFNode* aNewTarget)
02024 {
02025   return NS_RDF_ASSERTION_REJECTED;
02026 }
02027 
02028 NS_IMETHODIMP
02029 nsGlobalHistory::Move(nsIRDFResource* aOldSource,
02030                       nsIRDFResource* aNewSource,
02031                       nsIRDFResource* aProperty,
02032                       nsIRDFNode* aTarget)
02033 {
02034   return NS_RDF_ASSERTION_REJECTED;
02035 }
02036 
02037 NS_IMETHODIMP
02038 nsGlobalHistory::HasAssertion(nsIRDFResource* aSource,
02039                               nsIRDFResource* aProperty,
02040                               nsIRDFNode* aTarget,
02041                               PRBool aTruthValue,
02042                               PRBool* aHasAssertion)
02043 {
02044 
02045   NS_PRECONDITION(aSource != nsnull, "null ptr");
02046   if (! aSource)
02047     return NS_ERROR_NULL_POINTER;
02048 
02049   NS_PRECONDITION(aProperty != nsnull, "null ptr");
02050   if (! aProperty)
02051     return NS_ERROR_NULL_POINTER;
02052 
02053   NS_PRECONDITION(aTarget != nsnull, "null ptr");
02054   if (! aTarget)
02055     return NS_ERROR_NULL_POINTER;
02056 
02057   // Only "positive" assertions here.
02058   if (!aTruthValue) {
02059     *aHasAssertion = PR_FALSE;
02060     return NS_OK;
02061   }
02062 
02063   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
02064   nsresult rv;
02065   
02066   // answer if a specific row matches a find URI
02067   // 
02068   // at some point, we should probably match groupby= findURIs with
02069   // findURIs that match all their criteria
02070   //
02071   nsCOMPtr<nsIRDFResource> target = do_QueryInterface(aTarget);
02072   if (target &&
02073       aProperty == kNC_child &&
02074       IsFindResource(aSource) &&
02075       !IsFindResource(target)) {
02076 
02077     const char *uri;
02078     rv = target->GetValueConst(&uri);
02079     if (NS_FAILED(rv)) return rv;
02080 
02081     searchQuery query;
02082     FindUrlToSearchQuery(uri, query);
02083     
02084     nsCOMPtr<nsIMdbRow> row;
02085     rv = FindRow(kToken_URLColumn, uri, getter_AddRefs(row));
02086     // not even in history. don't bother trying
02087     if (NS_FAILED(rv) || HasCell(mEnv, row, kToken_HiddenColumn)) {
02088       *aHasAssertion = PR_FALSE;
02089       return NS_OK;
02090     }
02091     
02092     *aHasAssertion = RowMatches(row, &query);
02093     FreeSearchQuery(query);
02094     return NS_OK;
02095   }
02096   
02097   // Do |GetTargets()| and grovel through the results to see if we
02098   // have the assertion.
02099   //
02100   // XXX *AHEM*, this could be implemented much more efficiently...
02101 
02102   nsCOMPtr<nsISimpleEnumerator> targets;
02103   rv = GetTargets(aSource, aProperty, aTruthValue, getter_AddRefs(targets));
02104   if (NS_FAILED(rv)) return rv;
02105   
02106   while (1) {
02107     PRBool hasMore;
02108     rv = targets->HasMoreElements(&hasMore);
02109     if (NS_FAILED(rv)) return rv;
02110     
02111     if (! hasMore)
02112       break;
02113     
02114     nsCOMPtr<nsISupports> isupports;
02115     rv = targets->GetNext(getter_AddRefs(isupports));
02116     if (NS_FAILED(rv)) return rv;
02117     
02118     nsCOMPtr<nsIRDFNode> node = do_QueryInterface(isupports);
02119     if (node.get() == aTarget) {
02120       *aHasAssertion = PR_TRUE;
02121       return NS_OK;
02122     }
02123   }
02124 
02125   *aHasAssertion = PR_FALSE;
02126   return NS_OK;
02127 }
02128 
02129 NS_IMETHODIMP
02130 nsGlobalHistory::AddObserver(nsIRDFObserver* aObserver)
02131 {
02132   NS_PRECONDITION(aObserver != nsnull, "null ptr");
02133   if (! aObserver)
02134     return NS_ERROR_NULL_POINTER;
02135 
02136   if (! mObservers) {
02137     nsresult rv;
02138     rv = NS_NewISupportsArray(getter_AddRefs(mObservers));
02139     if (NS_FAILED(rv)) return rv;
02140   }
02141   mObservers->AppendElement(aObserver);
02142   return NS_OK;
02143 }
02144 
02145 NS_IMETHODIMP
02146 nsGlobalHistory::RemoveObserver(nsIRDFObserver* aObserver)
02147 {
02148   NS_PRECONDITION(aObserver != nsnull, "null ptr");
02149   if (! aObserver)
02150     return NS_ERROR_NULL_POINTER;
02151 
02152   if (! mObservers)
02153     return NS_OK;
02154 
02155   mObservers->RemoveElement(aObserver);
02156 
02157   return NS_OK;
02158 }
02159 
02160 NS_IMETHODIMP 
02161 nsGlobalHistory::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, PRBool *result)
02162 {
02163   NS_PRECONDITION(aNode != nsnull, "null ptr");
02164   if (! aNode)
02165     return NS_ERROR_NULL_POINTER;
02166 
02167   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
02168   nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(aNode);
02169   if (resource && IsURLInHistory(resource)) {
02170     *result = (aArc == kNC_child);
02171   }
02172   else {
02173     *result = PR_FALSE;
02174   }
02175   return NS_OK;
02176 }
02177 
02178 NS_IMETHODIMP 
02179 nsGlobalHistory::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, PRBool *result)
02180 {
02181   NS_PRECONDITION(aSource != nsnull, "null ptr");
02182   if (! aSource)
02183     return NS_ERROR_NULL_POINTER;
02184 
02185   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
02186   if ((aSource == kNC_HistoryRoot) ||
02187       (aSource == kNC_HistoryByDate)) {
02188     *result = (aArc == kNC_child);
02189   }
02190   else if (IsFindResource(aSource)) {
02191     // we handle children of find urls
02192     *result = (aArc == kNC_child ||
02193                aArc == kNC_Name ||
02194                aArc == kNC_NameSort);
02195   }
02196   else if (IsURLInHistory(aSource)) {
02197     // If the URL is in the history, then it'll have all the
02198     // appropriate attributes.
02199     *result = (aArc == kNC_Date ||
02200                aArc == kNC_FirstVisitDate ||
02201                aArc == kNC_VisitCount ||
02202                aArc == kNC_Name ||
02203                aArc == kNC_Hostname ||
02204                aArc == kNC_Referrer);
02205   }
02206   else {
02207     *result = PR_FALSE;
02208   }
02209   return NS_OK; 
02210 }
02211 
02212 NS_IMETHODIMP
02213 nsGlobalHistory::ArcLabelsIn(nsIRDFNode* aNode,
02214                              nsISimpleEnumerator** aLabels)
02215 {
02216   NS_PRECONDITION(aNode != nsnull, "null ptr");
02217   if (! aNode)
02218     return NS_ERROR_NULL_POINTER;
02219 
02220   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
02221   nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(aNode);
02222   if (resource && IsURLInHistory(resource)) {
02223     return NS_NewSingletonEnumerator(aLabels, kNC_child);
02224   }
02225   else {
02226     return NS_NewEmptyEnumerator(aLabels);
02227   }
02228 }
02229 
02230 NS_IMETHODIMP
02231 nsGlobalHistory::ArcLabelsOut(nsIRDFResource* aSource,
02232                               nsISimpleEnumerator** aLabels)
02233 {
02234   NS_PRECONDITION(aSource != nsnull, "null ptr");
02235   if (! aSource)
02236     return NS_ERROR_NULL_POINTER;
02237 
02238   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
02239   nsresult rv;
02240 
02241   if ((aSource == kNC_HistoryRoot) ||
02242       (aSource == kNC_HistoryByDate)) {
02243     return NS_NewSingletonEnumerator(aLabels, kNC_child);
02244   }
02245   else if (IsURLInHistory(aSource)) {
02246     // If the URL is in the history, then it'll have all the
02247     // appropriate attributes.
02248     nsCOMPtr<nsISupportsArray> array;
02249     rv = NS_NewISupportsArray(getter_AddRefs(array));
02250     if (NS_FAILED(rv)) return rv;
02251 
02252     array->AppendElement(kNC_Date);
02253     array->AppendElement(kNC_FirstVisitDate);
02254     array->AppendElement(kNC_VisitCount);
02255     array->AppendElement(kNC_Name);
02256     array->AppendElement(kNC_Hostname);
02257     array->AppendElement(kNC_Referrer);
02258 
02259     return NS_NewArrayEnumerator(aLabels, array);
02260   }
02261   else if (IsFindResource(aSource)) {
02262     nsCOMPtr<nsISupportsArray> array;
02263     rv = NS_NewISupportsArray(getter_AddRefs(array));
02264     if (NS_FAILED(rv)) return rv;
02265 
02266     array->AppendElement(kNC_child);
02267     array->AppendElement(kNC_Name);
02268     array->AppendElement(kNC_NameSort);
02269     
02270     return NS_NewArrayEnumerator(aLabels, array);
02271   }
02272   else {
02273     return NS_NewEmptyEnumerator(aLabels);
02274   }
02275 }
02276 
02277 NS_IMETHODIMP
02278 nsGlobalHistory::GetAllCmds(nsIRDFResource* aSource,
02279                             nsISimpleEnumerator/*<nsIRDFResource>*/** aCommands)
02280 {
02281   return NS_NewEmptyEnumerator(aCommands);
02282 }
02283 
02284 NS_IMETHODIMP
02285 nsGlobalHistory::IsCommandEnabled(nsISupportsArray/*<nsIRDFResource>*/* aSources,
02286                                   nsIRDFResource*   aCommand,
02287                                   nsISupportsArray/*<nsIRDFResource>*/* aArguments,
02288                                   PRBool* aResult)
02289 {
02290   NS_NOTYETIMPLEMENTED("sorry");
02291   return NS_ERROR_NOT_IMPLEMENTED;
02292 }
02293 
02294 NS_IMETHODIMP
02295 nsGlobalHistory::DoCommand(nsISupportsArray/*<nsIRDFResource>*/* aSources,
02296                            nsIRDFResource*   aCommand,
02297                            nsISupportsArray/*<nsIRDFResource>*/* aArguments)
02298 {
02299   NS_NOTYETIMPLEMENTED("sorry");
02300   return NS_ERROR_NOT_IMPLEMENTED;
02301 }
02302 
02303 NS_IMETHODIMP
02304 nsGlobalHistory::GetAllResources(nsISimpleEnumerator** aResult)
02305 {
02306   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
02307   URLEnumerator* result = new URLEnumerator(kToken_URLColumn,
02308                                             kToken_HiddenColumn);
02309   if (! result)
02310     return NS_ERROR_OUT_OF_MEMORY;
02311 
02312   nsresult rv;
02313   rv = result->Init(mEnv, mTable);
02314   if (NS_FAILED(rv)) return rv;
02315 
02316   *aResult = result;
02317   NS_ADDREF(*aResult);
02318   return NS_OK;
02319 }
02320 
02321 NS_IMETHODIMP
02322 nsGlobalHistory::BeginUpdateBatch()
02323 {
02324   nsresult rv = NS_OK;
02325 
02326   ++mBatchesInProgress;
02327   
02328   // we could call mObservers->EnumerateForwards() here
02329   // to save the addref/release on each observer, but
02330   // it's unlikely that anyone but the tree builder
02331   // is observing us
02332   if (mObservers) {
02333     PRUint32 count;
02334     rv = mObservers->Count(&count);
02335     if (NS_FAILED(rv)) return rv;
02336 
02337     for (PRInt32 i = 0; i < PRInt32(count); ++i) {
02338       nsIRDFObserver* observer = NS_STATIC_CAST(nsIRDFObserver*, mObservers->ElementAt(i));
02339 
02340       NS_ASSERTION(observer != nsnull, "null ptr");
02341       if (! observer)
02342         continue;
02343 
02344       rv = observer->OnBeginUpdateBatch(this);
02345       NS_RELEASE(observer);
02346     }
02347   }
02348   return rv;
02349 }
02350 
02351 NS_IMETHODIMP
02352 nsGlobalHistory::EndUpdateBatch()
02353 {
02354   nsresult rv = NS_OK;
02355 
02356   --mBatchesInProgress;
02357 
02358   // we could call mObservers->EnumerateForwards() here
02359   // to save the addref/release on each observer, but
02360   // it's unlikely that anyone but the tree builder
02361   // is observing us
02362   if (mObservers) {
02363     PRUint32 count;
02364     rv = mObservers->Count(&count);
02365     if (NS_FAILED(rv)) return rv;
02366 
02367     for (PRInt32 i = 0; i < PRInt32(count); ++i) {
02368       nsIRDFObserver* observer = NS_STATIC_CAST(nsIRDFObserver*, mObservers->ElementAt(i));
02369 
02370       NS_ASSERTION(observer != nsnull, "null ptr");
02371       if (! observer)
02372         continue;
02373 
02374       rv = observer->OnEndUpdateBatch(this);
02375       NS_RELEASE(observer);
02376     }
02377   }
02378   return rv;
02379 }
02380 
02381 
02383 // nsIRDFRemoteDataSource
02384 
02385 NS_IMETHODIMP
02386 nsGlobalHistory::GetLoaded(PRBool* _result)
02387 {
02388     *_result = PR_TRUE;
02389     return NS_OK;
02390 }
02391 
02392 
02393 
02394 NS_IMETHODIMP
02395 nsGlobalHistory::Init(const char* aURI)
02396 {
02397        return(NS_OK);
02398 }
02399 
02400 
02401 
02402 NS_IMETHODIMP
02403 nsGlobalHistory::Refresh(PRBool aBlocking)
02404 {
02405        return(NS_OK);
02406 }
02407 
02408 NS_IMETHODIMP
02409 nsGlobalHistory::Flush()
02410 {
02411   return Commit(kLargeCommit);
02412 }
02413 
02414 NS_IMETHODIMP
02415 nsGlobalHistory::FlushTo(const char *aURI)
02416 {
02417   // Do not ever implement this (security)
02418   return(NS_ERROR_NOT_IMPLEMENTED);
02419 }
02420 
02421 //----------------------------------------------------------------------
02422 //
02423 // nsGlobalHistory
02424 //
02425 //   Miscellaneous implementation methods
02426 //
02427 
02428 nsresult
02429 nsGlobalHistory::Init()
02430 {
02431   nsresult rv;
02432 
02433   // we'd like to get this pref when we need it, but at that point,
02434   // we can't get the pref service. register a pref observer so we update
02435   // if the pref changes
02436 
02437   if (!gPrefBranch) {
02438     nsCOMPtr<nsIPrefService> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
02439     NS_ENSURE_SUCCESS(rv, rv);
02440     rv = prefs->GetBranch(PREF_BRANCH_BASE, &gPrefBranch);
02441     NS_ENSURE_SUCCESS(rv, rv);
02442   }
02443 
02444   gPrefBranch->GetIntPref(PREF_BROWSER_HISTORY_EXPIRE_DAYS, &mExpireDays);
02445   gPrefBranch->GetBoolPref(PREF_AUTOCOMPLETE_ONLY_TYPED, &mAutocompleteOnlyTyped);
02446   nsCOMPtr<nsIPrefBranch2> pbi = do_QueryInterface(gPrefBranch);
02447   if (pbi) {
02448     pbi->AddObserver(PREF_AUTOCOMPLETE_ONLY_TYPED, this, PR_FALSE);
02449     pbi->AddObserver(PREF_BROWSER_HISTORY_EXPIRE_DAYS, this, PR_FALSE);
02450   }
02451 
02452   if (gRefCnt++ == 0) {
02453     rv = CallGetService(kRDFServiceCID, &gRDFService);
02454 
02455     NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service");
02456     if (NS_FAILED(rv)) return rv;
02457 
02458     gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Page"),
02459                              &kNC_Page);
02460     gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Date"),
02461                              &kNC_Date);
02462     gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "FirstVisitDate"),
02463                              &kNC_FirstVisitDate);
02464     gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "VisitCount"),
02465                              &kNC_VisitCount);
02466     gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "AgeInDays"),
02467                              &kNC_AgeInDays);
02468     gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name"),
02469                              &kNC_Name);
02470     gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name?sort=true"),
02471                              &kNC_NameSort);
02472     gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Hostname"),
02473                              &kNC_Hostname);
02474     gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Referrer"),
02475                              &kNC_Referrer);
02476     gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"),
02477                              &kNC_child);
02478     gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "URL"),
02479                              &kNC_URL);
02480     gRDFService->GetResource(NS_LITERAL_CSTRING("NC:HistoryRoot"),
02481                              &kNC_HistoryRoot);
02482     gRDFService->GetResource(NS_LITERAL_CSTRING("NC:HistoryByDate"),
02483                              &kNC_HistoryByDate);
02484   }
02485 
02486   // register this as a named data source with the RDF service
02487   rv = gRDFService->RegisterDataSource(this, PR_FALSE);
02488   NS_ENSURE_SUCCESS(rv, rv);
02489 
02490   nsCOMPtr<nsIStringBundleService> bundleService =
02491     do_GetService(kStringBundleServiceCID, &rv);
02492   
02493   if (NS_SUCCEEDED(rv)) {
02494     rv = bundleService->CreateBundle("chrome://communicator/locale/history/history.properties", getter_AddRefs(mBundle));
02495   }
02496 
02497   // register to observe profile changes
02498   nsCOMPtr<nsIObserverService> observerService = 
02499            do_GetService("@mozilla.org/observer-service;1", &rv);
02500   NS_ASSERTION(observerService, "failed to get observer service");
02501   if (observerService) {
02502     observerService->AddObserver(this, "profile-before-change", PR_TRUE);
02503     observerService->AddObserver(this, "profile-do-change", PR_TRUE);
02504   }
02505   
02506   return NS_OK;
02507 }
02508 
02509 
02510 nsresult
02511 nsGlobalHistory::OpenDB()
02512 {
02513   nsresult rv;
02514 
02515   if (mStore) return NS_OK;
02516   
02517   nsCOMPtr <nsIFile> historyFile;
02518   rv = NS_GetSpecialDirectory(NS_APP_HISTORY_50_FILE, getter_AddRefs(historyFile));
02519   NS_ENSURE_SUCCESS(rv, rv);
02520 
02521   static NS_DEFINE_CID(kMorkCID, NS_MORK_CID);
02522   nsCOMPtr<nsIMdbFactoryFactory> factoryfactory =
02523       do_CreateInstance(kMorkCID, &rv);
02524   NS_ENSURE_SUCCESS(rv, rv);
02525 
02526   rv = factoryfactory->GetMdbFactory(&gMdbFactory);
02527   NS_ENSURE_SUCCESS(rv, rv);
02528 
02529   mdb_err err;
02530 
02531   err = gMdbFactory->MakeEnv(nsnull, &mEnv);
02532   mEnv->SetAutoClear(PR_TRUE);
02533   NS_ASSERTION((err == 0), "unable to create mdb env");
02534   if (err != 0) return NS_ERROR_FAILURE;
02535 
02536   // MDB requires native file paths
02537 
02538   nsCAutoString filePath;
02539   rv = historyFile->GetNativePath(filePath);
02540   NS_ENSURE_SUCCESS(rv, rv);
02541 
02542   PRBool exists = PR_TRUE;
02543 
02544   historyFile->Exists(&exists);
02545     
02546   if (!exists || NS_FAILED(rv = OpenExistingFile(gMdbFactory, filePath.get()))) {
02547 
02548     // we couldn't open the file, so it's either corrupt or doesn't exist.
02549     // attempt to delete the file, but ignore the error
02550     historyFile->Remove(PR_FALSE);
02551     rv = OpenNewFile(gMdbFactory, filePath.get());
02552   }
02553 
02554   NS_ENSURE_SUCCESS(rv, rv);
02555 
02556   // get the initial filesize. Used in Commit() to determine type of commit.
02557   rv = historyFile->GetFileSize(&mFileSizeOnDisk);
02558   if (NS_FAILED(rv)) {
02559     LL_I2L(mFileSizeOnDisk, 0);
02560   }
02561   
02562   // See if we need to byte-swap.
02563   InitByteOrder(PR_FALSE);
02564 
02565   return NS_OK;
02566 }
02567 
02568 nsresult
02569 nsGlobalHistory::OpenExistingFile(nsIMdbFactory *factory, const char *filePath)
02570 {
02571 
02572   mdb_err err;
02573   nsresult rv;
02574   mdb_bool canopen = 0;
02575   mdbYarn outfmt = { nsnull, 0, 0, 0, 0, nsnull };
02576 
02577   nsIMdbHeap* dbHeap = 0;
02578   mdb_bool dbFrozen = mdbBool_kFalse; // not readonly, we want modifiable
02579   nsCOMPtr<nsIMdbFile> oldFile; // ensures file is released
02580   err = factory->OpenOldFile(mEnv, dbHeap, filePath,
02581                              dbFrozen, getter_AddRefs(oldFile));
02582 
02583   // don't assert, the file might just not be there
02584   if ((err !=0) || !oldFile) return NS_ERROR_FAILURE;
02585 
02586   err = factory->CanOpenFilePort(mEnv, oldFile, // the file to investigate
02587                                  &canopen, &outfmt);
02588 
02589   // XXX possible that format out of date, in which case we should
02590   // just re-write the file.
02591   if ((err !=0) || !canopen) return NS_ERROR_FAILURE;
02592 
02593   nsIMdbThumb* thumb = nsnull;
02594   mdbOpenPolicy policy = { { 0, 0 }, 0, 0 };
02595 
02596   err = factory->OpenFileStore(mEnv, dbHeap, oldFile, &policy, &thumb);
02597   if ((err !=0) || !thumb) return NS_ERROR_FAILURE;
02598 
02599   mdb_count total;
02600   mdb_count current;
02601   mdb_bool done;
02602   mdb_bool broken;
02603 
02604   do {
02605     err = thumb->DoMore(mEnv, &total, &current, &done, &broken);
02606   } while ((err == 0) && !broken && !done);
02607 
02608   if ((err == 0) && done) {
02609     err = factory->ThumbToOpenStore(mEnv, thumb, &mStore);
02610   }
02611 
02612   NS_IF_RELEASE(thumb);
02613 
02614   if (err != 0) return NS_ERROR_FAILURE;
02615 
02616   rv = CreateTokens();
02617   NS_ENSURE_SUCCESS(rv, rv);
02618 
02619   mdbOid oid = { kToken_HistoryRowScope, 1 };
02620   err = mStore->GetTable(mEnv, &oid, &mTable);
02621   NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE);
02622   if (!mTable) {
02623     NS_WARNING("Your history file is somehow corrupt.. deleting it.");
02624     return NS_ERROR_FAILURE;
02625   }
02626 
02627   err = mTable->GetMetaRow(mEnv, &oid, nsnull, getter_AddRefs(mMetaRow));
02628   if (err != 0)
02629     NS_WARNING("Could not get meta row\n");
02630 
02631   CheckHostnameEntries();
02632 
02633   if (err != 0) return NS_ERROR_FAILURE;
02634   
02635   return NS_OK;
02636 }
02637 
02638 nsresult
02639 nsGlobalHistory::OpenNewFile(nsIMdbFactory *factory, const char *filePath)
02640 {
02641   nsresult rv;
02642   mdb_err err;
02643   
02644   nsIMdbHeap* dbHeap = 0;
02645   nsCOMPtr<nsIMdbFile> newFile; // ensures file is released
02646   err = factory->CreateNewFile(mEnv, dbHeap, filePath,
02647                                getter_AddRefs(newFile));
02648 
02649   if ((err != 0) || !newFile) return NS_ERROR_FAILURE;
02650   
02651   mdbOpenPolicy policy = { { 0, 0 }, 0, 0 };
02652   err = factory->CreateNewFileStore(mEnv, dbHeap, newFile, &policy, &mStore);
02653   
02654   if (err != 0) return NS_ERROR_FAILURE;
02655   
02656   rv = CreateTokens();
02657   NS_ENSURE_SUCCESS(rv, rv);
02658 
02659   // Create the one and only table in the history db
02660   err = mStore->NewTable(mEnv, kToken_HistoryRowScope, kToken_HistoryKind, PR_TRUE, nsnull, &mTable);
02661   if (err != 0) return NS_ERROR_FAILURE;
02662   if (!mTable) return NS_ERROR_FAILURE;
02663 
02664   // Create the meta row.
02665   mdbOid oid = { kToken_HistoryRowScope, 1 };
02666   err = mTable->GetMetaRow(mEnv, &oid, nsnull, getter_AddRefs(mMetaRow));
02667   if (err != 0)
02668     NS_WARNING("Could not get meta row\n");
02669 
02670   // Force a commit now to get it written out.
02671   nsCOMPtr<nsIMdbThumb> thumb;
02672   err = mStore->LargeCommit(mEnv, getter_AddRefs(thumb));
02673   if (err != 0) return NS_ERROR_FAILURE;
02674 
02675   mdb_count total;
02676   mdb_count current;
02677   mdb_bool done;
02678   mdb_bool broken;
02679 
02680   do {
02681     err = thumb->DoMore(mEnv, &total, &current, &done, &broken);
02682   } while ((err == 0) && !broken && !done);
02683 
02684   if ((err != 0) || !done) return NS_ERROR_FAILURE;
02685 
02686   return NS_OK;
02687 }
02688 
02689 // Set the history file byte order if necessary, and determine if
02690 // we need to byte-swap Unicode values.
02691 // If the force argument is true, the file byte order will be set
02692 // to that of this machine.
02693 nsresult
02694 nsGlobalHistory::InitByteOrder(PRBool aForce)
02695 {
02696 #ifdef IS_LITTLE_ENDIAN
02697   NS_NAMED_LITERAL_CSTRING(machine_byte_order, "LE");
02698 #endif
02699 #ifdef IS_BIG_ENDIAN
02700   NS_NAMED_LITERAL_CSTRING(machine_byte_order, "BE");
02701 #endif
02702   nsXPIDLCString file_byte_order;
02703   nsresult rv = NS_OK;
02704 
02705   if (!aForce)
02706     rv = GetByteOrder(getter_Copies(file_byte_order));
02707   if (aForce || NS_FAILED(rv) ||
02708       !(file_byte_order.EqualsLiteral("BE") ||
02709         file_byte_order.EqualsLiteral("LE"))) {
02710     // Byte order is not yet set, or needs to be reset; initialize it.
02711     mReverseByteOrder = PR_FALSE;
02712     rv = SaveByteOrder(machine_byte_order.get());
02713     if (NS_FAILED(rv))
02714       return rv;
02715   }
02716   else
02717     mReverseByteOrder = !file_byte_order.Equals(machine_byte_order);
02718 
02719   return NS_OK;
02720 }
02721 
02722 // break the uri down into a search query, and pass off to
02723 // SearchEnumerator
02724 nsresult
02725 nsGlobalHistory::CreateFindEnumerator(nsIRDFResource *aSource,
02726                                       nsISimpleEnumerator **aResult)
02727 {
02728   nsresult rv;
02729   // make sure this was a find query
02730   if (!IsFindResource(aSource))
02731     return NS_ERROR_FAILURE;
02732 
02733   const char* uri;
02734   rv = aSource->GetValueConst(&uri);
02735   if (NS_FAILED(rv)) return rv;
02736 
02737   // convert uri to a query
02738   searchQuery* query = new searchQuery;
02739   if (!query) return NS_ERROR_OUT_OF_MEMORY;
02740   FindUrlToSearchQuery(uri, *query);
02741 
02742   // the enumerator will take ownership of the query
02743   SearchEnumerator *result =
02744     new SearchEnumerator(query, kToken_HiddenColumn, this);
02745   if (!result) return NS_ERROR_OUT_OF_MEMORY;
02746 
02747   rv = result->Init(mEnv, mTable);
02748   if (NS_FAILED(rv)) return rv;
02749 
02750   // return the value
02751   *aResult = result;
02752   NS_ADDREF(*aResult);
02753   
02754   return NS_OK;
02755 }
02756 
02757 
02758 // for each row, we need to parse out the hostname from the url
02759 // then store it in a column
02760 nsresult
02761 nsGlobalHistory::CheckHostnameEntries()
02762 {
02763   nsresult rv = NS_OK;
02764 
02765   mdb_err err;
02766 
02767   nsCOMPtr<nsIMdbTableRowCursor> cursor;
02768   nsCOMPtr<nsIMdbRow> row;
02769 
02770   err = mTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(cursor));
02771   if (err != 0) return NS_ERROR_FAILURE;
02772 
02773   int marker;
02774   err = mTable->StartBatchChangeHint(mEnv, &marker);
02775   NS_ASSERTION(err == 0, "unable to start batch");
02776   if (err != 0) return NS_ERROR_FAILURE;
02777   
02778   mdb_pos pos;
02779   err = cursor->NextRow(mEnv, getter_AddRefs(row), &pos);
02780   if (err != 0) return NS_ERROR_FAILURE;
02781 
02782   // comment out this code to rebuild the hostlist at startup
02783 #if 1
02784   // bail early if the first row has a hostname
02785   if (row) {
02786     nsCAutoString hostname;
02787     rv = GetRowValue(row, kToken_HostnameColumn, hostname);
02788     if (NS_SUCCEEDED(rv) && !hostname.IsEmpty())
02789       return NS_OK;
02790   }
02791 #endif
02792   
02793   // cached variables used in the loop
02794   nsCAutoString url;
02795   nsXPIDLCString hostname;
02796 
02797   nsCOMPtr<nsIIOService> ioService = do_GetService(NS_IOSERVICE_CONTRACTID);
02798   if (!ioService) return NS_ERROR_FAILURE;
02799   
02800 
02801   while (row) {
02802 #if 0
02803     rv = GetRowValue(row, kToken_URLColumn, url);
02804     if (NS_FAILED(rv)) break;
02805 
02806     ioService->ExtractUrlPart(url, nsIIOService::url_Host, 0, 0, getter_Copies(hostname));
02807 
02808     SetRowValue(row, kToken_HostnameColumn, hostname);
02809     
02810 #endif
02811 
02812     // to be turned on when we're confident in mork's ability
02813     // to handle yarn forms properly
02814 #if 0
02815     nsAutoString title;
02816     rv = GetRowValue(row, kToken_NameColumn, title);
02817     // reencode back into UTF8
02818     if (NS_SUCCEEDED(rv))
02819       SetRowValue(row, kToken_NameColumn, title.get());
02820 #endif
02821     cursor->NextRow(mEnv, getter_AddRefs(row), &pos);
02822   }
02823 
02824   // Finish the batch.
02825   err = mTable->EndBatchChangeHint(mEnv, &marker);
02826   NS_ASSERTION(err == 0, "error ending batch");
02827   
02828   return rv;
02829 }
02830 
02831 nsresult
02832 nsGlobalHistory::CreateTokens()
02833 {
02834   mdb_err err;
02835 
02836   NS_PRECONDITION(mStore != nsnull, "not initialized");
02837   if (! mStore)
02838     return NS_ERROR_NOT_INITIALIZED;
02839 
02840   err = mStore->StringToToken(mEnv, "ns:history:db:row:scope:history:all", &kToken_HistoryRowScope);
02841   if (err != 0) return NS_ERROR_FAILURE;
02842   
02843   err = mStore->StringToToken(mEnv, "ns:history:db:table:kind:history", &kToken_HistoryKind);
02844   if (err != 0) return NS_ERROR_FAILURE;
02845   
02846   err = mStore->StringToToken(mEnv, "URL", &kToken_URLColumn);
02847   if (err != 0) return NS_ERROR_FAILURE;
02848 
02849   err = mStore->StringToToken(mEnv, "Referrer", &kToken_ReferrerColumn);
02850   if (err != 0) return NS_ERROR_FAILURE;
02851 
02852   err = mStore->StringToToken(mEnv, "LastVisitDate", &kToken_LastVisitDateColumn);
02853   if (err != 0) return NS_ERROR_FAILURE;
02854 
02855   err = mStore->StringToToken(mEnv, "FirstVisitDate", &kToken_FirstVisitDateColumn);
02856   if (err != 0) return NS_ERROR_FAILURE;
02857 
02858   err = mStore->StringToToken(mEnv, "VisitCount", &kToken_VisitCountColumn);
02859   if (err != 0) return NS_ERROR_FAILURE;
02860 
02861   err = mStore->StringToToken(mEnv, "Name", &kToken_NameColumn);
02862   if (err != 0) return NS_ERROR_FAILURE;
02863 
02864   err = mStore->StringToToken(mEnv, "Hostname", &kToken_HostnameColumn);
02865   if (err != 0) return NS_ERROR_FAILURE;
02866   
02867   err = mStore->StringToToken(mEnv, "Hidden", &kToken_HiddenColumn);
02868   if (err != 0) return NS_ERROR_FAILURE;
02869 
02870   err = mStore->StringToToken(mEnv, "Typed", &kToken_TypedColumn);
02871   if (err != 0) return NS_ERROR_FAILURE;
02872 
02873   // meta-data tokens
02874   err = mStore->StringToToken(mEnv, "LastPageVisited", &kToken_LastPageVisited);
02875   err = mStore->StringToToken(mEnv, "ByteOrder", &kToken_ByteOrder);
02876 
02877   return NS_OK;
02878 }
02879 
02880 nsresult nsGlobalHistory::Commit(eCommitType commitType)
02881 {
02882   if (!mStore || !mTable)
02883     return NS_OK;
02884 
02885   nsresult    err = NS_OK;
02886   nsCOMPtr<nsIMdbThumb> thumb;
02887 
02888   if (commitType == kLargeCommit || commitType == kSessionCommit)
02889   {
02890     mdb_percent outActualWaste = 0;
02891     mdb_bool outShould;
02892     if (mStore) 
02893     {
02894       // check how much space would be saved by doing a compress commit.
02895       // If it's more than 30%, go for it.
02896       // N.B. - I'm not sure this calls works in Mork for all cases.
02897       err = mStore->ShouldCompress(mEnv, 30, &outActualWaste, &outShould);
02898       if (NS_SUCCEEDED(err) && outShould)
02899       {
02900           commitType = kCompressCommit;
02901       }
02902       else
02903       {
02904         mdb_count count;
02905         err = mTable->GetCount(mEnv, &count);
02906         // Since Mork's shouldCompress doesn't work, we need to look
02907         // at the file size and the number of rows, and make a stab
02908         // at guessing if we've got a lot of deleted rows. The file
02909         // size is the size when we opened the db, and we really want
02910         // it to be the size after we've written out the file,
02911         // but I think this is a good enough approximation.
02912         if (count > 0)
02913         {
02914           PRInt64 numRows;
02915           PRInt64 bytesPerRow;
02916           PRInt64 desiredAvgRowSize;
02917 
02918           LL_UI2L(numRows, count);
02919           LL_DIV(bytesPerRow, mFileSizeOnDisk, numRows);
02920           LL_I2L(desiredAvgRowSize, 400);
02921           if (LL_CMP(bytesPerRow, >, desiredAvgRowSize))
02922             commitType = kCompressCommit;
02923         }
02924       }
02925     }
02926   }
02927   switch (commitType)
02928   {
02929   case kLargeCommit:
02930     err = mStore->LargeCommit(mEnv, getter_AddRefs(thumb));
02931     break;
02932   case kSessionCommit:
02933     err = mStore->SessionCommit(mEnv, getter_AddRefs(thumb));
02934     break;
02935   case kCompressCommit:
02936     err = mStore->CompressCommit(mEnv, getter_AddRefs(thumb));
02937     break;
02938   }
02939   if (err == 0) {
02940     mdb_count total;
02941     mdb_count current;
02942     mdb_bool done;
02943     mdb_bool broken;
02944 
02945     do {
02946       err = thumb->DoMore(mEnv, &total, &current, &done, &broken);
02947     } while ((err == 0) && !broken && !done);
02948   }
02949   if (err != 0) // mork doesn't return NS error codes. Yet.
02950     return NS_ERROR_FAILURE;
02951   else
02952     return NS_OK;
02953 
02954 }
02955 // if notify is true, we'll notify rdf of deleted rows.
02956 // If we're shutting down history, then (maybe?) we don't
02957 // need or want to notify rdf.
02958 nsresult nsGlobalHistory::ExpireEntries(PRBool notify)
02959 {
02960   PRTime expirationDate;
02961   PRInt64 microSecondsPerSecond, secondsInDays, microSecondsInExpireDays;
02962   
02963   LL_I2L(microSecondsPerSecond, PR_USEC_PER_SEC);
02964   LL_UI2L(secondsInDays, 60 * 60 * 24 * mExpireDays);
02965   LL_MUL(microSecondsInExpireDays, secondsInDays, microSecondsPerSecond);
02966   LL_SUB(expirationDate, GetNow(), microSecondsInExpireDays);
02967 
02968   matchExpiration_t expiration;
02969   expiration.history = this;
02970   expiration.expirationDate = &expirationDate;
02971   
02972   return RemoveMatchingRows(matchExpirationCallback, (void *)&expiration, notify);
02973 }
02974 
02975 nsresult
02976 nsGlobalHistory::CloseDB()
02977 {
02978   if (!mStore)
02979     return NS_OK;
02980 
02981   mdb_err err;
02982 
02983   ExpireEntries(PR_FALSE /* don't notify */);
02984   err = Commit(kSessionCommit);
02985 
02986   // order is important here - logically smallest objects first
02987   mMetaRow = nsnull;
02988   
02989   if (mTable)
02990     mTable->Release();
02991 
02992   mStore->Release();
02993 
02994   if (mEnv)
02995     mEnv->Release();
02996 
02997   mTable = nsnull; mEnv = nsnull; mStore = nsnull;
02998 
02999   return NS_OK;
03000 }
03001 
03002 nsresult
03003 nsGlobalHistory::FindRow(mdb_column aCol,
03004                          const char *aValue, nsIMdbRow **aResult)
03005 {
03006   if (! mStore)
03007     return NS_ERROR_NOT_INITIALIZED;
03008 
03009   mdb_err err;
03010   PRInt32 len = PL_strlen(aValue);
03011   mdbYarn yarn = { (void*) aValue, len, len, 0, 0, nsnull };
03012 
03013   mdbOid rowId;
03014   nsCOMPtr<nsIMdbRow> row;
03015   if (aResult) {
03016     err = mStore->FindRow(mEnv, kToken_HistoryRowScope,
03017                           aCol, &yarn,
03018                           &rowId, getter_AddRefs(row));
03019 
03020     if (!row) return NS_ERROR_NOT_AVAILABLE;
03021   } else {
03022     err = mStore->FindRow(mEnv, kToken_HistoryRowScope,
03023                           aCol, &yarn, &rowId, nsnull);
03024   }
03025 
03026   // make sure it's actually stored in the main table
03027   mdb_bool hasRow;
03028   mTable->HasOid(mEnv, &rowId, &hasRow);
03029 
03030   if (!hasRow) return NS_ERROR_NOT_AVAILABLE;
03031   
03032   if (aResult) {
03033     *aResult = row;
03034     (*aResult)->AddRef();
03035   }
03036 
03037   return NS_OK;
03038 }
03039 
03040 PRBool
03041 nsGlobalHistory::IsURLInHistory(nsIRDFResource* aResource)
03042 {
03043   nsresult rv;
03044 
03045   const char* url;
03046   rv = aResource->GetValueConst(&url);
03047   if (NS_FAILED(rv)) return PR_FALSE;
03048 
03049   rv = FindRow(kToken_URLColumn, url, nsnull);
03050   return (NS_SUCCEEDED(rv)) ? PR_TRUE : PR_FALSE;
03051 }
03052 
03053 
03054 nsresult
03055 nsGlobalHistory::NotifyAssert(nsIRDFResource* aSource,
03056                               nsIRDFResource* aProperty,
03057                               nsIRDFNode* aValue)
03058 {
03059   nsresult rv;
03060 
03061   if (mObservers) {
03062     PRUint32 count;
03063     rv = mObservers->Count(&count);
03064     if (NS_FAILED(rv)) return rv;
03065 
03066     for (PRInt32 i = 0; i < PRInt32(count); ++i) {
03067       nsIRDFObserver* observer = NS_STATIC_CAST(nsIRDFObserver*, mObservers->ElementAt(i));
03068 
03069       NS_ASSERTION(observer != nsnull, "null ptr");
03070       if (! observer)
03071         continue;
03072 
03073       rv = observer->OnAssert(this, aSource, aProperty, aValue);
03074       NS_RELEASE(observer);
03075     }
03076   }
03077 
03078   return NS_OK;
03079 }
03080 
03081 
03082 nsresult
03083 nsGlobalHistory::NotifyUnassert(nsIRDFResource* aSource,
03084                                 nsIRDFResource* aProperty,
03085                                 nsIRDFNode* aValue)
03086 {
03087   nsresult rv;
03088 
03089   if (mObservers) {
03090     PRUint32 count;
03091     rv = mObservers->Count(&count);
03092     if (NS_FAILED(rv)) return rv;
03093 
03094     for (PRInt32 i = 0; i < PRInt32(count); ++i) {
03095       nsIRDFObserver* observer = NS_STATIC_CAST(nsIRDFObserver*, mObservers->ElementAt(i));
03096 
03097       NS_ASSERTION(observer != nsnull, "null ptr");
03098       if (! observer)
03099         continue;
03100 
03101       rv = observer->OnUnassert(this, aSource, aProperty, aValue);
03102       NS_RELEASE(observer);
03103     }
03104   }
03105 
03106   return NS_OK;
03107 }
03108 
03109 
03110 
03111 nsresult
03112 nsGlobalHistory::NotifyChange(nsIRDFResource* aSource,
03113                               nsIRDFResource* aProperty,
03114                               nsIRDFNode* aOldValue,
03115                               nsIRDFNode* aNewValue)
03116 {
03117   nsresult rv;
03118 
03119   if (mObservers) {
03120     PRUint32 count;
03121     rv = mObservers->Count(&count);
03122     if (NS_FAILED(rv)) return rv;
03123 
03124     for (PRInt32 i = 0; i < PRInt32(count); ++i) {
03125       nsIRDFObserver* observer = NS_STATIC_CAST(nsIRDFObserver*, mObservers->ElementAt(i));
03126 
03127       NS_ASSERTION(observer != nsnull, "null ptr");
03128       if (! observer)
03129         continue;
03130 
03131       rv = observer->OnChange(this, aSource, aProperty, aOldValue, aNewValue);
03132       NS_RELEASE(observer);
03133     }
03134   }
03135 
03136   return NS_OK;
03137 }
03138 
03139 //
03140 // this just generates a static list of find-style queries
03141 // only returns queries that currently have matches in global history
03142 // 
03143 nsresult
03144 nsGlobalHistory::GetRootDayQueries(nsISimpleEnumerator **aResult)
03145 {
03146   nsresult rv;
03147   nsCOMPtr<nsISupportsArray> dayArray;
03148   NS_NewISupportsArray(getter_AddRefs(dayArray));
03149   
03150   PRInt32 i;
03151   nsCOMPtr<nsIRDFResource> finduri;
03152   nsDependentCString
03153     prefix(FIND_BY_AGEINDAYS_PREFIX "is" "&text=");
03154   nsCAutoString uri;
03155   nsCOMPtr<nsISimpleEnumerator> findEnumerator;
03156   PRBool hasMore = PR_FALSE;
03157   for (i=0; i<7; i++) {
03158     uri = prefix;
03159     uri.AppendInt(i);
03160     uri.Append("&groupby=Hostname");
03161     rv = gRDFService->GetResource(uri, getter_AddRefs(finduri));
03162     if (NS_FAILED(rv)) continue;
03163     rv = CreateFindEnumerator(finduri, getter_AddRefs(findEnumerator));
03164     if (NS_FAILED(rv)) continue;
03165     rv = findEnumerator->HasMoreElements(&hasMore);
03166     if (NS_SUCCEEDED(rv) && hasMore)
03167       dayArray->AppendElement(finduri);
03168   }
03169 
03170   uri = FIND_BY_AGEINDAYS_PREFIX "isgreater" "&text=";
03171   uri.AppendInt(i-1);
03172   uri.Append("&groupby=Hostname");
03173   rv = gRDFService->GetResource(uri, getter_AddRefs(finduri));
03174   if (NS_SUCCEEDED(rv)) {
03175     rv = CreateFindEnumerator(finduri, getter_AddRefs(findEnumerator));
03176     if (NS_SUCCEEDED(rv)) {
03177       rv = findEnumerator->HasMoreElements(&hasMore);
03178       if (NS_SUCCEEDED(rv) && hasMore)
03179         dayArray->AppendElement(finduri);
03180     }
03181   }
03182 
03183   return NS_NewArrayEnumerator(aResult, dayArray);
03184 }
03185 
03186 //
03187 // convert the name/value pairs stored in a string into an array of
03188 // these pairs
03189 // find:a=b&c=d&e=f&g=h
03190 // becomes an array containing
03191 // {"a" = "b", "c" = "d", "e" = "f", "g" = "h" }
03192 //
03193 nsresult
03194 nsGlobalHistory::FindUrlToTokenList(const char *aURL, nsVoidArray& aResult)
03195 {
03196   if (PL_strncmp(aURL, "find:", 5) != 0)
03197     return NS_ERROR_UNEXPECTED;
03198   
03199   const char *curpos = aURL + 5;
03200   const char *tokenstart = curpos;
03201 
03202   // this is where we will store the current name and value
03203   const char *tokenName = nsnull;
03204   const char *tokenValue = nsnull;
03205   PRUint32 tokenNameLength=0;
03206   PRUint32 tokenValueLength=0;
03207   
03208   PRBool haveValue = PR_FALSE;  // needed because some values are 0-length
03209   while (PR_TRUE) {
03210     while (*curpos && (*curpos != '&') && (*curpos != '='))
03211       curpos++;
03212 
03213     if (*curpos == '=')  {        // just found a token name
03214       tokenName = tokenstart;
03215       tokenNameLength = (curpos - tokenstart);
03216     }
03217     else if ((!*curpos || *curpos == '&') &&
03218              (tokenNameLength>0)) { // found a value, and we have a
03219                                     // name
03220       tokenValue = tokenstart;
03221       tokenValueLength = (curpos - tokenstart);
03222       haveValue = PR_TRUE;
03223     }
03224 
03225     // once we have a name/value pair, store it away
03226     // note we're looking at lengths, so that
03227     // "find:&a=b" doesn't connect with a=""
03228     if (tokenNameLength>0 && haveValue) {
03229 
03230       tokenPair *tokenStruct = new tokenPair(tokenName, tokenNameLength,
03231                                              tokenValue, tokenValueLength);
03232       aResult.AppendElement((void *)tokenStruct);
03233       
03234       // reset our state
03235       tokenName = tokenValue = nsnull;
03236       tokenNameLength = tokenValueLength = 0;
03237       haveValue = PR_FALSE;
03238     }
03239 
03240     // the test has to be here to catch empty values
03241     if (!*curpos) break;
03242     
03243     curpos++;
03244     tokenstart = curpos;
03245   }
03246 
03247   return NS_OK;
03248 }
03249 
03250 void
03251 nsGlobalHistory::FreeTokenList(nsVoidArray& tokens)
03252 {
03253   PRUint32 length = tokens.Count();
03254   PRUint32 i;
03255   for (i=0; i<length; i++) {
03256     tokenPair *token = (tokenPair*)tokens[i];
03257     delete token;
03258   }
03259   tokens.Clear();
03260 }
03261 
03262 void nsGlobalHistory::FreeSearchQuery(searchQuery& aQuery)
03263 {
03264   // free up the token pairs
03265   PRInt32 i;
03266   for (i=0; i<aQuery.terms.Count(); i++) {
03267     searchTerm *term = (searchTerm*)aQuery.terms.ElementAt(i);
03268     delete term;
03269   }
03270   // clean out the array, just for good measure
03271   aQuery.terms.Clear();
03272 }
03273 
03274 //
03275 // helper function to figure out if something starts with "find"
03276 //
03277 PRBool
03278 nsGlobalHistory::IsFindResource(nsIRDFResource *aResource)
03279 {
03280   nsresult rv;
03281   const char *value;
03282   rv = aResource->GetValueConst(&value);
03283   if (NS_FAILED(rv)) return PR_FALSE;
03284 
03285   return (PL_strncmp(value, "find:", 5)==0);
03286 }
03287 
03288 //
03289 // convert a list of name/value pairs into a search query with 0 or
03290 // more terms and an optional groupby
03291 //
03292 // a term consists of the values of the 4 name/value pairs
03293 // {datasource, match, method, text}
03294 // groupby is stored as a column #
03295 //
03296 nsresult
03297 nsGlobalHistory::TokenListToSearchQuery(const nsVoidArray& aTokens,
03298                                         searchQuery& aResult)
03299 {
03300 
03301   PRInt32 i;
03302   PRInt32 length = aTokens.Count();
03303 
03304   aResult.groupBy = 0;
03305   const char *datasource=nsnull, *property=nsnull,
03306     *method=nsnull, *text=nsnull;
03307 
03308   PRUint32 datasourceLen=0, propertyLen=0, methodLen=0, textLen=0;
03309   rowMatchCallback matchCallback=nsnull; // matching callback if needed
03310   
03311   for (i=0; i<length; i++) {
03312     tokenPair *token = (tokenPair *)aTokens[i];
03313 
03314     // per-term tokens
03315     const nsASingleFragmentCString& tokenName =
03316         Substring(token->tokenName, token->tokenName + token->tokenNameLength);
03317     if (tokenName.EqualsLiteral("datasource")) {
03318       datasource = token->tokenValue;
03319       datasourceLen = token->tokenValueLength;
03320     }
03321     else if (tokenName.EqualsLiteral("match")) {
03322       if (Substring(token->tokenValue, token->tokenValue+token->tokenValueLength).Equals("AgeInDays"))
03323         matchCallback = matchAgeInDaysCallback;
03324       
03325       property = token->tokenValue;
03326       propertyLen = token->tokenValueLength;
03327     }
03328     else if (tokenName.EqualsLiteral("method")) {
03329       method = token->tokenValue;
03330       methodLen = token->tokenValueLength;
03331     }    
03332     else if (tokenName.EqualsLiteral("text")) {
03333       text = token->tokenValue;
03334       textLen = token->tokenValueLength;
03335     }
03336     
03337     // really, we should be storing the group-by as a column number or
03338     // rdf resource
03339     else if (tokenName.EqualsLiteral("groupby")) {
03340       mdb_err err;
03341       err = mStore->QueryToken(mEnv,
03342                                nsCAutoString(token->tokenValue).get(),
03343                                &aResult.groupBy);
03344       if (err != 0)
03345         aResult.groupBy = 0;
03346     }
03347     
03348     // once we complete a term, we move on to the next one
03349     if (datasource && property && method && text) {
03350       searchTerm *currentTerm = new searchTerm(datasource, datasourceLen,
03351                                                property, propertyLen,
03352                                                method, methodLen,
03353                                                text, textLen);
03354       currentTerm->match = matchCallback;
03355       
03356       // append the old one, then create a new one
03357       aResult.terms.AppendElement((void *)currentTerm);
03358 
03359       // reset our state
03360       matchCallback=nsnull;
03361       currentTerm = nsnull;
03362       datasource = property = method = text = 0;
03363     }
03364   }
03365 
03366   return NS_OK;
03367 }
03368 
03369 nsresult
03370 nsGlobalHistory::FindUrlToSearchQuery(const char *aUrl, searchQuery& aResult)
03371 {
03372 
03373   nsresult rv;
03374   // convert uri to list of tokens
03375   nsVoidArray tokenPairs;
03376   rv = FindUrlToTokenList(aUrl, tokenPairs);
03377   if (NS_FAILED(rv)) return rv;
03378 
03379   // now convert the tokens to a query
03380   rv = TokenListToSearchQuery(tokenPairs, aResult);
03381   
03382   FreeTokenList(tokenPairs);
03383 
03384   return rv;
03385 }
03386 
03387 // preemptively construct some common find-queries so that we show up
03388 // asychronously when a search is open
03389 
03390 // we have to do the following assertions:
03391 // (a=AgeInDays, h=hostname; g=groupby, -> = #child)
03392 // 1) NC:HistoryRoot -> uri
03393 //
03394 // 2) NC:HistoryByDate -> a&g=h
03395 // 3)                     a&g=h -> a&h
03396 // 4)                              a&h -> uri
03397 //
03398 // 5) g=h -> h
03399 // 6)        h->uri
03400 nsresult
03401 nsGlobalHistory::NotifyFindAssertions(nsIRDFResource *aSource,
03402                                       nsIMdbRow *aRow)
03403 {
03404   // we'll construct a bunch of sample queries, and then do
03405   // appropriate assertions
03406 
03407   // first pull out the appropriate values
03408   PRTime lastVisited;
03409   GetRowValue(aRow, kToken_LastVisitDateColumn, &lastVisited);
03410 
03411   PRInt32 ageInDays = GetAgeInDays(lastVisited);
03412   nsCAutoString ageString; ageString.AppendInt(ageInDays);
03413 
03414   nsCAutoString hostname;
03415   GetRowValue(aRow, kToken_HostnameColumn, hostname);
03416   
03417   // construct some terms that we'll use later
03418   
03419   // Hostname=<hostname>
03420   searchTerm hostterm("history", sizeof("history")-1,
03421                       "Hostname", sizeof("Hostname")-1,
03422                       "is", sizeof("is")-1,
03423                       hostname.get(), hostname.Length());
03424 
03425   // AgeInDays=<age>
03426   searchTerm ageterm("history", sizeof("history") -1,
03427                      "AgeInDays", sizeof("AgeInDays")-1,
03428                      "is", sizeof("is")-1,
03429                      ageString.get(), ageString.Length());
03430 
03431   searchQuery query;
03432   nsCAutoString findUri;
03433   nsCOMPtr<nsIRDFResource> childFindResource;
03434   nsCOMPtr<nsIRDFResource> parentFindResource;
03435 
03436   // 2) NC:HistoryByDate -> AgeInDays=<age>&groupby=Hostname
03437   query.groupBy = kToken_HostnameColumn;
03438   query.terms.AppendElement((void *)&ageterm);
03439 
03440   GetFindUriPrefix(query, PR_TRUE, findUri);
03441   gRDFService->GetResource(findUri, getter_AddRefs(childFindResource));
03442   NotifyAssert(kNC_HistoryByDate, kNC_child, childFindResource);
03443   
03444   query.terms.Clear();
03445 
03446   // 3) AgeInDays=<age>&groupby=Hostname ->
03447   //    AgeInDays=<age>&Hostname=<host>
03448   
03449   parentFindResource=childFindResource; // AgeInDays=<age>&groupby=Hostname
03450 
03451   query.groupBy = 0;            // create AgeInDays=<age>&Hostname=<host>
03452   query.terms.AppendElement((void *)&ageterm);
03453   query.terms.AppendElement((void *)&hostterm);
03454   
03455   GetFindUriPrefix(query, PR_FALSE, findUri);
03456   gRDFService->GetResource(findUri, getter_AddRefs(childFindResource));
03457   NotifyAssert(parentFindResource, kNC_child, childFindResource);
03458   
03459   query.terms.Clear();
03460 
03461   // 4) AgeInDays=<age>&Hostname=<host> -> uri
03462   parentFindResource = childFindResource; // AgeInDays=<age>&hostname=<host>
03463   NotifyAssert(childFindResource, kNC_child, aSource);
03464   
03465   // 5) groupby=Hostname -> Hostname=<host>
03466   query.groupBy = kToken_HostnameColumn; // create groupby=Hostname
03467   
03468   GetFindUriPrefix(query, PR_TRUE, findUri);
03469   gRDFService->GetResource(findUri, getter_AddRefs(parentFindResource));
03470 
03471   query.groupBy = 0;            // create Hostname=<host>
03472   query.terms.AppendElement((void *)&hostterm);
03473   GetFindUriPrefix(query, PR_FALSE, findUri);
03474   findUri.Append(hostname);     // append <host>
03475   gRDFService->GetResource(findUri, getter_AddRefs(childFindResource));
03476   
03477   NotifyAssert(parentFindResource, kNC_child, childFindResource);
03478 
03479   // 6) Hostname=<host> -> uri
03480   parentFindResource = childFindResource; // Hostname=<host>
03481   NotifyAssert(parentFindResource, kNC_child, aSource);
03482 
03483   return NS_OK;
03484 }
03485 
03486 
03487 // simpler than NotifyFindAssertions - basically just notifies
03488 // unassertions from
03489 // 1) NC:HistoryRoot -> uri
03490 // 2) a&h -> uri
03491 // 3) h -> uri
03492 
03493 nsresult
03494 nsGlobalHistory::NotifyFindUnassertions(nsIRDFResource *aSource,
03495                                         nsIMdbRow* aRow)
03496 {
03497   // 1) NC:HistoryRoot
03498   NotifyUnassert(kNC_HistoryRoot, kNC_child, aSource);
03499 
03500   //    first get age in days
03501   PRTime lastVisited;
03502   GetRowValue(aRow, kToken_LastVisitDateColumn, &lastVisited);
03503   PRInt32 ageInDays = GetAgeInDays(lastVisited);
03504   nsCAutoString ageString; ageString.AppendInt(ageInDays);
03505 
03506   //    now get hostname
03507   nsCAutoString hostname;
03508   GetRowValue(aRow, kToken_HostnameColumn, hostname);
03509 
03510   //    construct some terms
03511   //    Hostname=<hostname>
03512   searchTerm hostterm("history", sizeof("history")-1,
03513                       "Hostname", sizeof("Hostname")-1,
03514                       "is", sizeof("is")-1,
03515                       hostname.get(), hostname.Length());
03516   
03517   //    AgeInDays=<age>
03518   searchTerm ageterm("history", sizeof("history") -1,
03519                      "AgeInDays", sizeof("AgeInDays")-1,
03520                      "is", sizeof("is")-1,
03521                      ageString.get(), ageString.Length());
03522 
03523   searchQuery query;
03524   query.groupBy = 0;
03525   
03526   nsCAutoString findUri;
03527   nsCOMPtr<nsIRDFResource> findResource;
03528   
03529   // 2) AgeInDays=<age>&Hostname=<host>
03530   query.terms.AppendElement((void *)&ageterm);
03531   query.terms.AppendElement((void *)&hostterm);
03532   GetFindUriPrefix(query, PR_FALSE, findUri);
03533   
03534   gRDFService->GetResource(findUri, getter_AddRefs(findResource));
03535   
03536   NotifyUnassert(findResource, kNC_child, aSource);
03537 
03538   // 3) Hostname=<host>
03539   query.terms.Clear();
03540   
03541   query.terms.AppendElement((void *)&hostterm);
03542   GetFindUriPrefix(query, PR_FALSE, findUri);
03543   
03544   gRDFService->GetResource(findUri, getter_AddRefs(findResource));
03545   NotifyUnassert(findResource, kNC_child, aSource);
03546 
03547   return NS_OK;
03548 }
03549 
03550 //
03551 // get the user-visible "name" of a find resource
03552 // we basically parse the string, and use the data stored in the last
03553 // term to generate an appropriate string
03554 //
03555 nsresult
03556 nsGlobalHistory::GetFindUriName(const char *aURL, nsIRDFNode **aResult)
03557 {
03558 
03559   nsresult rv;
03560 
03561   searchQuery query;
03562   rv = FindUrlToSearchQuery(aURL, query);
03563 
03564   // can't exactly get a name if there's nothing to search for
03565   if (query.terms.Count() < 1)
03566     return NS_OK;
03567 
03568   // now build up a string from the query (using only the last term)
03569   searchTerm *term = (searchTerm*)query.terms[query.terms.Count()-1];
03570 
03571   // automatically build up string in the form
03572   // findurl-<property>-<method>[-<text>]
03573   // such as "finduri-AgeInDays-is" or "find-uri-AgeInDays-is-0"
03574   nsAutoString stringName(NS_LITERAL_STRING("finduri-"));
03575 
03576   // property
03577   AppendASCIItoUTF16(term->property, stringName);
03578   stringName.Append(PRUnichar('-'));
03579 
03580   // and now the method, such as "is" or "isgreater"
03581   AppendASCIItoUTF16(term->method, stringName);
03582 
03583   // try adding -<text> to see if there's a match
03584   // for example, to match
03585   // finduri-LastVisitDate-is-0=Today
03586   PRInt32 preTextLength = stringName.Length();
03587   stringName.Append(PRUnichar('-'));
03588   stringName.Append(term->text);
03589   stringName.Append(PRUnichar(0));
03590 
03591   // try to find a localizable string
03592   const PRUnichar *strings[] = {
03593     term->text.get()
03594   };
03595   nsXPIDLString value;
03596 
03597   // first with the search text
03598   rv = mBundle->FormatStringFromName(stringName.get(),
03599                                      strings, 1, getter_Copies(value));
03600 
03601   // ok, try it without the -<text>, to match
03602   // finduri-LastVisitDate-is=%S days ago
03603   if (NS_FAILED(rv)) {
03604     stringName.Truncate(preTextLength);
03605     rv = mBundle->FormatStringFromName(stringName.get(),
03606                                        strings, 1, getter_Copies(value));
03607   }
03608 
03609   nsCOMPtr<nsIRDFLiteral> literal;
03610   if (NS_SUCCEEDED(rv)) {
03611     rv = gRDFService->GetLiteral(value, getter_AddRefs(literal));
03612   } else {
03613     // ok, no such string, so just put the match text itself there
03614     rv = gRDFService->GetLiteral(term->text.get(),
03615                                  getter_AddRefs(literal));
03616   }
03617   FreeSearchQuery(query);
03618     
03619   if (NS_FAILED(rv)) return rv;
03620   
03621   *aResult = literal;
03622   NS_ADDREF(*aResult);
03623   return NS_OK;
03624 }
03625 
03626 NS_IMETHODIMP
03627 nsGlobalHistory::Observe(nsISupports *aSubject, 
03628                          const char *aTopic,
03629                          const PRUnichar *aSomeData)
03630 {
03631   nsresult rv;
03632   // pref changing - update member vars
03633   if (!nsCRT::strcmp(aTopic, "nsPref:changed")) {
03634     NS_ENSURE_STATE(gPrefBranch);
03635 
03636     // expiration date
03637     if (!nsCRT::strcmp(aSomeData, NS_LITERAL_STRING(PREF_BROWSER_HISTORY_EXPIRE_DAYS).get())) {
03638       gPrefBranch->GetIntPref(PREF_BROWSER_HISTORY_EXPIRE_DAYS, &mExpireDays);
03639     }
03640     else if (!nsCRT::strcmp(aSomeData, NS_LITERAL_STRING(PREF_AUTOCOMPLETE_ONLY_TYPED).get())) {
03641       gPrefBranch->GetBoolPref(PREF_AUTOCOMPLETE_ONLY_TYPED, &mAutocompleteOnlyTyped);
03642     }
03643   }
03644   else if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
03645     rv = CloseDB();    
03646     if (!nsCRT::strcmp(aSomeData, NS_LITERAL_STRING("shutdown-cleanse").get())) {
03647       nsCOMPtr <nsIFile> historyFile;
03648       rv = NS_GetSpecialDirectory(NS_APP_HISTORY_50_FILE, getter_AddRefs(historyFile));
03649       if (NS_SUCCEEDED(rv))
03650         rv = historyFile->Remove(PR_FALSE);
03651     }
03652   }
03653   else if (!nsCRT::strcmp(aTopic, "profile-do-change"))
03654     rv = OpenDB();
03655 
03656   return NS_OK;
03657 }
03658 
03659 //----------------------------------------------------------------------
03660 //
03661 // nsGlobalHistory::URLEnumerator
03662 //
03663 //   Implementation
03664 
03665 nsGlobalHistory::URLEnumerator::~URLEnumerator()
03666 {
03667   nsMemory::Free(mSelectValue);
03668 }
03669 
03670 
03671 PRBool
03672 nsGlobalHistory::URLEnumerator::IsResult(nsIMdbRow* aRow)
03673 {
03674   if (HasCell(mEnv, aRow, mHiddenColumn))
03675     return PR_FALSE;
03676   
03677   if (mSelectColumn) {
03678     mdb_err err;
03679 
03680     mdbYarn yarn;
03681     err = aRow->AliasCellYarn(mEnv, mURLColumn, &yarn);
03682     if (err != 0) return PR_FALSE;
03683 
03684     // Do bitwise comparison
03685     PRInt32 count = PRInt32(yarn.mYarn_Fill);
03686     if (count != mSelectValueLen)
03687       return PR_FALSE;
03688 
03689     const char* p = (const char*) yarn.mYarn_Buf;
03690     const char* q = (const char*) mSelectValue;
03691 
03692     while (--count >= 0) {
03693       if (*p++ != *q++)
03694         return PR_FALSE;
03695     }
03696   }
03697 
03698   return PR_TRUE;
03699 }
03700 
03701 nsresult
03702 nsGlobalHistory::URLEnumerator::ConvertToISupports(nsIMdbRow* aRow, nsISupports** aResult)
03703 {
03704   mdb_err err;
03705 
03706   mdbYarn yarn;
03707   err = aRow->AliasCellYarn(mEnv, mURLColumn, &yarn);
03708   if (err != 0) return NS_ERROR_FAILURE;
03709 
03710   // Since the URLEnumerator always returns the value of the URL
03711   // column, we create an RDF resource.
03712   nsresult rv;
03713   nsCOMPtr<nsIRDFResource> resource;
03714   const char* startPtr = (const char*) yarn.mYarn_Buf;
03715   rv = gRDFService->GetResource(
03716             Substring(startPtr, startPtr+yarn.mYarn_Fill),
03717             getter_AddRefs(resource));
03718   if (NS_FAILED(rv)) return rv;
03719 
03720   *aResult = resource;
03721   NS_ADDREF(*aResult);
03722   return NS_OK;
03723 }
03724 
03725 //----------------------------------------------------------------------
03726 // nsGlobalHistory::SearchEnumerator
03727 //
03728 //   Implementation
03729 
03730 nsGlobalHistory::SearchEnumerator::~SearchEnumerator()
03731 {
03732   nsGlobalHistory::FreeSearchQuery(*mQuery);
03733   delete mQuery;
03734 }
03735 
03736 
03737 // convert the query in mQuery into a find URI
03738 // if there is a groupby= in the query, then convert that
03739 // into the start of another search term
03740 // for example, in the following query with one term:
03741 //
03742 // term[0] = { history, AgeInDays, is, 0 }
03743 // groupby = Hostname
03744 //
03745 // we generate the following uri:
03746 //
03747 // find:datasource=history&match=AgeInDays&method=is&text=0&datasource=history
03748 //   &match=Hostname&method=is&text=
03749 //
03750 // and then the caller will append some text after after the "text="
03751 //
03752 void
03753 nsGlobalHistory::GetFindUriPrefix(const searchQuery& aQuery,
03754                                   const PRBool aDoGroupBy,
03755                                   nsACString& aResult)
03756 {
03757   mdb_err err;
03758   
03759   aResult.Assign("find:");
03760   PRUint32 length = aQuery.terms.Count();
03761   PRUint32 i;
03762   
03763   for (i=0; i<length; i++) {
03764     searchTerm *term = (searchTerm*)aQuery.terms[i];
03765     if (i != 0)
03766       aResult.Append('&');
03767     aResult.Append("datasource=");
03768     aResult.Append(term->datasource);
03769     
03770     aResult.Append("&match=");
03771     aResult.Append(term->property);
03772     
03773     aResult.Append("&method=");
03774     aResult.Append(term->method);
03775 
03776     aResult.Append("&text=");
03777     AppendUTF16toUTF8(term->text, aResult);
03778   }
03779 
03780   if (aQuery.groupBy == 0) return;
03781 
03782   // find out the name of the column we're grouping by
03783   char groupby[100];
03784   mdbYarn yarn = { groupby, 0, sizeof(groupby), 0, 0, nsnull };
03785   err = mStore->TokenToString(mEnv, aQuery.groupBy, &yarn);
03786   
03787   // put a "groupby=<colname>"
03788   if (aDoGroupBy) {
03789     aResult.Append("&groupby=");
03790     if (err == 0)
03791       aResult.Append((const char*)yarn.mYarn_Buf, yarn.mYarn_Fill);
03792   }
03793 
03794   // put &datasource=history&match=<colname>&method=is&text=
03795   else {
03796     // if the query has a groupby=<foo> then we want to append that
03797     // field as the last field to match.. caller has to be sure to
03798     // append that!
03799     aResult.Append("&datasource=history");
03800     
03801     aResult.Append("&match=");
03802     if (err == 0)
03803       aResult.Append((const char*)yarn.mYarn_Buf, yarn.mYarn_Fill);
03804     // herep  
03805     aResult.Append("&method=is");
03806     aResult.Append("&text=");
03807   }
03808   
03809 }
03810 
03811 //
03812 // determines if the given row matches all terms
03813 //
03814 // if there is a "groupBy" column, then we have to remember that we've
03815 // seen a row with the given value in that column, and then make sure
03816 // all future rows with that value in that column DON'T match, no
03817 // matter if they match the terms or not.
03818 PRBool
03819 nsGlobalHistory::SearchEnumerator::IsResult(nsIMdbRow *aRow)
03820 {
03821   if (HasCell(mEnv, aRow, mHiddenColumn))
03822     return PR_FALSE;
03823   
03824   mdb_err err;
03825 
03826   mdbYarn groupColumnValue = { nsnull, 0, 0, 0, 0, nsnull};
03827   if (mQuery->groupBy!=0) {
03828 
03829     // if we have a 'groupby', then we use the hashtable to make sure
03830     // we only match the FIRST row with the column value that we're
03831     // grouping by
03832     
03833     err = aRow->AliasCellYarn(mEnv, mQuery->groupBy, &groupColumnValue);
03834     if (err!=0) return PR_FALSE;
03835     if (!groupColumnValue.mYarn_Buf) return PR_FALSE;
03836 
03837     const char* startPtr = (const char*)groupColumnValue.mYarn_Buf;
03838     nsCStringKey key(Substring(startPtr,
03839                                startPtr +  groupColumnValue.mYarn_Fill));
03840 
03841     void *otherRow = mUniqueRows.Get(&key);
03842 
03843     // Hey! we've seen this row before, so ignore it
03844     if (otherRow) return PR_FALSE;
03845   }
03846 
03847   // now do the actual match
03848   if (!mHistory->RowMatches(aRow, mQuery))
03849     return PR_FALSE;
03850 
03851   if (mQuery->groupBy != 0) {
03852     // we got this far, so we must have matched.
03853     // add ourselves to the hashtable so we don't match rows like this
03854     // in the future
03855     const char* startPtr = (const char*)groupColumnValue.mYarn_Buf;
03856     nsCStringKey key(Substring(startPtr,
03857                                startPtr + groupColumnValue.mYarn_Fill));
03858     
03859     // note - weak ref, don't worry about releasing
03860     mUniqueRows.Put(&key, (void *)aRow);
03861   }
03862 
03863   return PR_TRUE;
03864 }
03865 
03866 //
03867 // determines if the row matches the given terms, used above
03868 //
03869 PRBool
03870 nsGlobalHistory::RowMatches(nsIMdbRow *aRow,
03871                             searchQuery *aQuery)
03872 {
03873   PRUint32 length = aQuery->terms.Count();
03874   PRUint32 i;
03875 
03876   for (i=0; i<length; i++) {
03877     
03878     searchTerm *term = (searchTerm*)aQuery->terms[i];
03879 
03880     if (!term->datasource.Equals("history"))
03881       continue;                 // we only match against history queries
03882     
03883     // use callback if it exists
03884     if (term->match) {
03885       // queue up some values just in case callback needs it
03886       // (how would we do this dynamically?)
03887       matchSearchTerm_t matchSearchTerm = { mEnv, mStore, term, PR_FALSE, 0, this };
03888       
03889       if (!term->match(aRow, (void *)&matchSearchTerm))
03890         return PR_FALSE;
03891     } else {
03892       mdb_err err;
03893 
03894       mdb_column property_column;
03895       nsCAutoString property_name(term->property);
03896       property_name.Append(char(0));
03897       
03898       err = mStore->QueryToken(mEnv, property_name.get(), &property_column);
03899       if (err != 0) {
03900         NS_WARNING("Unrecognized column!");
03901         continue;               // assume we match???
03902       }
03903       
03904       // match the term directly against the column?
03905       mdbYarn yarn;
03906       err = aRow->AliasCellYarn(mEnv, property_column, &yarn);
03907       if (err != 0 || !yarn.mYarn_Buf) return PR_FALSE;
03908 
03909       const char* startPtr;
03910       PRInt32 yarnLength = yarn.mYarn_Fill;
03911       nsCAutoString titleStr;
03912       if (property_column == kToken_NameColumn) {
03913         AppendUTF16toUTF8(Substring((const PRUnichar*)yarn.mYarn_Buf,
03914                                     (const PRUnichar*)yarn.mYarn_Buf + yarnLength),
03915                           titleStr);
03916         startPtr = titleStr.get();
03917         yarnLength = titleStr.Length();
03918       }
03919       else {
03920         // account for null strings
03921         if (yarn.mYarn_Buf)
03922           startPtr = (const char *)yarn.mYarn_Buf;
03923         else 
03924           startPtr = "";
03925       }
03926       
03927       const nsASingleFragmentCString& rowVal =
03928           Substring(startPtr, startPtr + yarnLength);
03929 
03930       // set up some iterators
03931       nsASingleFragmentCString::const_iterator start, end;
03932       rowVal.BeginReading(start);
03933       rowVal.EndReading(end);
03934   
03935       NS_ConvertUCS2toUTF8 utf8Value(term->text);
03936       
03937       if (term->method.Equals("is")) {
03938         if (!utf8Value.Equals(rowVal, nsCaseInsensitiveCStringComparator()))
03939           return PR_FALSE;
03940       }
03941 
03942       else if (term->method.Equals("isnot")) {
03943         if (utf8Value.Equals(rowVal, nsCaseInsensitiveCStringComparator()))
03944           return PR_FALSE;
03945       }
03946 
03947       else if (term->method.Equals("contains")) {
03948         if (!FindInReadable(utf8Value, start, end, nsCaseInsensitiveCStringComparator()))
03949           return PR_FALSE;
03950       }
03951 
03952       else if (term->method.Equals("doesntcontain")) {
03953         if (FindInReadable(utf8Value, start, end, nsCaseInsensitiveCStringComparator()))
03954           return PR_FALSE;
03955       }
03956 
03957       else if (term->method.Equals("startswith")) {
03958         // need to make sure that the found string is 
03959         // at the beginning of the string
03960         nsACString::const_iterator real_start = start;
03961         if (!(FindInReadable(utf8Value, start, end, nsCaseInsensitiveCStringComparator()) &&
03962               real_start == start))
03963           return PR_FALSE;
03964       }
03965 
03966       else if (term->method.Equals("endswith")) {
03967         // need to make sure that the found string ends
03968         // at the end of the string
03969         nsACString::const_iterator real_end = end;
03970         if (!(RFindInReadable(utf8Value, start, end, nsCaseInsensitiveCStringComparator()) &&
03971               real_end == end))
03972           return PR_FALSE;
03973       }
03974 
03975       else {
03976         NS_WARNING("Unrecognized search method in SearchEnumerator::RowMatches");
03977         // don't handle other match types like isgreater/etc yet,
03978         // so assume the match failed and bail
03979         return PR_FALSE;
03980       }
03981       
03982     }
03983   }
03984   
03985   // we've gone through each term and didn't bail, so they must have
03986   // all matched!
03987   return PR_TRUE;
03988 }
03989 
03990 // 
03991 // return either the row, or another find resource.
03992 // if we're doing grouping, then we don't want to return a real row,
03993 // instead we want to expand the current query into a deeper query
03994 // where we match up the groupby attribute.
03995 // if we're not doing grouping, then we just return the URL for the
03996 // current row
03997 nsresult
03998 nsGlobalHistory::SearchEnumerator::ConvertToISupports(nsIMdbRow* aRow,
03999                                                       nsISupports** aResult)
04000 
04001 {
04002   mdb_err err;
04003   nsresult rv;
04004   
04005   nsCOMPtr<nsIRDFResource> resource;
04006   if (mQuery->groupBy == 0) {
04007     // no column to group by
04008     // just create a resource based on the URL of the current row
04009     mdbYarn yarn;
04010     err = aRow->AliasCellYarn(mEnv, mHistory->kToken_URLColumn, &yarn);
04011     if (err != 0) return NS_ERROR_FAILURE;
04012 
04013     
04014     const char* startPtr = (const char*)yarn.mYarn_Buf;
04015     rv = gRDFService->GetResource(
04016             Substring(startPtr, startPtr+yarn.mYarn_Fill),
04017             getter_AddRefs(resource));
04018     if (NS_FAILED(rv)) return rv;
04019 
04020     *aResult = resource;
04021     NS_ADDREF(*aResult);
04022     return NS_OK;
04023   }
04024 
04025   // we have a group by, so now we recreate the find url, but add a
04026   // query for the row asked for by groupby
04027   mdbYarn groupByValue;
04028   err = aRow->AliasCellYarn(mEnv, mQuery->groupBy, &groupByValue);
04029   if (err != 0) return NS_ERROR_FAILURE;
04030 
04031   if (mFindUriPrefix.IsEmpty())
04032     mHistory->GetFindUriPrefix(*mQuery, PR_FALSE, mFindUriPrefix);
04033   
04034   nsCAutoString findUri(mFindUriPrefix);
04035 
04036   const char* startPtr = (const char *)groupByValue.mYarn_Buf;
04037   findUri.Append(Substring(startPtr, startPtr+groupByValue.mYarn_Fill));
04038   findUri.Append('\0');
04039 
04040   rv = gRDFService->GetResource(findUri, getter_AddRefs(resource));
04041   if (NS_FAILED(rv)) return rv;
04042 
04043   *aResult = resource;
04044   NS_ADDREF(*aResult);
04045   return NS_OK;
04046 }
04047 
04048 //----------------------------------------------------------------------
04049 //
04050 // nsGlobalHistory::AutoCompleteEnumerator
04051 //
04052 //   Implementation
04053 
04054 nsGlobalHistory::AutoCompleteEnumerator::~AutoCompleteEnumerator()
04055 {
04056 }
04057 
04058 
04059 PRBool
04060 nsGlobalHistory::AutoCompleteEnumerator::IsResult(nsIMdbRow* aRow)
04061 {
04062   if (!HasCell(mEnv, aRow, mTypedColumn)) {
04063     if (mMatchOnlyTyped || HasCell(mEnv, aRow, mHiddenColumn))
04064       return PR_FALSE;
04065   }
04066 
04067   nsCAutoString url;
04068   mHistory->GetRowValue(aRow, mURLColumn, url);
04069 
04070   NS_ConvertUTF8toUCS2 utf8Url(url);
04071 
04072   PRBool result = mHistory->AutoCompleteCompare(utf8Url, mSelectValue, mExclude); 
04073   
04074   return result;
04075 }
04076 
04077 nsresult
04078 nsGlobalHistory::AutoCompleteEnumerator::ConvertToISupports(nsIMdbRow* aRow, nsISupports** aResult)
04079 {
04080   nsCAutoString url;
04081   mHistory->GetRowValue(aRow, mURLColumn, url);
04082   nsAutoString comments;
04083   mHistory->GetRowValue(aRow, mCommentColumn, comments);
04084 
04085   nsCOMPtr<nsIAutoCompleteItem> newItem(do_CreateInstance(NS_AUTOCOMPLETEITEM_CONTRACTID));
04086   NS_ENSURE_TRUE(newItem, NS_ERROR_FAILURE);
04087 
04088   newItem->SetValue(NS_ConvertUTF8toUCS2(url.get()));
04089   newItem->SetParam(aRow);
04090   newItem->SetComment(comments.get());
04091 
04092   *aResult = newItem;
04093   NS_ADDREF(*aResult);
04094   return NS_OK;
04095 }
04096 
04097 //----------------------------------------------------------------------
04098 //
04099 // nsIAutoCompleteSession implementation
04100 //
04101 
04102 NS_IMETHODIMP 
04103 nsGlobalHistory::OnStartLookup(const PRUnichar *searchString,
04104                                nsIAutoCompleteResults *previousSearchResult,
04105                                nsIAutoCompleteListener *listener)
04106 {
04107   NS_ASSERTION(searchString, "searchString can't be null, fix your caller");
04108   NS_ENSURE_ARG_POINTER(listener);
04109   NS_ENSURE_STATE(gPrefBranch);
04110 
04111   NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
04112 
04113   PRBool enabled = PR_FALSE;
04114   gPrefBranch->GetBoolPref(PREF_AUTOCOMPLETE_ENABLED, &enabled);      
04115 
04116   if (!enabled || searchString[0] == 0) {
04117     listener->OnAutoComplete(nsnull, nsIAutoCompleteStatus::ignored);
04118     return NS_OK;
04119   }
04120   
04121   nsresult rv = NS_OK;
04122   nsCOMPtr<nsIAutoCompleteResults> results;
04123   results = do_CreateInstance(NS_AUTOCOMPLETERESULTS_CONTRACTID, &rv);
04124   NS_ENSURE_SUCCESS(rv, rv);
04125 
04126   AutoCompleteStatus status = nsIAutoCompleteStatus::failed;
04127 
04128   // if the search string is empty after it has had prefixes removed, then 
04129   // there is no need to proceed with the search
04130   nsAutoString cut(searchString);
04131   AutoCompleteCutPrefix(cut, nsnull);
04132   if (cut.IsEmpty()) {
04133     listener->OnAutoComplete(results, status);
04134     return NS_OK;
04135   }
04136   
04137   // pass string through filter and then determine which prefixes to exclude
04138   // when chopping prefixes off of history urls during comparison
04139   nsString filtered = AutoCompletePrefilter(nsDependentString(searchString));
04140   AutocompleteExclude exclude;
04141   AutoCompleteGetExcludeInfo(filtered, &exclude);
04142   
04143   // perform the actual search here
04144   rv = AutoCompleteSearch(filtered, &exclude, previousSearchResult, results);
04145 
04146   // describe the search results
04147   if (NS_SUCCEEDED(rv)) {
04148   
04149     results->SetSearchString(searchString);
04150     results->SetDefaultItemIndex(0);
04151   
04152     // determine if we have found any matches or not
04153     nsCOMPtr<nsISupportsArray> array;
04154     rv = results->GetItems(getter_AddRefs(array));
04155     if (NS_SUCCEEDED(rv)) {
04156       PRUint32 nbrOfItems;
04157       rv = array->Count(&nbrOfItems);
04158       if (NS_SUCCEEDED(rv)) {
04159         if (nbrOfItems >= 1) {
04160           status = nsIAutoCompleteStatus::matchFound;
04161         } else {
04162           status = nsIAutoCompleteStatus::noMatch;
04163         }
04164       }
04165     }
04166     
04167     // notify the listener
04168     listener->OnAutoComplete(results, status);
04169   }
04170   
04171   return NS_OK;
04172 }
04173 
04174 
04175 NS_IMETHODIMP
04176 nsGlobalHistory::OnStopLookup()
04177 {
04178   return NS_OK;
04179 }
04180 
04181 NS_IMETHODIMP
04182 nsGlobalHistory::OnAutoComplete(const PRUnichar *searchString,
04183                                 nsIAutoCompleteResults *previousSearchResult,
04184                                 nsIAutoCompleteListener *listener)
04185 {
04186   return NS_OK;
04187 }
04188 
04189 //----------------------------------------------------------------------
04190 //
04191 // AutoComplete stuff
04192 //
04193 
04194 nsresult
04195 nsGlobalHistory::AutoCompleteSearch(const nsAString& aSearchString,
04196                                     AutocompleteExclude* aExclude,
04197                                     nsIAutoCompleteResults* aPrevResults,
04198                                     nsIAutoCompleteResults* aResults)
04199 {
04200   // determine if we can skip searching the whole history and only search
04201   // through the previous search results
04202   PRBool searchPrevious = PR_FALSE;
04203   if (aPrevResults) {
04204     nsXPIDLString prevURL;
04205     aPrevResults->GetSearchString(getter_Copies(prevURL));
04206     // if search string begins with the previous search string, it's a go
04207     searchPrevious = StringBeginsWith(aSearchString, prevURL);
04208   }
04209     
04210   nsCOMPtr<nsISupportsArray> resultItems;
04211   nsresult rv = aResults->GetItems(getter_AddRefs(resultItems));
04212 
04213   if (searchPrevious) {
04214     // searching through the previous results...
04215     
04216     nsCOMPtr<nsISupportsArray> prevResultItems;
04217     aPrevResults->GetItems(getter_AddRefs(prevResultItems));
04218     
04219     PRUint32 count;
04220     prevResultItems->Count(&count);
04221     for (PRUint32 i = 0; i < count; ++i) {
04222       nsCOMPtr<nsIAutoCompleteItem> item;
04223       prevResultItems->GetElementAt(i, getter_AddRefs(item));
04224 
04225       // make a copy of the value because AutoCompleteCompare
04226       // is destructive
04227       nsAutoString url;
04228       item->GetValue(url);
04229       
04230       if (AutoCompleteCompare(url, aSearchString, aExclude))
04231         resultItems->AppendElement(item);
04232     }    
04233   } else {
04234     // searching through the entire history...
04235         
04236     // prepare the search enumerator
04237     AutoCompleteEnumerator* enumerator;
04238     enumerator = new AutoCompleteEnumerator(this, kToken_URLColumn, 
04239                                             kToken_NameColumn,
04240                                             kToken_HiddenColumn,
04241                                             kToken_TypedColumn,
04242                                             mAutocompleteOnlyTyped,
04243                                             aSearchString, aExclude);
04244     
04245     nsCOMPtr<nsISupports> kungFuDeathGrip(enumerator);
04246 
04247     rv = enumerator->Init(mEnv, mTable);
04248     if (NS_FAILED(rv)) return rv;
04249   
04250     // store hits in an auto array initially
04251     nsAutoVoidArray array;
04252       
04253     // not using nsCOMPtr here to avoid time spent
04254     // refcounting while passing these around between the 3 arrays
04255     nsISupports* entry; 
04256 
04257     // step through the enumerator to get the items into 'array'
04258     // because we don't know how many items there will be
04259     PRBool hasMore;
04260     while (PR_TRUE) {
04261       enumerator->HasMoreElements(&hasMore);
04262       if (!hasMore) break;
04263       
04264       // addref's each entry as it enters 'array'
04265       enumerator->GetNext(&entry);
04266       array.AppendElement(entry);
04267     }
04268 
04269     // turn auto array into flat array for quick sort, now that we
04270     // know how many items there are
04271     PRUint32 count = array.Count();
04272     nsIAutoCompleteItem** items = new nsIAutoCompleteItem*[count];
04273     PRUint32 i;
04274     for (i = 0; i < count; ++i)
04275       items[i] = (nsIAutoCompleteItem*)array.ElementAt(i);
04276     
04277     // Setup the structure we pass into the sort function,
04278     // including a set of url prefixes to ignore.   These prefixes 
04279     // must match with the logic in nsGlobalHistory::nsGlobalHistory().
04280     NS_NAMED_LITERAL_STRING(prefixHWStr, "http://www.");
04281     NS_NAMED_LITERAL_STRING(prefixHStr, "http://");
04282     NS_NAMED_LITERAL_STRING(prefixHSWStr, "https://www.");
04283     NS_NAMED_LITERAL_STRING(prefixHSStr, "https://");
04284     NS_NAMED_LITERAL_STRING(prefixFFStr, "ftp://ftp.");
04285     NS_NAMED_LITERAL_STRING(prefixFStr, "ftp://");
04286 
04287     // note: the number of prefixes stored in the closure below 
04288     // must match with the constant AUTOCOMPLETE_PREFIX_LIST_COUNT
04289     AutoCompleteSortClosure closure;
04290     closure.history = this;
04291     closure.prefixCount = AUTOCOMPLETE_PREFIX_LIST_COUNT;
04292     closure.prefixes[0] = &prefixHWStr;
04293     closure.prefixes[1] = &prefixHStr;
04294     closure.prefixes[2] = &prefixHSWStr;
04295     closure.prefixes[3] = &prefixHSStr;
04296     closure.prefixes[4] = &prefixFFStr;
04297     closure.prefixes[5] = &prefixFStr;
04298 
04299     // sort it
04300     NS_QuickSort(items, count, sizeof(nsIAutoCompleteItem*),
04301                  AutoCompleteSortComparison,
04302                  NS_STATIC_CAST(void*, &closure));
04303   
04304     // place the sorted array into the autocomplete results
04305     for (i = 0; i < count; ++i) {
04306       nsISupports* item = (nsISupports*)items[i];
04307       resultItems->AppendElement(item);
04308       NS_IF_RELEASE(item); // release manually since we didn't use nsCOMPtr above
04309     }
04310     
04311     delete[] items;
04312   }
04313     
04314   return NS_OK;
04315 }
04316 
04317 // If aURL begins with a protocol or domain prefix from our lists,
04318 // then mark their index in an AutocompleteExclude struct.
04319 void
04320 nsGlobalHistory::AutoCompleteGetExcludeInfo(const nsAString& aURL, AutocompleteExclude* aExclude)
04321 {
04322   aExclude->schemePrefix = -1;
04323   aExclude->hostnamePrefix = -1;
04324   
04325   PRInt32 index = 0;
04326   PRInt32 i;
04327   for (i = 0; i < mIgnoreSchemes.Count(); ++i) {
04328     nsString* string = mIgnoreSchemes.StringAt(i);    
04329     if (StringBeginsWith(aURL, *string)) {
04330       aExclude->schemePrefix = i;
04331       index = string->Length();
04332       break;
04333     }
04334   }
04335   
04336   for (i = 0; i < mIgnoreHostnames.Count(); ++i) {
04337     nsString* string = mIgnoreHostnames.StringAt(i);    
04338     if (Substring(aURL, index, string->Length()).Equals(*string)) {
04339       aExclude->hostnamePrefix = i;
04340       break;
04341     }
04342   }
04343 }
04344 
04345 // Cut any protocol and domain prefixes from aURL, except for those which
04346 // are specified in aExclude
04347 void
04348 nsGlobalHistory::AutoCompleteCutPrefix(nsAString& aURL, AutocompleteExclude* aExclude)
04349 {
04350   // This comparison is case-sensitive.  Therefore, it assumes that aUserURL is a 
04351   // potential URL whose host name is in all lower case.
04352   PRInt32 idx = 0;
04353   PRInt32 i;
04354   for (i = 0; i < mIgnoreSchemes.Count(); ++i) {
04355     if (aExclude && i == aExclude->schemePrefix)
04356       continue;
04357     nsString* string = mIgnoreSchemes.StringAt(i);    
04358     if (StringBeginsWith(aURL, *string)) {
04359       idx = string->Length();
04360       break;
04361     }
04362   }
04363 
04364   if (idx > 0)
04365     aURL.Cut(0, idx);
04366 
04367   idx = 0;
04368   for (i = 0; i < mIgnoreHostnames.Count(); ++i) {
04369     if (aExclude && i == aExclude->hostnamePrefix)
04370       continue;
04371     nsString* string = mIgnoreHostnames.StringAt(i);    
04372     if (StringBeginsWith(aURL, *string)) {
04373       idx = string->Length();
04374       break;
04375     }
04376   }
04377 
04378   if (idx > 0)
04379     aURL.Cut(0, idx);
04380 }
04381 
04382 nsString
04383 nsGlobalHistory::AutoCompletePrefilter(const nsAString& aSearchString)
04384 {
04385   nsAutoString url(aSearchString);
04386 
04387   PRInt32 slash = url.FindChar('/', 0);
04388   if (slash >= 0) {
04389     // if user is typing a url but has already typed past the host,
04390     // then convert the host to lowercase
04391     nsAutoString host;
04392     url.Left(host, slash);
04393     ToLowerCase(host);
04394     url.Assign(host + Substring(url, slash, url.Length()-slash));
04395   } else {
04396     // otherwise, assume the user could still be typing the host, and
04397     // convert everything to lowercase
04398     ToLowerCase(url);
04399   }
04400   
04401   return nsString(url);
04402 }
04403 
04404 PRBool
04405 nsGlobalHistory::AutoCompleteCompare(nsAString& aHistoryURL, 
04406                                      const nsAString& aUserURL, 
04407                                      AutocompleteExclude* aExclude)
04408 {
04409   AutoCompleteCutPrefix(aHistoryURL, aExclude);
04410   
04411   return StringBeginsWith(aHistoryURL, aUserURL);
04412 }
04413 
04414 int PR_CALLBACK 
04415 nsGlobalHistory::AutoCompleteSortComparison(const void *v1, const void *v2,
04416                                             void *closureVoid) 
04417 {
04418   //
04419   // NOTE: The design and reasoning behind the following autocomplete 
04420   // sort implementation is documented in bug 78270.
04421   //
04422 
04423   // cast our function parameters back into their real form
04424   nsIAutoCompleteItem *item1 = *(nsIAutoCompleteItem**) v1;
04425   nsIAutoCompleteItem *item2 = *(nsIAutoCompleteItem**) v2;
04426   AutoCompleteSortClosure* closure = 
04427       NS_STATIC_CAST(AutoCompleteSortClosure*, closureVoid);
04428 
04429   // get history rows
04430   nsCOMPtr<nsIMdbRow> row1, row2;
04431   item1->GetParam(getter_AddRefs(row1));
04432   item2->GetParam(getter_AddRefs(row2));
04433 
04434   // get visit counts - we're ignoring all errors from GetRowValue(), 
04435   // and relying on default values
04436   PRInt32 item1visits = 0, item2visits = 0;
04437   closure->history->GetRowValue(row1, 
04438                                 closure->history->kToken_VisitCountColumn, 
04439                                 &item1visits);
04440   closure->history->GetRowValue(row2, 
04441                                 closure->history->kToken_VisitCountColumn, 
04442                                 &item2visits);
04443 
04444   // get URLs
04445   nsAutoString url1, url2;
04446   item1->GetValue(url1);
04447   item2->GetValue(url2);
04448 
04449   // Favour websites and webpaths more than webpages by boosting 
04450   // their visit counts.  This assumes that URLs have been normalized, 
04451   // appending a trailing '/'.
04452   // 
04453   // We use addition to boost the visit count rather than multiplication 
04454   // since we want URLs with large visit counts to remain pretty much 
04455   // in raw visit count order - we assume the user has visited these urls
04456   // often for a reason and there shouldn't be a problem with putting them 
04457   // high in the autocomplete list regardless of whether they are sites/
04458   // paths or pages.  However for URLs visited only a few times, sites 
04459   // & paths should be presented before pages since they are generally 
04460   // more likely to be visited again.
04461   //
04462   PRBool isPath1 = PR_FALSE, isPath2 = PR_FALSE;
04463   if (!url1.IsEmpty())
04464   {
04465     // url is a site/path if it has a trailing slash
04466     isPath1 = (url1.Last() == PRUnichar('/'));
04467     if (isPath1)
04468       item1visits += AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST;
04469   }
04470   if (!url2.IsEmpty())
04471   {
04472     // url is a site/path if it has a trailing slash
04473     isPath2 = (url2.Last() == PRUnichar('/'));
04474     if (isPath2)
04475       item2visits += AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST;
04476   }
04477 
04478   // primary sort by visit count
04479   if (item1visits != item2visits)
04480   {
04481     // return visit count comparison
04482     return item2visits - item1visits;
04483   }
04484   else
04485   {
04486     // Favour websites and webpaths more than webpages
04487     if (isPath1 && !isPath2) return -1; // url1 is a website/path, url2 isn't
04488     if (!isPath1 && isPath2) return  1; // url1 isn't a website/path, url2 is
04489 
04490     // We have two websites/paths.. ignore "http[s]://[www.]" & "ftp://[ftp.]"
04491     // prefixes.  Find a starting position in the string, just past any of the 
04492     // above prefixes.  Only check for the prefix once, in the far left of the 
04493     // string - it is assumed there is no whitespace.
04494     PRInt32 postPrefix1 = 0, postPrefix2 = 0;
04495 
04496     size_t i;
04497     // iterate through our prefixes looking for a match
04498     for (i=0; i<closure->prefixCount; i++)
04499     {
04500       // Check if string is prefixed.  Note: the parameters of the Find() 
04501       // method specify the url is searched at the 0th character and if there
04502       // is no match the rest of the url is not searched.
04503       if (url1.Find((*closure->prefixes[i]), 0, 1) == 0)
04504       {
04505         // found a match - record post prefix position
04506         postPrefix1 = closure->prefixes[i]->Length();
04507         // bail out of the for loop
04508         break;
04509       }
04510     }
04511 
04512     // iterate through our prefixes looking for a match
04513     for (i=0; i<closure->prefixCount; i++)
04514     {
04515       // Check if string is prefixed.  Note: the parameters of the Find() 
04516       // method specify the url is searched at the 0th character and if there
04517       // is no match the rest of the url is not searched.
04518       if (url2.Find((*closure->prefixes[i]), 0, 1) == 0)
04519       {
04520         // found a match - record post prefix position
04521         postPrefix2 = closure->prefixes[i]->Length();
04522         // bail out of the for loop
04523         break;
04524       }
04525     }
04526 
04527     // compare non-prefixed urls
04528     PRInt32 ret = Compare(
04529       Substring(url1, postPrefix1, url1.Length()),
04530       Substring(url2, postPrefix2, url2.Length()));
04531     if (ret != 0) return ret;
04532 
04533     // sort http://xyz.com before http://www.xyz.com
04534     return postPrefix1 - postPrefix2;
04535   }
04536 }
04537