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