diff --git a/mozilla/browser/components/history/Makefile.in b/mozilla/browser/components/history/Makefile.in index e07fb7c964b..ea7298a5866 100644 --- a/mozilla/browser/components/history/Makefile.in +++ b/mozilla/browser/components/history/Makefile.in @@ -26,5 +26,7 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk +DIRS = public src + include $(topsrcdir)/config/rules.mk diff --git a/mozilla/browser/components/history/public/Makefile.in b/mozilla/browser/components/history/public/Makefile.in new file mode 100644 index 00000000000..33bee81952a --- /dev/null +++ b/mozilla/browser/components/history/public/Makefile.in @@ -0,0 +1,38 @@ +# +# The contents of this file are subject to the Netscape Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/NPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# Joe Hewitt (Original Author) +# + +DEPTH = ../../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = browsercomps +XPIDL_MODULE = history + +XPIDLSRCS = \ + nsIGlobalHistory.idl \ + nsIBrowserHistory.idl \ + $(NULL) + +include $(topsrcdir)/config/rules.mk diff --git a/mozilla/browser/components/history/public/nsIBrowserHistory.idl b/mozilla/browser/components/history/public/nsIBrowserHistory.idl new file mode 100644 index 00000000000..661f147d514 --- /dev/null +++ b/mozilla/browser/components/history/public/nsIBrowserHistory.idl @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the NPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + * browser-specific interface to global history + */ + +#include "nsISupports.idl" + +[scriptable, uuid(1a9129f7-2490-49d1-ba54-196ddb848ddb)] +interface nsIBrowserHistory : nsISupports +{ + /** + * count + * The number of entries in global history + */ + readonly attribute PRUint32 count; + + /** + * setPageTitle + * set the page title for the given url. Ignores urls that are not + * already in the history + */ + void setPageTitle(in string aURL, in wstring aTitle); + + /** + * removePage + * Remove the specified page from the global history + */ + void removePage(in string aURL); + + /** + * startBatchUpdate + * Signals the beginning of some kind of batch update (e.g. delete). + * Observers won't be notified until there are no batches in progress + * (a startBatchUpdate call should always be followed by one to + * endBatchUpdate). + */ + void startBatchUpdate(); + + /** + * endBatchUpdate + * Signals the end of a batch update. + */ + void endBatchUpdate(); + + /** + * removePagesFromHost + * Remove all pages from the given host. + * If aEntireDomain is true, will assume aHost is a domain, + * and remove all pages from the entire domain. + */ + void removePagesFromHost(in string aHost, in boolean aEntireDomain); + + /** + * removeAllPages + * Remove all pages from global history + */ + void removeAllPages(); + + /** + * lastPageVisited + * the most recently visited page + */ + readonly attribute string lastPageVisited; + + + /** + * hidePage + * Hide the specified URL from being enumerated (and thus + * displayed in the UI) + * + * if the page hasn't been visited yet, then it will be added + * as if it was visited, and then marked as hidden + */ + + void hidePage(in string url); + + /** + * markPageAsTyped + * Designate the url as having been explicitly typed in by + * the user, so it's okay to be an autocomplete result. + */ + + void markPageAsTyped(in string url); +}; diff --git a/mozilla/browser/components/history/public/nsIGlobalHistory.idl b/mozilla/browser/components/history/public/nsIGlobalHistory.idl new file mode 100644 index 00000000000..9da0848448f --- /dev/null +++ b/mozilla/browser/components/history/public/nsIGlobalHistory.idl @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the NPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * The interface to global history. + * + * @status FROZEN + * @version 1.0 + */ + +#include "nsISupports.idl" + +[scriptable, uuid(9491C383-E3C4-11d2-BDBE-0050040A9B44)] +interface nsIGlobalHistory : nsISupports +{ + /** + * addPage + * Add a page to the history + * + * @param aURL the url to the page + */ + + void addPage(in string aURL); + + /** + * isVisited + * Checks to see if the given page is in history + * + * @return true if a page has been passed into addPage(). + * @param aURL the url to the page + */ + boolean isVisited(in string aURL); +}; diff --git a/mozilla/browser/components/history/src/Makefile.in b/mozilla/browser/components/history/src/Makefile.in new file mode 100644 index 00000000000..0872c2fe2dc --- /dev/null +++ b/mozilla/browser/components/history/src/Makefile.in @@ -0,0 +1,49 @@ +# +# The contents of this file are subject to the Netscape Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/NPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is mozilla.org code. +# +# The Initial Developer of the Original Code is Netscape +# Communications Corporation. Portions created by Netscape are +# Copyright (C) 1998 Netscape Communications Corporation. All +# Rights Reserved. +# +# Contributor(s): +# Joe Hewitt (Original Author) +# + +DEPTH = ../../../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = browsercomp +LIBRARY_NAME = history_s +FORCE_STATIC_LIB = 1 + +REQUIRES = xpcom \ + string \ + rdf \ + mork \ + pref \ + necko \ + intl \ + unicharutil \ + toolkitcomps \ + $(NULL) + +CPPSRCS = nsGlobalHistory.cpp \ + $(NULL) + +include $(topsrcdir)/config/rules.mk + diff --git a/mozilla/browser/components/history/src/nsGlobalHistory.cpp b/mozilla/browser/components/history/src/nsGlobalHistory.cpp new file mode 100644 index 00000000000..504b419dc6e --- /dev/null +++ b/mozilla/browser/components/history/src/nsGlobalHistory.cpp @@ -0,0 +1,4306 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Chris Waterson + * Pierre Phaneuf + * Joe Hewitt + * Blake Ross + * Chris Sears + * Michael Lowe + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the NPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + + A global browser history implementation that also supports the RDF + datasource interface. + + TODO + + 1) Hook up Assert() etc. so that we can delete stuff. + +*/ +#include "nsNetUtil.h" +#include "nsGlobalHistory.h" +#include "nsIFileSpec.h" +#include "nsCRT.h" +#include "nsFileStream.h" +#include "nsIEnumerator.h" +#include "nsIServiceManager.h" +#include "nsEnumeratorUtils.h" +#include "nsRDFCID.h" +#include "nsIDirectoryService.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsUnicharUtils.h" +#include "nsXPIDLString.h" +#include "plhash.h" +#include "plstr.h" +#include "prprf.h" +#include "prtime.h" +#include "rdf.h" +#include "nsQuickSort.h" +#include "nsIIOService.h" +#include "nsILocalFile.h" + +#include "nsIURL.h" +#include "nsNetCID.h" + +#include "nsInt64.h" +#include "nsMorkCID.h" +#include "nsIMdbFactoryFactory.h" + +#include "nsIPrefService.h" +#include "nsIPrefBranchInternal.h" + +#include "nsIObserverService.h" + +PRInt32 nsGlobalHistory::gRefCnt; +nsIRDFService* nsGlobalHistory::gRDFService; +nsIRDFResource* nsGlobalHistory::kNC_Page; +nsIRDFResource* nsGlobalHistory::kNC_Date; +nsIRDFResource* nsGlobalHistory::kNC_FirstVisitDate; +nsIRDFResource* nsGlobalHistory::kNC_VisitCount; +nsIRDFResource* nsGlobalHistory::kNC_AgeInDays; +nsIRDFResource* nsGlobalHistory::kNC_Name; +nsIRDFResource* nsGlobalHistory::kNC_NameSort; +nsIRDFResource* nsGlobalHistory::kNC_Hostname; +nsIRDFResource* nsGlobalHistory::kNC_Referrer; +nsIRDFResource* nsGlobalHistory::kNC_child; +nsIRDFResource* nsGlobalHistory::kNC_URL; +nsIRDFResource* nsGlobalHistory::kNC_HistoryRoot; +nsIRDFResource* nsGlobalHistory::kNC_HistoryByDate; +nsIMdbFactory* nsGlobalHistory::gMdbFactory = nsnull; +nsIPrefBranch* nsGlobalHistory::gPrefBranch = nsnull; + +#define PREF_BRANCH_BASE "browser." +#define PREF_BROWSER_HISTORY_EXPIRE_DAYS "history_expire_days" +#define PREF_BROWSER_STARTUP_PAGE "startup.page" +#define PREF_AUTOCOMPLETE_ONLY_TYPED "urlbar.matchOnlyTyped" +#define PREF_AUTOCOMPLETE_ENABLED "urlbar.autocomplete.enabled" + +#define FIND_BY_AGEINDAYS_PREFIX "find:datasource=history&match=AgeInDays&method=" + +// sync history every 10 seconds +#define HISTORY_SYNC_TIMEOUT (10 * PR_MSEC_PER_SEC) +//#define HISTORY_SYNC_TIMEOUT 3000 // every 3 seconds - testing only! + +// the value of mLastNow expires every 3 seconds +#define HISTORY_EXPIRE_NOW_TIMEOUT (3 * PR_MSEC_PER_SEC) + +#define MSECS_PER_DAY (PR_MSEC_PER_SEC * 60 * 60 * 24) + +//---------------------------------------------------------------------- +// +// CIDs + +static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); +static NS_DEFINE_CID(kStringBundleServiceCID, NS_STRINGBUNDLESERVICE_CID); + +// closure structures for RemoveMatchingRows +struct matchExpiration_t { + PRInt64 *expirationDate; + nsGlobalHistory *history; +}; + +struct matchHost_t { + const char *host; + PRBool entireDomain; // should we delete the entire domain? + nsGlobalHistory *history; +}; + +struct matchSearchTerm_t { + nsIMdbEnv *env; + nsIMdbStore *store; + + searchTerm *term; + PRBool haveClosure; // are the rest of the fields valid? + PRInt64 now; + PRInt32 intValue; +}; + +struct matchQuery_t { + searchQuery* query; + nsGlobalHistory* history; +}; + +// simple token/value struct +class tokenPair { +public: + tokenPair(const char *aName, PRUint32 aNameLen, + const char *aValue, PRUint32 aValueLen) : + tokenName(aName), tokenNameLength(aNameLen), + tokenValue(aValue), tokenValueLength(aValueLen) { MOZ_COUNT_CTOR(tokenPair); } + ~tokenPair() { MOZ_COUNT_DTOR(tokenPair); } + const char* tokenName; + PRUint32 tokenNameLength; + const char* tokenValue; + PRUint32 tokenValueLength; +}; + +// individual search term, pulled from token/value structs +class searchTerm { +public: + searchTerm(const char* aDatasource, PRUint32 aDatasourceLen, + const char *aProperty, PRUint32 aPropertyLen, + const char* aMethod, PRUint32 aMethodLen, + const char* aText, PRUint32 aTextLen): + datasource(aDatasource, aDatasource+aDatasourceLen), + property(aProperty, aProperty+aPropertyLen), + method(aMethod, aMethod+aMethodLen) + { + MOZ_COUNT_CTOR(searchTerm); + // need to do UTF8-conversion/unescaping here, using + // nsITextToSubURI + text.AssignWithConversion(aText, aTextLen); + } + ~searchTerm() { + MOZ_COUNT_DTOR(searchTerm); + } + + nsDependentSingleFragmentCSubstring datasource; // should always be "history" ? + nsDependentSingleFragmentCSubstring property; // AgeInDays, Hostname, etc + nsDependentSingleFragmentCSubstring method; // is, isgreater, isless + nsAutoString text; // text to match + rowMatchCallback match; // matching callback if needed +}; + +// list of terms, plus an optional groupby column +struct searchQuery { + nsVoidArray terms; // array of searchTerms + mdb_column groupBy; // column to group by +}; + +static nsresult +PRInt64ToChars(const PRInt64& aValue, nsACString& aResult) +{ + // Convert an unsigned 64-bit value to a string of up to aSize + // decimal digits, placed in aBuf. + nsInt64 value(aValue); + + aResult.Truncate(0); + + if (value == nsInt64(0)) { + aResult.Append('0'); + } + + while (value != nsInt64(0)) { + PRInt32 ones = PRInt32(value % nsInt64(10)); + value /= nsInt64(10); + + if (ones <=9) + aResult.Insert(char('0' + ones), 0); + } + + return NS_OK; +} + +static PRBool HasCell(nsIMdbEnv *aEnv, nsIMdbRow* aRow, mdb_column aCol) +{ + mdbYarn yarn; + mdb_err err = aRow->AliasCellYarn(aEnv, aCol, &yarn); + + // no cell + if (err != 0) + return PR_FALSE; + + // if we have the cell, make sure it has a value?? + return (yarn.mYarn_Fill != 0); +} + + +//---------------------------------------------------------------------- + +static nsresult +CharsToPRInt64(const char* aBuf, PRUint32 aCount, PRInt64* aResult) +{ + // Convert aBuf of exactly aCount decimal characters to a 64-bit + // unsigned integer value. + nsInt64 result(0); + + while (aCount-- > 0) { + PRInt32 digit = (*aBuf++) - '0'; + result *= nsInt64(10); + result += nsInt64(digit); + } + + *aResult = result; + return NS_OK; +} + +static PRTime +NormalizeTime(PRInt64 aTime) +{ + // normalize both now and date to midnight of the day they occur on + PRExplodedTime explodedTime; + PR_ExplodeTime(aTime, PR_LocalTimeParameters, &explodedTime); + + // set to midnight (0:00) + explodedTime.tm_min = + explodedTime.tm_hour = + explodedTime.tm_sec = + explodedTime.tm_usec = 0; + + return PR_ImplodeTime(&explodedTime); +} + +// pass in a pre-normalized now and a date, and we'll find +// the difference since midnight on each of the days.. +static PRInt32 +GetAgeInDays(PRInt64 aNormalizedNow, PRInt64 aDate) +{ + PRInt64 dateMidnight = NormalizeTime(aDate); + + PRInt64 diff; + LL_SUB(diff, aNormalizedNow, dateMidnight); + + // two-step process since I can't seem to load + // MSECS_PER_DAY * PR_MSEC_PER_SEC into a PRInt64 at compile time + PRInt64 msecPerSec; + LL_I2L(msecPerSec, PR_MSEC_PER_SEC); + PRInt64 ageInSeconds; + LL_DIV(ageInSeconds, diff, msecPerSec); + + PRInt32 ageSec; LL_L2I(ageSec, ageInSeconds); + + PRInt64 msecPerDay; + LL_I2L(msecPerDay, MSECS_PER_DAY); + + PRInt64 ageInDays; + LL_DIV(ageInDays, ageInSeconds, msecPerDay); + + PRInt32 retval; + LL_L2I(retval, ageInDays); + return retval; +} + + +PRBool +nsGlobalHistory::MatchExpiration(nsIMdbRow *row, PRInt64* expirationDate) +{ + nsresult rv; + + // hidden and typed urls always match because they're invalid, + // so we want to expire them asap. (if they were valid, they'd + // have been unhidden -- see AddExistingPageToDatabase) + if (HasCell(mEnv, row, kToken_HiddenColumn) && HasCell(mEnv, row, kToken_TypedColumn)) + return PR_TRUE; + + PRInt64 lastVisitedTime; + rv = GetRowValue(row, kToken_LastVisitDateColumn, &lastVisitedTime); + + if (NS_FAILED(rv)) + return PR_FALSE; + + return LL_CMP(lastVisitedTime, <, *expirationDate); +} + +static PRBool +matchAgeInDaysCallback(nsIMdbRow *row, void *aClosure) +{ + matchSearchTerm_t *matchSearchTerm = (matchSearchTerm_t*)aClosure; + const searchTerm *term = matchSearchTerm->term; + nsIMdbEnv *env = matchSearchTerm->env; + nsIMdbStore *store = matchSearchTerm->store; + + // fill in the rest of the closure if it's not filled in yet + // this saves us from recalculating this stuff on every row + if (!matchSearchTerm->haveClosure) { + PRInt32 err; + matchSearchTerm->intValue = term->text.ToInteger(&err); + matchSearchTerm->now = NormalizeTime(PR_Now()); + if (err != 0) return PR_FALSE; + matchSearchTerm->haveClosure = PR_TRUE; + } + + // XXX convert the property to a column, get the column value + + PRInt64 rowDate; + mdb_column column; + mdb_err err = store->StringToToken(env, "LastVisitDate", &column); + if (err != 0) return PR_FALSE; + + mdbYarn yarn; + err = row->AliasCellYarn(env, column, &yarn); + if (err != 0) return PR_FALSE; + + CharsToPRInt64((const char*)yarn.mYarn_Buf, yarn.mYarn_Fill, &rowDate); + + PRInt32 days = GetAgeInDays(matchSearchTerm->now, rowDate); + + if (term->method.Equals("is")) + return (days == matchSearchTerm->intValue); + else if (term->method.Equals("isgreater")) + return (days > matchSearchTerm->intValue); + else if (term->method.Equals("isless")) + return (days < matchSearchTerm->intValue); + + return PR_FALSE; +} + +static PRBool +matchExpirationCallback(nsIMdbRow *row, void *aClosure) +{ + matchExpiration_t *expires = (matchExpiration_t*)aClosure; + return expires->history->MatchExpiration(row, expires->expirationDate); +} + +static PRBool +matchAllCallback(nsIMdbRow *row, void *aClosure) +{ + return PR_TRUE; +} + +static PRBool +matchHostCallback(nsIMdbRow *row, void *aClosure) +{ + matchHost_t *hostInfo = (matchHost_t*)aClosure; + return hostInfo->history->MatchHost(row, hostInfo); +} + +static PRBool +matchQueryCallback(nsIMdbRow *row, void *aClosure) +{ + matchQuery_t *query = (matchQuery_t*)aClosure; + return query->history->RowMatches(row, query->query); +} +//---------------------------------------------------------------------- + +nsMdbTableEnumerator::nsMdbTableEnumerator() + : mEnv(nsnull), + mTable(nsnull), + mCursor(nsnull), + mCurrent(nsnull) +{ + NS_INIT_ISUPPORTS(); +} + + +nsresult +nsMdbTableEnumerator::Init(nsIMdbEnv* aEnv, + nsIMdbTable* aTable) +{ + NS_PRECONDITION(aEnv != nsnull, "null ptr"); + if (! aEnv) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTable != nsnull, "null ptr"); + if (! aTable) + return NS_ERROR_NULL_POINTER; + + mEnv = aEnv; + NS_ADDREF(mEnv); + + mTable = aTable; + NS_ADDREF(mTable); + + mdb_err err; + err = mTable->GetTableRowCursor(mEnv, -1, &mCursor); + if (err != 0) return NS_ERROR_FAILURE; + + return NS_OK; +} + + +nsMdbTableEnumerator::~nsMdbTableEnumerator() +{ + NS_IF_RELEASE(mCurrent); + + NS_IF_RELEASE(mCursor); + + NS_IF_RELEASE(mTable); + + NS_IF_RELEASE(mEnv); +} + + +NS_IMPL_ISUPPORTS1(nsMdbTableEnumerator, nsISimpleEnumerator); + +NS_IMETHODIMP +nsMdbTableEnumerator::HasMoreElements(PRBool* _result) +{ + if (! mCurrent) { + mdb_err err; + + while (1) { + mdb_pos pos; + err = mCursor->NextRow(mEnv, &mCurrent, &pos); + if (err != 0) return NS_ERROR_FAILURE; + + // If there are no more rows, then bail. + if (! mCurrent) + break; + + // If this is a result, the stop. + if (IsResult(mCurrent)) + break; + + // Otherwise, drop the ref to the row we retrieved, and continue + // on to the next one. + NS_RELEASE(mCurrent); + mCurrent = nsnull; + } + } + + *_result = (mCurrent != nsnull); + return NS_OK; +} + + +NS_IMETHODIMP +nsMdbTableEnumerator::GetNext(nsISupports** _result) +{ + nsresult rv; + + PRBool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + if (! hasMore) + return NS_ERROR_UNEXPECTED; + + rv = ConvertToISupports(mCurrent, _result); + + NS_RELEASE(mCurrent); + mCurrent = nsnull; + + return rv; +} + + +//---------------------------------------------------------------------- +// +// nsGlobalHistory +// +// ctor dtor etc. +// + + +nsGlobalHistory::nsGlobalHistory() + : mExpireDays(9), // make default be nine days + mAutocompleteOnlyTyped(PR_FALSE), + mBatchesInProgress(0), + mNowValid(PR_FALSE), + mDirty(PR_FALSE), + mEnv(nsnull), + mStore(nsnull), + mTable(nsnull) +{ + NS_INIT_ISUPPORTS(); + LL_I2L(mFileSizeOnDisk, 0); + + // commonly used prefixes that should be chopped off all + // history and input urls before comparison + + mIgnoreSchemes.AppendString(NS_LITERAL_STRING("http://")); + mIgnoreSchemes.AppendString(NS_LITERAL_STRING("https://")); + mIgnoreSchemes.AppendString(NS_LITERAL_STRING("ftp://")); + mIgnoreHostnames.AppendString(NS_LITERAL_STRING("www.")); + mIgnoreHostnames.AppendString(NS_LITERAL_STRING("ftp.")); +} + +nsGlobalHistory::~nsGlobalHistory() +{ + gRDFService->UnregisterDataSource(this); + + nsresult rv; + rv = CloseDB(); + + NS_IF_RELEASE(mTable); + NS_IF_RELEASE(mStore); + + if (--gRefCnt == 0) { + if (gRDFService) { + nsServiceManager::ReleaseService(kRDFServiceCID, gRDFService); + gRDFService = nsnull; + } + + NS_IF_RELEASE(kNC_Page); + NS_IF_RELEASE(kNC_Date); + NS_IF_RELEASE(kNC_FirstVisitDate); + NS_IF_RELEASE(kNC_VisitCount); + NS_IF_RELEASE(kNC_AgeInDays); + NS_IF_RELEASE(kNC_Name); + NS_IF_RELEASE(kNC_NameSort); + NS_IF_RELEASE(kNC_Hostname); + NS_IF_RELEASE(kNC_Referrer); + NS_IF_RELEASE(kNC_child); + NS_IF_RELEASE(kNC_URL); + NS_IF_RELEASE(kNC_HistoryRoot); + NS_IF_RELEASE(kNC_HistoryByDate); + + NS_IF_RELEASE(gMdbFactory); + NS_IF_RELEASE(gPrefBranch); + } + + NS_IF_RELEASE(mEnv); + if (mSyncTimer) + mSyncTimer->Cancel(); + + if (mExpireNowTimer) + mExpireNowTimer->Cancel(); + +} + + + +//---------------------------------------------------------------------- +// +// nsGlobalHistory +// +// nsISupports methods + +NS_IMPL_ISUPPORTS7(nsGlobalHistory, + nsIGlobalHistory, + nsIBrowserHistory, + nsIObserver, + nsISupportsWeakReference, + nsIRDFDataSource, + nsIRDFRemoteDataSource, + nsIAutoCompleteSearch) + +//---------------------------------------------------------------------- +// +// nsGlobalHistory +// +// nsIGlobalHistory methods +// + + +NS_IMETHODIMP +nsGlobalHistory::AddPage(const char *aURL) +{ + // If history is set to expire after 0 days, + // then it's technically disabled. Don't even + // bother adding the page + if (mExpireDays == 0) + return NS_OK; + + NS_ENSURE_ARG_POINTER(aURL); + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + + nsresult rv = AddPageToDatabase(aURL, GetNow()); + NS_ENSURE_SUCCESS(rv, rv); + + if (gPrefBranch) { + PRInt32 choice = 0; + gPrefBranch->GetIntPref(PREF_BROWSER_STARTUP_PAGE, &choice); + if (choice == 2) { + rv = SaveLastPageVisited(aURL); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +nsresult +nsGlobalHistory::AddPageToDatabase(const char *aURL, + PRInt64 aDate) +{ + nsresult rv; + + // Sanity check the URL + PRInt32 len = PL_strlen(aURL); + NS_ASSERTION(len != 0, "no URL"); + if (! len) + return NS_ERROR_INVALID_ARG; + + // For notifying observers, later... + nsCOMPtr url; + rv = gRDFService->GetResource(aURL, getter_AddRefs(url)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr date; + rv = gRDFService->GetDateLiteral(aDate, getter_AddRefs(date)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, aURL, getter_AddRefs(row)); + + if (NS_SUCCEEDED(rv)) { + + // update the database, and get the old info back + PRInt64 oldDate; + PRInt32 oldCount; + rv = AddExistingPageToDatabase(row, aDate, &oldDate, &oldCount); + NS_ASSERTION(NS_SUCCEEDED(rv), "AddExistingPageToDatabase failed; see bug 88961"); + if (NS_FAILED(rv)) return rv; + + // Notify observers + + // visit date + nsCOMPtr oldDateLiteral; + rv = gRDFService->GetDateLiteral(oldDate, getter_AddRefs(oldDateLiteral)); + if (NS_FAILED(rv)) return rv; + + rv = NotifyChange(url, kNC_Date, oldDateLiteral, date); + if (NS_FAILED(rv)) return rv; + + // visit count + nsCOMPtr oldCountLiteral; + rv = gRDFService->GetIntLiteral(oldCount, getter_AddRefs(oldCountLiteral)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr newCountLiteral; + rv = gRDFService->GetIntLiteral(oldCount+1, + getter_AddRefs(newCountLiteral)); + if (NS_FAILED(rv)) return rv; + + rv = NotifyChange(url, kNC_VisitCount, oldCountLiteral, newCountLiteral); + if (NS_FAILED(rv)) return rv; + + } + else { + rv = AddNewPageToDatabase(aURL, aDate, getter_AddRefs(row)); + NS_ASSERTION(NS_SUCCEEDED(rv), "AddNewPageToDatabase failed; see bug 88961"); + if (NS_FAILED(rv)) return rv; + + // Notify observers + rv = NotifyAssert(url, kNC_Date, date); + if (NS_FAILED(rv)) return rv; + + rv = NotifyAssert(kNC_HistoryRoot, kNC_child, url); + if (NS_FAILED(rv)) return rv; + + NotifyFindAssertions(url, row); + } + + SetDirty(); + + return rv; +} + +nsresult +nsGlobalHistory::AddExistingPageToDatabase(nsIMdbRow *row, + PRInt64 aDate, + PRInt64 *aOldDate, + PRInt32 *aOldCount) +{ + + nsresult rv; + + // if the page was typed, unhide it now because it's + // known to be valid + if (HasCell(mEnv, row, kToken_TypedColumn)) + row->CutColumn(mEnv, kToken_HiddenColumn); + + // Update last visit date. + // First get the old date so we can update observers... + rv = GetRowValue(row, kToken_LastVisitDateColumn, aOldDate); + if (NS_FAILED(rv)) return rv; + + // get the old count, so we can update it + rv = GetRowValue(row, kToken_VisitCountColumn, aOldCount); + if (NS_FAILED(rv) || *aOldCount < 1) + *aOldCount = 1; // assume we've visited at least once + + // ...now set the new date. + SetRowValue(row, kToken_LastVisitDateColumn, aDate); + SetRowValue(row, kToken_VisitCountColumn, (*aOldCount) + 1); + + return NS_OK; +} + +nsresult +nsGlobalHistory::AddNewPageToDatabase(const char *aURL, + PRInt64 aDate, + nsIMdbRow **aResult) +{ + mdb_err err; + + // Create a new row + mdbOid rowId; + rowId.mOid_Scope = kToken_HistoryRowScope; + rowId.mOid_Id = mdb_id(-1); + + NS_PRECONDITION(mTable != nsnull, "not initialized"); + if (! mTable) + return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr row; + err = mTable->NewRow(mEnv, &rowId, getter_AddRefs(row)); + if (err != 0) return NS_ERROR_FAILURE; + + // Set the URL + SetRowValue(row, kToken_URLColumn, aURL); + + // Set the date. + SetRowValue(row, kToken_LastVisitDateColumn, aDate); + SetRowValue(row, kToken_FirstVisitDateColumn, aDate); + + nsCOMPtr uri; + NS_NewURI(getter_AddRefs(uri), nsDependentCString(aURL), nsnull, nsnull); + nsCAutoString hostname; + if (uri) + uri->GetHost(hostname); + + SetRowValue(row, kToken_HostnameColumn, hostname.get()); + + *aResult = row; + NS_ADDREF(*aResult); + + return NS_OK; +} + +nsresult +nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRInt64& aValue) +{ + mdb_err err; + nsCAutoString val; + PRInt64ToChars(aValue, val); + + mdbYarn yarn = { (void *)val.get(), val.Length(), val.Length(), 0, 0, nsnull }; + + err = aRow->AddColumn(mEnv, aCol, &yarn); + + if ( err != 0 ) return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, + const PRUnichar* aValue) +{ + mdb_err err; + + PRInt32 len = (nsCRT::strlen(aValue) * sizeof(PRUnichar)); + + // eventually turn this on when we're confident in mork's abilitiy + // to handle yarn forms properly +#if 0 + NS_ConvertUCS2toUTF8 utf8Value(aValue); + printf("Storing utf8 value %s\n", utf8Value.get()); + mdbYarn yarn = { (void *)utf8Value.get(), utf8Value.Length(), utf8Value.Length(), 0, 1, nsnull }; +#else + + mdbYarn yarn = { (void *)aValue, len, len, 0, 0, nsnull }; + +#endif + err = aRow->AddColumn(mEnv, aCol, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + return NS_OK; +} + +nsresult +nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, + const char* aValue) +{ + mdb_err err; + PRInt32 len = PL_strlen(aValue); + mdbYarn yarn = { (void*) aValue, len, len, 0, 0, nsnull }; + err = aRow->AddColumn(mEnv, aCol, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRInt32 aValue) +{ + mdb_err err; + + nsCAutoString buf; buf.AppendInt(aValue); + mdbYarn yarn = { (void *)buf.get(), buf.Length(), buf.Length(), 0, 0, nsnull }; + + err = aRow->AddColumn(mEnv, aCol, &yarn); + + if (err != 0) return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol, + nsAString& aResult) +{ + mdb_err err; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, aCol, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + aResult.Truncate(0); + if (!yarn.mYarn_Fill) + return NS_OK; + + switch (yarn.mYarn_Form) { + case 0: // unicode + aResult.Assign((const PRUnichar *)yarn.mYarn_Buf, yarn.mYarn_Fill/sizeof(PRUnichar)); + break; + + // eventually we'll be supporting this in SetRowValue() + case 1: // UTF8 + aResult.Assign(NS_ConvertUTF8toUCS2((const char*)yarn.mYarn_Buf, yarn.mYarn_Fill)); + break; + + default: + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult +nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol, + PRInt64 *aResult) +{ + mdb_err err; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, aCol, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + *aResult = LL_ZERO; + + if (!yarn.mYarn_Fill || !yarn.mYarn_Buf) + return NS_OK; + + return CharsToPRInt64((char *)yarn.mYarn_Buf, yarn.mYarn_Fill, aResult); +} + +nsresult +nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol, + PRInt32 *aResult) +{ + mdb_err err; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, aCol, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + if (yarn.mYarn_Buf) + *aResult = atoi((char *)yarn.mYarn_Buf); + else + *aResult = 0; + + return NS_OK; +} + +nsresult +nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol, + nsACString& aResult) +{ + mdb_err err; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, aCol, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + const char* startPtr = (const char*)yarn.mYarn_Buf; + if (startPtr) + aResult.Assign(Substring(startPtr, startPtr + yarn.mYarn_Fill)); + else + aResult.Truncate(); + + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::GetCount(PRUint32* aCount) +{ + NS_ENSURE_ARG_POINTER(aCount); + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + if (!mTable) return NS_ERROR_FAILURE; + + mdb_err err = mTable->GetCount(mEnv, aCount); + return (err == 0) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsGlobalHistory::SetPageTitle(const char *aURL, const PRUnichar *aTitle) +{ + NS_PRECONDITION(aURL != nsnull, "null ptr"); + if (! aURL) + return NS_ERROR_NULL_POINTER; + + // avoid this one well-known url since we can avoid + // reading in the db + if (PL_strcmp(aURL, "about:blank")==0) + return NS_OK; + + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + + nsresult rv; + + // Be defensive if somebody sends us a null title. + static PRUnichar kEmptyString[] = { 0 }; + if (! aTitle) + aTitle = kEmptyString; + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, aURL, getter_AddRefs(row)); + if (NS_FAILED(rv)) + return rv; + + // Get the old title so we can notify observers + nsAutoString oldtitle; + rv = GetRowValue(row, kToken_NameColumn, oldtitle); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr oldname; + if (!oldtitle.IsEmpty()) { + rv = gRDFService->GetLiteral(oldtitle.get(), getter_AddRefs(oldname)); + if (NS_FAILED(rv)) return rv; + } + + SetRowValue(row, kToken_NameColumn, aTitle); + + // ...and update observers + nsCOMPtr url; + rv = gRDFService->GetResource(aURL, getter_AddRefs(url)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr name; + rv = gRDFService->GetLiteral(aTitle, getter_AddRefs(name)); + if (NS_FAILED(rv)) return rv; + + if (oldname) { + rv = NotifyChange(url, kNC_Name, oldname, name); + } + else { + rv = NotifyAssert(url, kNC_Name, name); + } + + return rv; +} + + +NS_IMETHODIMP +nsGlobalHistory::RemovePage(const char *aURL) +{ + mdb_err err; + nsresult rv; + + if (!mTable) return NS_ERROR_NOT_INITIALIZED; + // find the old row, ignore it if we don't have it + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, aURL, getter_AddRefs(row)); + if (NS_FAILED(rv)) return NS_OK; + + // remove the row + err = mTable->CutRow(mEnv, row); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + + // if there are batches in progress, we don't want to notify + // observers that we're deleting items. the caller promises + // to handle whatever UI updating is necessary when we're finished. + if (!mBatchesInProgress) { + // get the resource so we can do the notification + nsCOMPtr oldRowResource; + gRDFService->GetResource(aURL, getter_AddRefs(oldRowResource)); + NotifyFindUnassertions(oldRowResource, row); + } + + // not a fatal error if we can't cut all column + err = row->CutAllColumns(mEnv); + NS_ASSERTION(err == 0, "couldn't cut all columns"); + + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::RemovePagesFromHost(const char *aHost, PRBool aEntireDomain) +{ + matchHost_t hostInfo; + hostInfo.history = this; + hostInfo.entireDomain = aEntireDomain; + hostInfo.host = aHost; + + return RemoveMatchingRows(matchHostCallback, (void *)&hostInfo, PR_TRUE); +} + +PRBool +nsGlobalHistory::MatchHost(nsIMdbRow *aRow, + matchHost_t *hostInfo) +{ + mdb_err err; + nsresult rv; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, kToken_URLColumn, &yarn); + if (err != 0) return PR_FALSE; + + nsCOMPtr uri; + // do smart zero-termination + const char* startPtr = (const char *)yarn.mYarn_Buf; + rv = NS_NewURI(getter_AddRefs(uri), + Substring(startPtr, startPtr + yarn.mYarn_Fill)); + if (NS_FAILED(rv)) return PR_FALSE; + + nsCAutoString urlHost; + rv = uri->GetHost(urlHost); + if (NS_FAILED(rv)) return PR_FALSE; + + if (PL_strcmp(urlHost.get(), hostInfo->host) == 0) + return PR_TRUE; + + // now try for a domain match, if necessary + if (hostInfo->entireDomain) { + // do a reverse-search to match the end of the string + char *domain = PL_strrstr(urlHost.get(), hostInfo->host); + + // now verify that we're matching EXACTLY the domain, and + // not some random string inside the hostname + if (domain && (PL_strcmp(domain, hostInfo->host) == 0)) + return PR_TRUE; + } + + return PR_FALSE; +} + +NS_IMETHODIMP +nsGlobalHistory::RemoveAllPages() +{ + nsresult rv; + + rv = RemoveMatchingRows(matchAllCallback, nsnull, PR_TRUE); + if (NS_FAILED(rv)) return rv; + + return Commit(kCompressCommit); +} + +nsresult +nsGlobalHistory::RemoveMatchingRows(rowMatchCallback aMatchFunc, + void *aClosure, + PRBool notify) +{ + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + nsresult rv; + if (!mTable) return NS_OK; + + mdb_err err; + mdb_count count; + err = mTable->GetCount(mEnv, &count); + if (err != 0) return NS_ERROR_FAILURE; + + StartBatchUpdate(); + + // Begin the batch. + int marker; + err = mTable->StartBatchChangeHint(mEnv, &marker); + NS_ASSERTION(err == 0, "unable to start batch"); + if (err != 0) return NS_ERROR_FAILURE; + + nsCOMPtr resource; + // XXX from here until end batch, no early returns! + for (mdb_pos pos = count - 1; pos >= 0; --pos) { + nsCOMPtr row; + err = mTable->PosToRow(mEnv, pos, getter_AddRefs(row)); + NS_ASSERTION(err == 0, "unable to get row"); + if (err != 0) + break; + + NS_ASSERTION(row != nsnull, "no row"); + if (! row) + continue; + + // now we actually do the match. If this row doesn't match, loop again + if (!(aMatchFunc)(row, aClosure)) + continue; + + if (notify) { + // What's the URL? We need to know to properly notify our RDF + // observers. + mdbYarn yarn; + err = row->AliasCellYarn(mEnv, kToken_URLColumn, &yarn); + if (err != 0) + continue; + + const char* startPtr = (const char*) yarn.mYarn_Buf; + nsCAutoString uri(Substring(startPtr, startPtr+yarn.mYarn_Fill)); + rv = gRDFService->GetResource(uri.get(), getter_AddRefs(resource)); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get resource"); + if (NS_FAILED(rv)) + continue; + } + // Officially cut the row *now*, before notifying any observers: + // that way, any re-entrant calls won't find the row. + err = mTable->CutRow(mEnv, row); + NS_ASSERTION(err == 0, "couldn't cut row"); + if (err != 0) + continue; + + // possibly avoid leakage + err = row->CutAllColumns(mEnv); + NS_ASSERTION(err == 0, "couldn't cut all columns"); + // we'll notify regardless of whether we could successfully + // CutAllColumns or not. + + + } + + // Finish the batch. + err = mTable->EndBatchChangeHint(mEnv, &marker); + NS_ASSERTION(err == 0, "error ending batch"); + + EndBatchUpdate(); + + return ( err == 0) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsGlobalHistory::IsVisited(const char *aURL, PRBool *_retval) +{ + NS_PRECONDITION(aURL != nsnull, "null ptr"); + if (! aURL) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, aURL, getter_AddRefs(row)); + + *_retval = NS_SUCCEEDED(rv); + + return NS_OK; +} + +nsresult +nsGlobalHistory::SaveLastPageVisited(const char *aURL) +{ + NS_ENSURE_TRUE(aURL, NS_ERROR_FAILURE); + NS_ENSURE_STATE(mMetaRow); + + mdb_err err = SetRowValue(mMetaRow, kToken_LastPageVisited, aURL); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::GetLastPageVisited(char **_retval) +{ + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + + NS_ENSURE_ARG_POINTER(_retval); + NS_ENSURE_STATE(mMetaRow); + + + nsCAutoString lastPageVisited; + mdb_err err = GetRowValue(mMetaRow, kToken_LastPageVisited, lastPageVisited); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + + *_retval = ToNewCString(lastPageVisited); + NS_ENSURE_TRUE(*_retval, NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::HidePage(const char *aURL) +{ + nsresult rv; + + nsCOMPtr row; + + rv = FindRow(kToken_URLColumn, aURL, getter_AddRefs(row)); + + if (NS_FAILED(rv)) { + // it hasn't been visited yet, but if one ever comes in, we need + // to hide it when it is visited + rv = AddPage(aURL); + if (NS_FAILED(rv)) return rv; + + rv = FindRow(kToken_URLColumn, aURL, getter_AddRefs(row)); + if (NS_FAILED(rv)) return rv; + } + + rv = SetRowValue(row, kToken_HiddenColumn, 1); + if (NS_FAILED(rv)) return rv; + + // now pretend as if this row was deleted + // HasAssertion() correctly checks the Hidden column to show that + // the row is hidden + nsCOMPtr urlResource; + rv = gRDFService->GetResource(aURL, getter_AddRefs(urlResource)); + if (NS_FAILED(rv)) return rv; + return NotifyFindUnassertions(urlResource, row); +} + +NS_IMETHODIMP +nsGlobalHistory::MarkPageAsTyped(const char* aURL) +{ + nsCOMPtr row; + nsresult rv = FindRow(kToken_URLColumn, aURL, getter_AddRefs(row)); + if (NS_FAILED(rv)) { + rv = AddPage(aURL); + if (NS_FAILED(rv)) return rv; + + rv = FindRow(kToken_URLColumn, aURL, getter_AddRefs(row)); + if (NS_FAILED(rv)) return rv; + } + + // hide the page for now in case the url turns out to be invalid + // we'll unhide it in AddExistingPageToDatabase + rv = SetRowValue(row, kToken_HiddenColumn, 1); + if (NS_FAILED(rv)) return rv; + + return SetRowValue(row, kToken_TypedColumn, 1); +} + + +//---------------------------------------------------------------------- +// +// nsGlobalHistory +// +// nsIRDFDataSource methods + +NS_IMETHODIMP +nsGlobalHistory::GetURI(char* *aURI) +{ + NS_PRECONDITION(aURI != nsnull, "null ptr"); + if (! aURI) + return NS_ERROR_NULL_POINTER; + + *aURI = nsCRT::strdup("rdf:history"); + if (! *aURI) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + + +NS_IMETHODIMP +nsGlobalHistory::GetSource(nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + PRBool aTruthValue, + nsIRDFResource** aSource) +{ + NS_PRECONDITION(aProperty != nsnull, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nsnull, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + *aSource = nsnull; + + if (aProperty == kNC_URL) { + // See if we have the row... + + // XXX We could be more forgiving here, and check for literal + // values as well. + nsCOMPtr target = do_QueryInterface(aTarget); + if (IsURLInHistory(target)) + return CallQueryInterface(aTarget, aSource); + + } + else if ((aProperty == kNC_Date) || + (aProperty == kNC_FirstVisitDate) || + (aProperty == kNC_VisitCount) || + (aProperty == kNC_Name) || + (aProperty == kNC_Hostname) || + (aProperty == kNC_Referrer)) { + // Call GetSources() and return the first one we find. + nsCOMPtr sources; + rv = GetSources(aProperty, aTarget, aTruthValue, getter_AddRefs(sources)); + if (NS_FAILED(rv)) return rv; + + PRBool hasMore; + rv = sources->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + if (hasMore) { + nsCOMPtr isupports; + rv = sources->GetNext(getter_AddRefs(isupports)); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(isupports, aSource); + } + } + + return NS_RDF_NO_VALUE; +} + +NS_IMETHODIMP +nsGlobalHistory::GetSources(nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + PRBool aTruthValue, + nsISimpleEnumerator** aSources) +{ + // XXX TODO: make sure each URL in history is connected back to + // NC:HistoryRoot. + NS_PRECONDITION(aProperty != nsnull, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nsnull, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + if (aProperty == kNC_URL) { + // Call GetSource() and return a singleton enumerator for the URL. + nsCOMPtr source; + rv = GetSource(aProperty, aTarget, aTruthValue, getter_AddRefs(source)); + if (NS_FAILED(rv)) return rv; + + return NS_NewSingletonEnumerator(aSources, source); + } + else { + // See if aProperty is something we understand, and create an + // URLEnumerator to select URLs with the appropriate value. + + mdb_column col = 0; // == "not a property that I grok" + void* value = nsnull; + PRInt32 len = 0; + + // PRInt64/date properties + if (aProperty == kNC_Date || + aProperty == kNC_FirstVisitDate) { + nsCOMPtr date = do_QueryInterface(aTarget); + if (date) { + PRInt64 n; + + rv = date->GetValue(&n); + if (NS_FAILED(rv)) return rv; + + nsCAutoString valueStr; + rv = PRInt64ToChars(n, valueStr); + if (NS_FAILED(rv)) return rv; + + value = (void *)ToNewCString(valueStr); + if (aProperty == kNC_Date) + col = kToken_LastVisitDateColumn; + else + col = kToken_FirstVisitDateColumn; + } + } + // PRInt32 properties + else if (aProperty == kNC_VisitCount) { + nsCOMPtr countLiteral = do_QueryInterface(aTarget); + if (countLiteral) { + PRInt32 intValue; + rv = countLiteral->GetValue(&intValue); + if (NS_FAILED(rv)) return rv; + + nsAutoString valueStr; valueStr.AppendInt(intValue); + value = ToNewUnicode(valueStr); + len = valueStr.Length() * sizeof(PRUnichar); + col = kToken_VisitCountColumn; + } + + } + // PRUnichar* properties + else if (aProperty == kNC_Name) { + nsCOMPtr name = do_QueryInterface(aTarget); + if (name) { + PRUnichar* p; + rv = name->GetValue(&p); + if (NS_FAILED(rv)) return rv; + + len = nsCRT::strlen(p) * sizeof(PRUnichar); + value = p; + + col = kToken_NameColumn; + } + } + + // char* properties + else if (aProperty == kNC_Hostname || + aProperty == kNC_Referrer) { + col = kToken_ReferrerColumn; + nsCOMPtr referrer = do_QueryInterface(aTarget); + if (referrer) { + char* p; + rv = referrer->GetValue(&p); + if (NS_FAILED(rv)) return rv; + + len = PL_strlen(p); + value = p; + + if (aProperty == kNC_Hostname) + col = kToken_HostnameColumn; + else if (aProperty == kNC_Referrer) + col = kToken_ReferrerColumn; + } + } + + if (col) { + // The URLEnumerator takes ownership of the bytes allocated in |value|. + URLEnumerator* result = new URLEnumerator(kToken_URLColumn, col, + kToken_HiddenColumn, + value, len); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + rv = result->Init(mEnv, mTable); + if (NS_FAILED(rv)) return rv; + + *aSources = result; + NS_ADDREF(*aSources); + return NS_OK; + } + } + + return NS_NewEmptyEnumerator(aSources); +} + +NS_IMETHODIMP +nsGlobalHistory::GetTarget(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + PRBool aTruthValue, + nsIRDFNode** aTarget) +{ + + NS_PRECONDITION(aSource != nsnull, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nsnull, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + // Initialize return value. + *aTarget = nsnull; + + // Only "positive" assertions here. + if (! aTruthValue) + return NS_RDF_NO_VALUE; + + // XXX eventually use IsFindResource to simply return the first + // matching row? + if (aProperty == kNC_child && + (aSource == kNC_HistoryRoot || + aSource == kNC_HistoryByDate || + IsFindResource(aSource))) { + + // If they're asking for all the children of the HistoryRoot, call + // through to GetTargets() and return the first one. + nsCOMPtr targets; + rv = GetTargets(aSource, aProperty, aTruthValue, getter_AddRefs(targets)); + if (NS_FAILED(rv)) return rv; + + PRBool hasMore; + rv = targets->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + if (! hasMore) return NS_RDF_NO_VALUE; + + nsCOMPtr isupports; + rv = targets->GetNext(getter_AddRefs(isupports)); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(isupports, aTarget); + } + else if ((aProperty == kNC_Date) || + (aProperty == kNC_FirstVisitDate) || + (aProperty == kNC_VisitCount) || + (aProperty == kNC_AgeInDays) || + (aProperty == kNC_Name) || + (aProperty == kNC_NameSort) || + (aProperty == kNC_Hostname) || + (aProperty == kNC_Referrer) || + (aProperty == kNC_URL)) { + + const char* uri; + rv = aSource->GetValueConst(&uri); + if (NS_FAILED(rv)) return rv; + + // url is self-referential, so we'll always just return itself + // however, don't return the URLs of find resources + if (aProperty == kNC_URL && !IsFindResource(aSource)) { + + nsCOMPtr uriLiteral; + rv = gRDFService->GetLiteral(NS_ConvertUTF8toUCS2(uri).get(), getter_AddRefs(uriLiteral)); + if (NS_FAILED(rv)) return(rv); + *aTarget = uriLiteral; + NS_ADDREF(*aTarget); + return NS_OK; + } + + // find URIs are special + if (((aProperty == kNC_Name) || (aProperty == kNC_NameSort)) && + IsFindResource(aSource)) { + + // for sorting, we sort by uri, so just return the URI as a literal + if (aProperty == kNC_NameSort) { + nsCOMPtr uriLiteral; + rv = gRDFService->GetLiteral(NS_ConvertUTF8toUCS2(uri).get(), + getter_AddRefs(uriLiteral)); + if (NS_FAILED(rv)) return(rv); + + *aTarget = uriLiteral; + NS_ADDREF(*aTarget); + return NS_OK; + } + else + return GetFindUriName(uri, aTarget); + } + // ok, we got this far, so we have to retrieve something from + // the row in the database + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, uri, getter_AddRefs(row)); + if (NS_FAILED(rv)) return NS_RDF_NO_VALUE; + + mdb_err err; + // ...and then depending on the property they want, we'll pull the + // cell they want out of it. + if (aProperty == kNC_Date || + aProperty == kNC_FirstVisitDate) { + // Last visit date + PRInt64 i; + if (aProperty == kNC_Date) + rv = GetRowValue(row, kToken_LastVisitDateColumn, &i); + else + rv = GetRowValue(row, kToken_FirstVisitDateColumn, &i); + + if (NS_FAILED(rv)) return rv; + + nsCOMPtr date; + rv = gRDFService->GetDateLiteral(i, getter_AddRefs(date)); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(date, aTarget); + } + else if (aProperty == kNC_VisitCount) { + mdbYarn yarn; + err = row->AliasCellYarn(mEnv, kToken_VisitCountColumn, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + PRInt32 visitCount = 0; + rv = GetRowValue(row, kToken_VisitCountColumn, &visitCount); + if (NS_FAILED(rv) || visitCount <1) + visitCount = 1; // assume we've visited at least once + + nsCOMPtr visitCountLiteral; + rv = gRDFService->GetIntLiteral(visitCount, + getter_AddRefs(visitCountLiteral)); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(visitCountLiteral, aTarget); + } + else if (aProperty == kNC_AgeInDays) { + PRInt64 lastVisitDate; + rv = GetRowValue(row, kToken_LastVisitDateColumn, &lastVisitDate); + if (NS_FAILED(rv)) return rv; + + PRInt32 days = GetAgeInDays(NormalizeTime(GetNow()), lastVisitDate); + + nsCOMPtr ageLiteral; + rv = gRDFService->GetIntLiteral(days, getter_AddRefs(ageLiteral)); + if (NS_FAILED(rv)) return rv; + + *aTarget = ageLiteral; + NS_ADDREF(*aTarget); + return NS_OK; + } + else if (aProperty == kNC_Name || + aProperty == kNC_NameSort) { + // Site name (i.e., page title) + nsAutoString title; + rv = GetRowValue(row, kToken_NameColumn, title); + if (NS_FAILED(rv) || title.IsEmpty()) { + // yank out the filename from the url, use that + nsCOMPtr aUri; + rv = NS_NewURI(getter_AddRefs(aUri), uri); + if (NS_FAILED(rv)) return rv; + nsCOMPtr urlObj(do_QueryInterface(aUri)); + if (!urlObj) + return NS_ERROR_FAILURE; + + nsCAutoString filename; + rv = urlObj->GetFileName(filename); + if (NS_FAILED(rv) || filename.IsEmpty()) { + // ok fine. we'll use the file path. then we give up! + rv = urlObj->GetPath(filename); + if (strcmp(filename.get(), "/") == 0) { + // if the top of a site does not have a title + // (common for redirections) then return the hostname + return GetTarget(aSource, kNC_Hostname, aTruthValue, aTarget); + + } + } + + if (NS_FAILED(rv)) return rv; + + // assume the url is in UTF8 + title = NS_ConvertUTF8toUCS2(filename); + } + if (NS_FAILED(rv)) return rv; + + nsCOMPtr name; + rv = gRDFService->GetLiteral(title.get(), getter_AddRefs(name)); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(name, aTarget); + } + else if (aProperty == kNC_Hostname || + aProperty == kNC_Referrer) { + + nsCAutoString str; + if (aProperty == kNC_Hostname) + rv = GetRowValue(row, kToken_HostnameColumn, str); + else if (aProperty == kNC_Referrer) + rv = GetRowValue(row, kToken_ReferrerColumn, str); + + if (NS_FAILED(rv)) return rv; + + nsCOMPtr resource; + rv = gRDFService->GetResource(str.get(), + getter_AddRefs(resource)); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(resource, aTarget); + } + + else { + NS_NOTREACHED("huh, how'd I get here?"); + } + } + return NS_RDF_NO_VALUE; +} + +void +nsGlobalHistory::Sync() +{ + if (mDirty) + Flush(); + + mDirty = PR_FALSE; + mSyncTimer = nsnull; +} + +void +nsGlobalHistory::ExpireNow() +{ + mNowValid = PR_FALSE; + mExpireNowTimer = nsnull; +} + +// when we're dirty, we want to make sure we sync again soon, +// but make sure that we don't keep syncing if someone is surfing +// a lot, so cancel the existing timer if any is still waiting to fire +nsresult +nsGlobalHistory::SetDirty() +{ + nsresult rv; + + if (mSyncTimer) + mSyncTimer->Cancel(); + + if (!mSyncTimer) + mSyncTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_FAILED(rv)) return rv; + + mDirty = PR_TRUE; + mSyncTimer->InitWithFuncCallback(fireSyncTimer, this, HISTORY_SYNC_TIMEOUT, + nsITimer::TYPE_ONE_SHOT); + + + return NS_OK; +} + +// hack to avoid calling PR_Now() too often, as is the case when +// we're asked the ageindays of many history entries in a row +PRInt64 +nsGlobalHistory::GetNow() +{ + if (!mNowValid) { // not dirty, mLastNow is crufty + mLastNow = PR_Now(); + mNowValid = PR_TRUE; + if (!mExpireNowTimer) + mExpireNowTimer = do_CreateInstance("@mozilla.org/timer;1"); + + if (mExpireNowTimer) + mExpireNowTimer->InitWithFuncCallback(expireNowTimer, this, HISTORY_EXPIRE_NOW_TIMEOUT, + nsITimer::TYPE_ONE_SHOT); + } + + return mLastNow; +} + +NS_IMETHODIMP +nsGlobalHistory::GetTargets(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + PRBool aTruthValue, + nsISimpleEnumerator** aTargets) +{ + NS_PRECONDITION(aSource != nsnull, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nsnull, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + if (!aTruthValue) + return NS_NewEmptyEnumerator(aTargets); + + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + + // list all URLs off the root + if ((aSource == kNC_HistoryRoot) && + (aProperty == kNC_child)) { + URLEnumerator* result = new URLEnumerator(kToken_URLColumn, + kToken_HiddenColumn); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + rv = result->Init(mEnv, mTable); + if (NS_FAILED(rv)) return rv; + + *aTargets = result; + NS_ADDREF(*aTargets); + return NS_OK; + } + else if ((aSource == kNC_HistoryByDate) && + (aProperty == kNC_child)) { + + return GetRootDayQueries(aTargets); + } + else if (aProperty == kNC_child && + IsFindResource(aSource)) { + return CreateFindEnumerator(aSource, aTargets); + } + + else if ((aProperty == kNC_Date) || + (aProperty == kNC_FirstVisitDate) || + (aProperty == kNC_VisitCount) || + (aProperty == kNC_AgeInDays) || + (aProperty == kNC_Name) || + (aProperty == kNC_Hostname) || + (aProperty == kNC_Referrer)) { + nsresult rv; + + nsCOMPtr target; + rv = GetTarget(aSource, aProperty, aTruthValue, getter_AddRefs(target)); + if (NS_FAILED(rv)) return rv; + + if (rv == NS_OK) { + return NS_NewSingletonEnumerator(aTargets, target); + } + } + + // we've already answered the queries from the root, so we must be + // looking for real entries + + return NS_NewEmptyEnumerator(aTargets); +} + +NS_IMETHODIMP +nsGlobalHistory::Assert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + PRBool aTruthValue) +{ + // History cannot be modified + return NS_RDF_ASSERTION_REJECTED; +} + +NS_IMETHODIMP +nsGlobalHistory::Unassert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + // translate into an appropriate removehistory call + nsresult rv; + if ((aSource == kNC_HistoryRoot || aSource == kNC_HistoryByDate || IsFindResource(aSource)) && + aProperty == kNC_child) { + + nsCOMPtr resource = do_QueryInterface(aTarget, &rv); + if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED; + + const char* targetUrl; + rv = resource->GetValueConst(&targetUrl); + if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED; + + if (IsFindResource(resource)) { + // convert uri to a query + searchQuery query; + rv = FindUrlToSearchQuery(targetUrl, query); + if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED; + + matchQuery_t matchQuery; + matchQuery.history = this; + matchQuery.query = &query; + rv = RemoveMatchingRows(matchQueryCallback, (void*)&matchQuery, PR_TRUE); + FreeSearchQuery(query); + if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED; + + // if there are batches in progress, we don't want to notify + // observers that we're deleting items. the caller promises + // to handle whatever UI updating is necessary when we're finished. + if (!mBatchesInProgress) + NotifyUnassert(aSource, aProperty, aTarget); + + return NS_OK; + } + + // ignore any error + rv = RemovePage(targetUrl); + if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED; + +#ifdef MOZ_PHOENIX + if (!mBatchesInProgress && IsFindResource(aSource)) { + // if there are batches in progress, we don't want to notify + // observers that we're deleting items. the caller promises + // to handle whatever UI updating is necessary when we're finished. + NotifyUnassert(aSource, aProperty, aTarget); + } +#endif + + return NS_OK; + } + + return NS_RDF_ASSERTION_REJECTED; +} + +NS_IMETHODIMP +nsGlobalHistory::Change(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aOldTarget, + nsIRDFNode* aNewTarget) +{ + return NS_RDF_ASSERTION_REJECTED; +} + +NS_IMETHODIMP +nsGlobalHistory::Move(nsIRDFResource* aOldSource, + nsIRDFResource* aNewSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + return NS_RDF_ASSERTION_REJECTED; +} + +NS_IMETHODIMP +nsGlobalHistory::HasAssertion(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + PRBool aTruthValue, + PRBool* aHasAssertion) +{ + + NS_PRECONDITION(aSource != nsnull, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nsnull, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nsnull, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + // Only "positive" assertions here. + if (!aTruthValue) { + *aHasAssertion = PR_FALSE; + return NS_OK; + } + + nsresult rv; + + // answer if a specific row matches a find URI + // + // at some point, we should probably match groupby= findURIs with + // findURIs that match all their criteria + // + nsCOMPtr target = do_QueryInterface(aTarget); + if (target && + aProperty == kNC_child && + IsFindResource(aSource) && + !IsFindResource(target)) { + + const char *uri; + rv = target->GetValueConst(&uri); + if (NS_FAILED(rv)) return rv; + + searchQuery query; + FindUrlToSearchQuery(uri, query); + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, uri, getter_AddRefs(row)); + // not even in history. don't bother trying + if (NS_FAILED(rv) || HasCell(mEnv, row, kToken_HiddenColumn)) { + *aHasAssertion = PR_FALSE; + return NS_OK; + } + + *aHasAssertion = RowMatches(row, &query); + FreeSearchQuery(query); + return NS_OK; + } + + // Do |GetTargets()| and grovel through the results to see if we + // have the assertion. + // + // XXX *AHEM*, this could be implemented much more efficiently... + + nsCOMPtr targets; + rv = GetTargets(aSource, aProperty, aTruthValue, getter_AddRefs(targets)); + if (NS_FAILED(rv)) return rv; + + while (1) { + PRBool hasMore; + rv = targets->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + if (! hasMore) + break; + + nsCOMPtr isupports; + rv = targets->GetNext(getter_AddRefs(isupports)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr node = do_QueryInterface(isupports); + if (node.get() == aTarget) { + *aHasAssertion = PR_TRUE; + return NS_OK; + } + } + + *aHasAssertion = PR_FALSE; + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::AddObserver(nsIRDFObserver* aObserver) +{ + NS_PRECONDITION(aObserver != nsnull, "null ptr"); + if (! aObserver) + return NS_ERROR_NULL_POINTER; + + if (! mObservers) { + nsresult rv; + rv = NS_NewISupportsArray(getter_AddRefs(mObservers)); + if (NS_FAILED(rv)) return rv; + } + mObservers->AppendElement(aObserver); + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::RemoveObserver(nsIRDFObserver* aObserver) +{ + NS_PRECONDITION(aObserver != nsnull, "null ptr"); + if (! aObserver) + return NS_ERROR_NULL_POINTER; + + if (! mObservers) + return NS_OK; + + mObservers->RemoveElement(aObserver); + + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, PRBool *result) +{ + NS_PRECONDITION(aNode != nsnull, "null ptr"); + if (! aNode) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr resource = do_QueryInterface(aNode); + if (resource && IsURLInHistory(resource)) { + *result = (aArc == kNC_child); + } + else { + *result = PR_FALSE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, PRBool *result) +{ + NS_PRECONDITION(aSource != nsnull, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + if ((aSource == kNC_HistoryRoot) || + (aSource == kNC_HistoryByDate)) { + *result = (aArc == kNC_child); + } + else if (IsFindResource(aSource)) { + // we handle children of find urls + *result = (aArc == kNC_child || + aArc == kNC_Name || + aArc == kNC_NameSort); + } + else if (IsURLInHistory(aSource)) { + // If the URL is in the history, then it'll have all the + // appropriate attributes. + *result = (aArc == kNC_Date || + aArc == kNC_FirstVisitDate || + aArc == kNC_VisitCount || + aArc == kNC_Name || + aArc == kNC_Hostname || + aArc == kNC_Referrer); + } + else { + *result = PR_FALSE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::ArcLabelsIn(nsIRDFNode* aNode, + nsISimpleEnumerator** aLabels) +{ + NS_PRECONDITION(aNode != nsnull, "null ptr"); + if (! aNode) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr resource = do_QueryInterface(aNode); + if (resource && IsURLInHistory(resource)) { + return NS_NewSingletonEnumerator(aLabels, kNC_child); + } + else { + return NS_NewEmptyEnumerator(aLabels); + } +} + +NS_IMETHODIMP +nsGlobalHistory::ArcLabelsOut(nsIRDFResource* aSource, + nsISimpleEnumerator** aLabels) +{ + NS_PRECONDITION(aSource != nsnull, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + if ((aSource == kNC_HistoryRoot) || + (aSource == kNC_HistoryByDate)) { + return NS_NewSingletonEnumerator(aLabels, kNC_child); + } + else if (IsURLInHistory(aSource)) { + // If the URL is in the history, then it'll have all the + // appropriate attributes. + nsCOMPtr array; + rv = NS_NewISupportsArray(getter_AddRefs(array)); + if (NS_FAILED(rv)) return rv; + + array->AppendElement(kNC_Date); + array->AppendElement(kNC_FirstVisitDate); + array->AppendElement(kNC_VisitCount); + array->AppendElement(kNC_Name); + array->AppendElement(kNC_Hostname); + array->AppendElement(kNC_Referrer); + + return NS_NewArrayEnumerator(aLabels, array); + } + else if (IsFindResource(aSource)) { + nsCOMPtr array; + rv = NS_NewISupportsArray(getter_AddRefs(array)); + if (NS_FAILED(rv)) return rv; + + array->AppendElement(kNC_child); + array->AppendElement(kNC_Name); + array->AppendElement(kNC_NameSort); + + return NS_NewArrayEnumerator(aLabels, array); + } + else { + return NS_NewEmptyEnumerator(aLabels); + } +} + +NS_IMETHODIMP +nsGlobalHistory::GetAllCommands(nsIRDFResource* aSource, + nsIEnumerator/**/** aCommands) +{ + NS_NOTYETIMPLEMENTED("sorry"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsGlobalHistory::GetAllCmds(nsIRDFResource* aSource, + nsISimpleEnumerator/**/** aCommands) +{ + return NS_NewEmptyEnumerator(aCommands); +} + +NS_IMETHODIMP +nsGlobalHistory::IsCommandEnabled(nsISupportsArray/**/* aSources, + nsIRDFResource* aCommand, + nsISupportsArray/**/* aArguments, + PRBool* aResult) +{ + NS_NOTYETIMPLEMENTED("sorry"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsGlobalHistory::DoCommand(nsISupportsArray/**/* aSources, + nsIRDFResource* aCommand, + nsISupportsArray/**/* aArguments) +{ + NS_NOTYETIMPLEMENTED("sorry"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsGlobalHistory::GetAllResources(nsISimpleEnumerator** aResult) +{ + URLEnumerator* result = new URLEnumerator(kToken_URLColumn, + kToken_HiddenColumn); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + rv = result->Init(mEnv, mTable); + if (NS_FAILED(rv)) return rv; + + *aResult = result; + NS_ADDREF(*aResult); + return NS_OK; +} + + + +//////////////////////////////////////////////////////////////////////// +// nsIRDFRemoteDataSource + +NS_IMETHODIMP +nsGlobalHistory::GetLoaded(PRBool* _result) +{ + *_result = PR_TRUE; + return NS_OK; +} + + + +NS_IMETHODIMP +nsGlobalHistory::Init(const char* aURI) +{ + return(NS_OK); +} + + + +NS_IMETHODIMP +nsGlobalHistory::Refresh(PRBool aBlocking) +{ + return(NS_OK); +} + +NS_IMETHODIMP +nsGlobalHistory::Flush() +{ + return Commit(kLargeCommit); +} + +NS_IMETHODIMP +nsGlobalHistory::StartBatchUpdate() +{ + nsresult rv = NS_OK; + + ++mBatchesInProgress; + + // we could call mObservers->EnumerateForwards() here + // to save the addref/release on each observer, but + // it's unlikely that anyone but the tree builder + // is observing us + if (mObservers) { + PRUint32 count; + rv = mObservers->Count(&count); + if (NS_FAILED(rv)) return rv; + + for (PRInt32 i = 0; i < PRInt32(count); ++i) { + nsIRDFObserver* observer = NS_STATIC_CAST(nsIRDFObserver*, mObservers->ElementAt(i)); + + NS_ASSERTION(observer != nsnull, "null ptr"); + if (! observer) + continue; + + rv = observer->BeginUpdateBatch(this); + NS_RELEASE(observer); + } + } + return rv; +} + +NS_IMETHODIMP +nsGlobalHistory::EndBatchUpdate() +{ + nsresult rv = NS_OK; + + --mBatchesInProgress; + + // we could call mObservers->EnumerateForwards() here + // to save the addref/release on each observer, but + // it's unlikely that anyone but the tree builder + // is observing us + if (mObservers) { + PRUint32 count; + rv = mObservers->Count(&count); + if (NS_FAILED(rv)) return rv; + + for (PRInt32 i = 0; i < PRInt32(count); ++i) { + nsIRDFObserver* observer = NS_STATIC_CAST(nsIRDFObserver*, mObservers->ElementAt(i)); + + NS_ASSERTION(observer != nsnull, "null ptr"); + if (! observer) + continue; + + rv = observer->EndUpdateBatch(this); + NS_RELEASE(observer); + } + } + return rv; +} + +//---------------------------------------------------------------------- +// +// nsGlobalHistory +// +// Miscellaneous implementation methods +// + +nsresult +nsGlobalHistory::Init() +{ + nsresult rv; + + // we'd like to get this pref when we need it, but at that point, + // we can't get the pref service. register a pref observer so we update + // if the pref changes + + if (!gPrefBranch) { + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = prefs->GetBranch(PREF_BRANCH_BASE, &gPrefBranch); + NS_ENSURE_SUCCESS(rv, rv); + } + + gPrefBranch->GetIntPref(PREF_BROWSER_HISTORY_EXPIRE_DAYS, &mExpireDays); + gPrefBranch->GetBoolPref(PREF_AUTOCOMPLETE_ONLY_TYPED, &mAutocompleteOnlyTyped); + nsCOMPtr pbi = do_QueryInterface(gPrefBranch); + if (pbi) { + pbi->AddObserver(PREF_AUTOCOMPLETE_ONLY_TYPED, this, PR_FALSE); + pbi->AddObserver(PREF_BROWSER_HISTORY_EXPIRE_DAYS, this, PR_FALSE); + } + + if (gRefCnt++ == 0) { + rv = nsServiceManager::GetService(kRDFServiceCID, + NS_GET_IID(nsIRDFService), + (nsISupports**) &gRDFService); + + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service"); + if (NS_FAILED(rv)) return rv; + + gRDFService->GetResource(NC_NAMESPACE_URI "Page", &kNC_Page); + gRDFService->GetResource(NC_NAMESPACE_URI "Date", &kNC_Date); + gRDFService->GetResource(NC_NAMESPACE_URI "FirstVisitDate", + &kNC_FirstVisitDate); + gRDFService->GetResource(NC_NAMESPACE_URI "VisitCount", &kNC_VisitCount); + gRDFService->GetResource(NC_NAMESPACE_URI "AgeInDays", &kNC_AgeInDays); + gRDFService->GetResource(NC_NAMESPACE_URI "Name", &kNC_Name); + gRDFService->GetResource(NC_NAMESPACE_URI "Name?sort=true", &kNC_NameSort); + gRDFService->GetResource(NC_NAMESPACE_URI "Hostname", &kNC_Hostname); + gRDFService->GetResource(NC_NAMESPACE_URI "Referrer", &kNC_Referrer); + gRDFService->GetResource(NC_NAMESPACE_URI "child", &kNC_child); + gRDFService->GetResource(NC_NAMESPACE_URI "URL", &kNC_URL); + gRDFService->GetResource("NC:HistoryRoot", &kNC_HistoryRoot); + gRDFService->GetResource("NC:HistoryByDate", &kNC_HistoryByDate); + } + + // register this as a named data source with the RDF service + rv = gRDFService->RegisterDataSource(this, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr bundleService = + do_GetService(kStringBundleServiceCID, &rv); + + if (NS_SUCCEEDED(rv)) { + rv = bundleService->CreateBundle("chrome://communicator/locale/history/history.properties", getter_AddRefs(mBundle)); + } + + // register to observe profile changes + nsCOMPtr observerService = + do_GetService("@mozilla.org/observer-service;1", &rv); + NS_ASSERTION(observerService, "failed to get observer service"); + if (observerService) { + observerService->AddObserver(this, "profile-before-change", PR_TRUE); + observerService->AddObserver(this, "profile-do-change", PR_TRUE); + } + + return NS_OK; +} + + +nsresult +nsGlobalHistory::OpenDB() +{ + nsresult rv; + + if (mStore) return NS_OK; + + nsCOMPtr historyFile; + rv = NS_GetSpecialDirectory(NS_APP_HISTORY_50_FILE, getter_AddRefs(historyFile)); + NS_ENSURE_SUCCESS(rv, rv); + + static NS_DEFINE_CID(kMorkCID, NS_MORK_CID); + nsCOMPtr factoryfactory; + rv = nsComponentManager::CreateInstance(kMorkCID, + nsnull, + NS_GET_IID(nsIMdbFactoryFactory), + getter_AddRefs(factoryfactory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = factoryfactory->GetMdbFactory(&gMdbFactory); + NS_ENSURE_SUCCESS(rv, rv); + + mdb_err err; + + err = gMdbFactory->MakeEnv(nsnull, &mEnv); + mEnv->SetAutoClear(PR_TRUE); + NS_ASSERTION((err == 0), "unable to create mdb env"); + if (err != 0) return NS_ERROR_FAILURE; + + // MDB requires native file paths + + nsCAutoString filePath; + rv = historyFile->GetNativePath(filePath); + NS_ENSURE_SUCCESS(rv, rv); + + PRBool exists = PR_TRUE; + + historyFile->Exists(&exists); + + if (!exists || NS_FAILED(rv = OpenExistingFile(gMdbFactory, filePath.get()))) { + + // we couldn't open the file, so it's either corrupt or doesn't exist. + // attempt to delete the file, but ignore the error + historyFile->Remove(PR_FALSE); + rv = OpenNewFile(gMdbFactory, filePath.get()); + } + + NS_ENSURE_SUCCESS(rv, rv); + + // get the initial filesize. Used in Commit() to determine type of commit. + rv = historyFile->GetFileSize(&mFileSizeOnDisk); + if (NS_FAILED(rv)) { + LL_I2L(mFileSizeOnDisk, 0); + } + + return NS_OK; +} + +nsresult +nsGlobalHistory::OpenExistingFile(nsIMdbFactory *factory, const char *filePath) +{ + + mdb_err err; + nsresult rv; + mdb_bool canopen = 0; + mdbYarn outfmt = { nsnull, 0, 0, 0, 0, nsnull }; + + nsIMdbHeap* dbHeap = 0; + mdb_bool dbFrozen = mdbBool_kFalse; // not readonly, we want modifiable + nsCOMPtr oldFile; // ensures file is released + err = factory->OpenOldFile(mEnv, dbHeap, filePath, + dbFrozen, getter_AddRefs(oldFile)); + + // don't assert, the file might just not be there + if ((err !=0) || !oldFile) return NS_ERROR_FAILURE; + + err = factory->CanOpenFilePort(mEnv, oldFile, // the file to investigate + &canopen, &outfmt); + + // XXX possible that format out of date, in which case we should + // just re-write the file. + if ((err !=0) || !canopen) return NS_ERROR_FAILURE; + + nsIMdbThumb* thumb = nsnull; + mdbOpenPolicy policy = { { 0, 0 }, 0, 0 }; + + err = factory->OpenFileStore(mEnv, dbHeap, oldFile, &policy, &thumb); + if ((err !=0) || !thumb) return NS_ERROR_FAILURE; + + mdb_count total; + mdb_count current; + mdb_bool done; + mdb_bool broken; + + do { + err = thumb->DoMore(mEnv, &total, ¤t, &done, &broken); + } while ((err == 0) && !broken && !done); + + if ((err == 0) && done) { + err = factory->ThumbToOpenStore(mEnv, thumb, &mStore); + } + + NS_IF_RELEASE(thumb); + + if (err != 0) return NS_ERROR_FAILURE; + + rv = CreateTokens(); + NS_ENSURE_SUCCESS(rv, rv); + + mdbOid oid = { kToken_HistoryRowScope, 1 }; + err = mStore->GetTable(mEnv, &oid, &mTable); + NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE); + if (!mTable) { + NS_WARNING("Your history file is somehow corrupt.. deleting it."); + return NS_ERROR_FAILURE; + } + + err = mTable->GetMetaRow(mEnv, &oid, nsnull, getter_AddRefs(mMetaRow)); + if (err != 0) + NS_WARNING("Could not get meta row\n"); + + CheckHostnameEntries(); + + if (err != 0) return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsresult +nsGlobalHistory::OpenNewFile(nsIMdbFactory *factory, const char *filePath) +{ + nsresult rv; + mdb_err err; + + nsIMdbHeap* dbHeap = 0; + nsCOMPtr newFile; // ensures file is released + err = factory->CreateNewFile(mEnv, dbHeap, filePath, + getter_AddRefs(newFile)); + + if ((err != 0) || !newFile) return NS_ERROR_FAILURE; + + mdbOpenPolicy policy = { { 0, 0 }, 0, 0 }; + err = factory->CreateNewFileStore(mEnv, dbHeap, newFile, &policy, &mStore); + + if (err != 0) return NS_ERROR_FAILURE; + + rv = CreateTokens(); + NS_ENSURE_SUCCESS(rv, rv); + + // Create the one and only table in the history db + err = mStore->NewTable(mEnv, kToken_HistoryRowScope, kToken_HistoryKind, PR_TRUE, nsnull, &mTable); + if (err != 0) return NS_ERROR_FAILURE; + if (!mTable) return NS_ERROR_FAILURE; + + // Force a commit now to get it written out. + nsCOMPtr thumb; + err = mStore->LargeCommit(mEnv, getter_AddRefs(thumb)); + if (err != 0) return NS_ERROR_FAILURE; + + mdb_count total; + mdb_count current; + mdb_bool done; + mdb_bool broken; + + do { + err = thumb->DoMore(mEnv, &total, ¤t, &done, &broken); + } while ((err == 0) && !broken && !done); + + if ((err != 0) || !done) return NS_ERROR_FAILURE; + + return NS_OK; +} + +// break the uri down into a search query, and pass off to +// SearchEnumerator +nsresult +nsGlobalHistory::CreateFindEnumerator(nsIRDFResource *aSource, + nsISimpleEnumerator **aResult) +{ + nsresult rv; + // make sure this was a find query + if (!IsFindResource(aSource)) + return NS_ERROR_FAILURE; + + const char* uri; + rv = aSource->GetValueConst(&uri); + if (NS_FAILED(rv)) return rv; + + // convert uri to a query + searchQuery* query = new searchQuery; + if (!query) return NS_ERROR_OUT_OF_MEMORY; + FindUrlToSearchQuery(uri, *query); + + // the enumerator will take ownership of the query + SearchEnumerator *result = + new SearchEnumerator(query, kToken_HiddenColumn, this); + if (!result) return NS_ERROR_OUT_OF_MEMORY; + + rv = result->Init(mEnv, mTable); + if (NS_FAILED(rv)) return rv; + + // return the value + *aResult = result; + NS_ADDREF(*aResult); + + return NS_OK; +} + + +// for each row, we need to parse out the hostname from the url +// then store it in a column +nsresult +nsGlobalHistory::CheckHostnameEntries() +{ + nsresult rv = NS_OK; + + mdb_err err; + + nsCOMPtr cursor; + nsCOMPtr row; + + err = mTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(cursor)); + if (err != 0) return NS_ERROR_FAILURE; + + int marker; + err = mTable->StartBatchChangeHint(mEnv, &marker); + NS_ASSERTION(err == 0, "unable to start batch"); + if (err != 0) return NS_ERROR_FAILURE; + + mdb_pos pos; + err = cursor->NextRow(mEnv, getter_AddRefs(row), &pos); + if (err != 0) return NS_ERROR_FAILURE; + + // comment out this code to rebuild the hostlist at startup +#if 1 + // bail early if the first row has a hostname + if (row) { + nsCAutoString hostname; + rv = GetRowValue(row, kToken_HostnameColumn, hostname); + if (NS_SUCCEEDED(rv) && !hostname.IsEmpty()) + return NS_OK; + } +#endif + + // cached variables used in the loop + nsCAutoString url; + nsXPIDLCString hostname; + + nsCOMPtr ioService = do_GetService(NS_IOSERVICE_CONTRACTID); + if (!ioService) return NS_ERROR_FAILURE; + + + while (row) { +#if 0 + rv = GetRowValue(row, kToken_URLColumn, url); + if (NS_FAILED(rv)) break; + + ioService->ExtractUrlPart(url, nsIIOService::url_Host, 0, 0, getter_Copies(hostname)); + + SetRowValue(row, kToken_HostnameColumn, hostname); + +#endif + + // to be turned on when we're confident in mork's ability + // to handle yarn forms properly +#if 0 + nsAutoString title; + rv = GetRowValue(row, kToken_NameColumn, title); + // reencode back into UTF8 + if (NS_SUCCEEDED(rv)) + SetRowValue(row, kToken_NameColumn, title.get()); +#endif + cursor->NextRow(mEnv, getter_AddRefs(row), &pos); + } + + // Finish the batch. + err = mTable->EndBatchChangeHint(mEnv, &marker); + NS_ASSERTION(err == 0, "error ending batch"); + + return rv; +} + +nsresult +nsGlobalHistory::CreateTokens() +{ + mdb_err err; + + NS_PRECONDITION(mStore != nsnull, "not initialized"); + if (! mStore) + return NS_ERROR_NOT_INITIALIZED; + + err = mStore->StringToToken(mEnv, "ns:history:db:row:scope:history:all", &kToken_HistoryRowScope); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "ns:history:db:table:kind:history", &kToken_HistoryKind); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "URL", &kToken_URLColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "Referrer", &kToken_ReferrerColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "LastVisitDate", &kToken_LastVisitDateColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "FirstVisitDate", &kToken_FirstVisitDateColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "VisitCount", &kToken_VisitCountColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "Name", &kToken_NameColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "Hostname", &kToken_HostnameColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "Hidden", &kToken_HiddenColumn); + if (err != 0) return NS_ERROR_FAILURE; + + err = mStore->StringToToken(mEnv, "Typed", &kToken_TypedColumn); + if (err != 0) return NS_ERROR_FAILURE; + + // meta-data tokens + err = mStore->StringToToken(mEnv, "LastPageVisited", &kToken_LastPageVisited); + + return NS_OK; +} + +nsresult nsGlobalHistory::Commit(eCommitType commitType) +{ + if (!mStore || !mTable) + return NS_OK; + + nsresult err = NS_OK; + nsCOMPtr thumb; + + if (commitType == kLargeCommit || commitType == kSessionCommit) + { + mdb_percent outActualWaste = 0; + mdb_bool outShould; + if (mStore) + { + // check how much space would be saved by doing a compress commit. + // If it's more than 30%, go for it. + // N.B. - I'm not sure this calls works in Mork for all cases. + err = mStore->ShouldCompress(mEnv, 30, &outActualWaste, &outShould); + if (NS_SUCCEEDED(err) && outShould) + { + commitType = kCompressCommit; + } + else + { + mdb_count count; + err = mTable->GetCount(mEnv, &count); + // Since Mork's shouldCompress doesn't work, we need to look + // at the file size and the number of rows, and make a stab + // at guessing if we've got a lot of deleted rows. The file + // size is the size when we opened the db, and we really want + // it to be the size after we've written out the file, + // but I think this is a good enough approximation. + if (count > 0) + { + PRInt64 numRows; + PRInt64 bytesPerRow; + PRInt64 desiredAvgRowSize; + + LL_UI2L(numRows, count); + LL_DIV(bytesPerRow, mFileSizeOnDisk, numRows); + LL_I2L(desiredAvgRowSize, 400); + if (LL_CMP(bytesPerRow, >, desiredAvgRowSize)) + commitType = kCompressCommit; + } + } + } + } + switch (commitType) + { + case kLargeCommit: + err = mStore->LargeCommit(mEnv, getter_AddRefs(thumb)); + break; + case kSessionCommit: + err = mStore->SessionCommit(mEnv, getter_AddRefs(thumb)); + break; + case kCompressCommit: + err = mStore->CompressCommit(mEnv, getter_AddRefs(thumb)); + break; + } + if (err == 0) { + mdb_count total; + mdb_count current; + mdb_bool done; + mdb_bool broken; + + do { + err = thumb->DoMore(mEnv, &total, ¤t, &done, &broken); + } while ((err == 0) && !broken && !done); + } + if (err != 0) // mork doesn't return NS error codes. Yet. + return NS_ERROR_FAILURE; + else + return NS_OK; + +} +// if notify is true, we'll notify rdf of deleted rows. +// If we're shutting down history, then (maybe?) we don't +// need or want to notify rdf. +nsresult nsGlobalHistory::ExpireEntries(PRBool notify) +{ + PRTime expirationDate; + PRInt64 microSecondsPerSecond, secondsInDays, microSecondsInExpireDays; + + LL_I2L(microSecondsPerSecond, PR_USEC_PER_SEC); + LL_UI2L(secondsInDays, 60 * 60 * 24 * mExpireDays); + LL_MUL(microSecondsInExpireDays, secondsInDays, microSecondsPerSecond); + LL_SUB(expirationDate, GetNow(), microSecondsInExpireDays); + + matchExpiration_t expiration; + expiration.history = this; + expiration.expirationDate = &expirationDate; + + return RemoveMatchingRows(matchExpirationCallback, (void *)&expiration, notify); +} + +nsresult +nsGlobalHistory::CloseDB() +{ + mdb_err err; + + ExpireEntries(PR_FALSE /* don't notify */); + err = Commit(kSessionCommit); + + // order is important here - logically smallest objects first + mMetaRow = nsnull; + + if (mTable) + mTable->Release(); + + if (mStore) + mStore->Release(); + + if (mEnv) + mEnv->Release(); + + mTable = nsnull; mEnv = nsnull; mStore = nsnull; + + return NS_OK; +} + +nsresult +nsGlobalHistory::FindRow(mdb_column aCol, + const char *aValue, nsIMdbRow **aResult) +{ + if (! mStore) + return NS_ERROR_NOT_INITIALIZED; + + mdb_err err; + PRInt32 len = PL_strlen(aValue); + mdbYarn yarn = { (void*) aValue, len, len, 0, 0, nsnull }; + + mdbOid rowId; + nsCOMPtr row; + err = mStore->FindRow(mEnv, kToken_HistoryRowScope, + aCol, &yarn, + &rowId, getter_AddRefs(row)); + + + if (!row) return NS_ERROR_NOT_AVAILABLE; + + + // make sure it's actually stored in the main table + mdb_bool hasRow; + mTable->HasRow(mEnv, row, &hasRow); + + if (!hasRow) return NS_ERROR_NOT_AVAILABLE; + + *aResult = row; + (*aResult)->AddRef(); + + return NS_OK; +} + +PRBool +nsGlobalHistory::IsURLInHistory(nsIRDFResource* aResource) +{ + nsresult rv; + + const char* url; + rv = aResource->GetValueConst(&url); + if (NS_FAILED(rv)) return PR_FALSE; + + nsCOMPtr row; + rv = FindRow(kToken_URLColumn, url, getter_AddRefs(row)); + + return (NS_SUCCEEDED(rv)) ? PR_TRUE : PR_FALSE; +} + + +nsresult +nsGlobalHistory::NotifyAssert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aValue) +{ + nsresult rv; + + if (mObservers) { + PRUint32 count; + rv = mObservers->Count(&count); + if (NS_FAILED(rv)) return rv; + + for (PRInt32 i = 0; i < PRInt32(count); ++i) { + nsIRDFObserver* observer = NS_STATIC_CAST(nsIRDFObserver*, mObservers->ElementAt(i)); + + NS_ASSERTION(observer != nsnull, "null ptr"); + if (! observer) + continue; + + rv = observer->OnAssert(this, aSource, aProperty, aValue); + NS_RELEASE(observer); + } + } + + return NS_OK; +} + + +nsresult +nsGlobalHistory::NotifyUnassert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aValue) +{ + nsresult rv; + + if (mObservers) { + PRUint32 count; + rv = mObservers->Count(&count); + if (NS_FAILED(rv)) return rv; + + for (PRInt32 i = 0; i < PRInt32(count); ++i) { + nsIRDFObserver* observer = NS_STATIC_CAST(nsIRDFObserver*, mObservers->ElementAt(i)); + + NS_ASSERTION(observer != nsnull, "null ptr"); + if (! observer) + continue; + + rv = observer->OnUnassert(this, aSource, aProperty, aValue); + NS_RELEASE(observer); + } + } + + return NS_OK; +} + + + +nsresult +nsGlobalHistory::NotifyChange(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aOldValue, + nsIRDFNode* aNewValue) +{ + nsresult rv; + + if (mObservers) { + PRUint32 count; + rv = mObservers->Count(&count); + if (NS_FAILED(rv)) return rv; + + for (PRInt32 i = 0; i < PRInt32(count); ++i) { + nsIRDFObserver* observer = NS_STATIC_CAST(nsIRDFObserver*, mObservers->ElementAt(i)); + + NS_ASSERTION(observer != nsnull, "null ptr"); + if (! observer) + continue; + + rv = observer->OnChange(this, aSource, aProperty, aOldValue, aNewValue); + NS_RELEASE(observer); + } + } + + return NS_OK; +} + +// +// this just generates a static list of find-style queries +// only returns queries that currently have matches in global history +// +nsresult +nsGlobalHistory::GetRootDayQueries(nsISimpleEnumerator **aResult) +{ + nsresult rv; + nsCOMPtr dayArray; + NS_NewISupportsArray(getter_AddRefs(dayArray)); + + PRInt32 i; + nsCOMPtr finduri; + nsDependentCString + prefix(FIND_BY_AGEINDAYS_PREFIX "is" "&text="); + nsCAutoString uri; + nsCOMPtr findEnumerator; + PRBool hasMore = PR_FALSE; + for (i=0; i<7; i++) { + uri = prefix; + uri.AppendInt(i); + uri.Append("&groupby=Hostname"); + rv = gRDFService->GetResource(uri.get(), getter_AddRefs(finduri)); + if (NS_FAILED(rv)) continue; + rv = CreateFindEnumerator(finduri, getter_AddRefs(findEnumerator)); + if (NS_FAILED(rv)) continue; + rv = findEnumerator->HasMoreElements(&hasMore); + if (NS_SUCCEEDED(rv) && hasMore) + dayArray->AppendElement(finduri); + } + + uri = FIND_BY_AGEINDAYS_PREFIX "isgreater" "&text="; + uri.AppendInt(i-1); + uri.Append("&groupby=Hostname"); + rv = gRDFService->GetResource(uri.get(), getter_AddRefs(finduri)); + if (NS_SUCCEEDED(rv)) { + rv = CreateFindEnumerator(finduri, getter_AddRefs(findEnumerator)); + if (NS_SUCCEEDED(rv)) { + rv = findEnumerator->HasMoreElements(&hasMore); + if (NS_SUCCEEDED(rv) && hasMore) + dayArray->AppendElement(finduri); + } + } + + return NS_NewArrayEnumerator(aResult, dayArray); +} + +// +// convert the name/value pairs stored in a string into an array of +// these pairs +// find:a=b&c=d&e=f&g=h +// becomes an array containing +// {"a" = "b", "c" = "d", "e" = "f", "g" = "h" } +// +nsresult +nsGlobalHistory::FindUrlToTokenList(const char *aURL, nsVoidArray& aResult) +{ + if (PL_strncmp(aURL, "find:", 5) != 0) + return NS_ERROR_UNEXPECTED; + + const char *curpos = aURL + 5; + const char *tokenstart = curpos; + + // this is where we will store the current name and value + const char *tokenName = nsnull; + const char *tokenValue = nsnull; + PRUint32 tokenNameLength=0; + PRUint32 tokenValueLength=0; + + PRBool haveValue = PR_FALSE; // needed because some values are 0-length + while (PR_TRUE) { + while (*curpos && (*curpos != '&') && (*curpos != '=')) + curpos++; + + if (*curpos == '=') { // just found a token name + tokenName = tokenstart; + tokenNameLength = (curpos - tokenstart); + } + else if ((!*curpos || *curpos == '&') && + (tokenNameLength>0)) { // found a value, and we have a + // name + tokenValue = tokenstart; + tokenValueLength = (curpos - tokenstart); + haveValue = PR_TRUE; + } + + // once we have a name/value pair, store it away + // note we're looking at lengths, so that + // "find:&a=b" doesn't connect with a="" + if (tokenNameLength>0 && haveValue) { + + tokenPair *tokenStruct = new tokenPair(tokenName, tokenNameLength, + tokenValue, tokenValueLength); + aResult.AppendElement((void *)tokenStruct); + + // reset our state + tokenName = tokenValue = nsnull; + tokenNameLength = tokenValueLength = 0; + haveValue = PR_FALSE; + } + + // the test has to be here to catch empty values + if (!*curpos) break; + + curpos++; + tokenstart = curpos; + } + + return NS_OK; +} + +void +nsGlobalHistory::FreeTokenList(nsVoidArray& tokens) +{ + PRUint32 length = tokens.Count(); + PRUint32 i; + for (i=0; iGetValueConst(&value); + if (NS_FAILED(rv)) return PR_FALSE; + + return (PL_strncmp(value, "find:", 5)==0); +} + +// +// convert a list of name/value pairs into a search query with 0 or +// more terms and an optional groupby +// +// a term consists of the values of the 4 name/value pairs +// {datasource, match, method, text} +// groupby is stored as a column # +// +nsresult +nsGlobalHistory::TokenListToSearchQuery(const nsVoidArray& aTokens, + searchQuery& aResult) +{ + + PRInt32 i; + PRInt32 length = aTokens.Count(); + + aResult.groupBy = 0; + const char *datasource=nsnull, *property=nsnull, + *method=nsnull, *text=nsnull; + + PRUint32 datasourceLen=0, propertyLen=0, methodLen=0, textLen=0; + rowMatchCallback matchCallback=nsnull; // matching callback if needed + + for (i=0; itokenName, token->tokenName + token->tokenNameLength); + if (tokenName.Equals(NS_LITERAL_CSTRING("datasource"))) { + datasource = token->tokenValue; + datasourceLen = token->tokenValueLength; + } + else if (tokenName.Equals(NS_LITERAL_CSTRING("match"))) { + if (Substring(token->tokenValue, token->tokenValue+token->tokenValueLength).Equals("AgeInDays")) + matchCallback = matchAgeInDaysCallback; + + property = token->tokenValue; + propertyLen = token->tokenValueLength; + } + else if (tokenName.Equals(NS_LITERAL_CSTRING("method"))) { + method = token->tokenValue; + methodLen = token->tokenValueLength; + } + else if (tokenName.Equals(NS_LITERAL_CSTRING("text"))) { + text = token->tokenValue; + textLen = token->tokenValueLength; + } + + // really, we should be storing the group-by as a column number or + // rdf resource + else if (tokenName.Equals(NS_LITERAL_CSTRING("groupby"))) { + mdb_err err; + err = mStore->QueryToken(mEnv, + nsCAutoString(token->tokenValue).get(), + &aResult.groupBy); + if (err != 0) + aResult.groupBy = 0; + } + + // once we complete a term, we move on to the next one + if (datasource && property && method && text) { + searchTerm *currentTerm = new searchTerm(datasource, datasourceLen, + property, propertyLen, + method, methodLen, + text, textLen); + currentTerm->match = matchCallback; + + // append the old one, then create a new one + aResult.terms.AppendElement((void *)currentTerm); + + // reset our state + matchCallback=nsnull; + currentTerm = nsnull; + datasource = property = method = text = 0; + } + } + + return NS_OK; +} + +nsresult +nsGlobalHistory::FindUrlToSearchQuery(const char *aUrl, searchQuery& aResult) +{ + + nsresult rv; + // convert uri to list of tokens + nsVoidArray tokenPairs; + rv = FindUrlToTokenList(aUrl, tokenPairs); + if (NS_FAILED(rv)) return rv; + + // now convert the tokens to a query + rv = TokenListToSearchQuery(tokenPairs, aResult); + + FreeTokenList(tokenPairs); + + return rv; +} + +// preemptively construct some common find-queries so that we show up +// asychronously when a search is open + +// we have to do the following assertions: +// (a=AgeInDays, h=hostname; g=groupby, -> = #child) +// 1) NC:HistoryRoot -> uri +// +// 2) NC:HistoryByDate -> a&g=h +// 3) a&g=h -> a&h +// 4) a&h -> uri +// +// 5) g=h -> h +// 6) h->uri +nsresult +nsGlobalHistory::NotifyFindAssertions(nsIRDFResource *aSource, + nsIMdbRow *aRow) +{ + // we'll construct a bunch of sample queries, and then do + // appropriate assertions + + // first pull out the appropriate values + PRInt64 lastVisited; + GetRowValue(aRow, kToken_LastVisitDateColumn, &lastVisited); + + PRInt32 ageInDays = GetAgeInDays(NormalizeTime(GetNow()), lastVisited); + nsCAutoString ageString; ageString.AppendInt(ageInDays); + + nsCAutoString hostname; + GetRowValue(aRow, kToken_HostnameColumn, hostname); + + // construct some terms that we'll use later + + // Hostname= + searchTerm hostterm("history", sizeof("history")-1, + "Hostname", sizeof("Hostname")-1, + "is", sizeof("is")-1, + hostname.get(), hostname.Length()); + + // AgeInDays= + searchTerm ageterm("history", sizeof("history") -1, + "AgeInDays", sizeof("AgeInDays")-1, + "is", sizeof("is")-1, + ageString.get(), ageString.Length()); + + searchQuery query; + nsCAutoString findUri; + nsCOMPtr childFindResource; + nsCOMPtr parentFindResource; + + // 2) NC:HistoryByDate -> AgeInDays=&groupby=Hostname + query.groupBy = kToken_HostnameColumn; + query.terms.AppendElement((void *)&ageterm); + + GetFindUriPrefix(query, PR_TRUE, findUri); + gRDFService->GetResource(findUri.get(), getter_AddRefs(childFindResource)); + NotifyAssert(kNC_HistoryByDate, kNC_child, childFindResource); + + query.terms.Clear(); + + // 3) AgeInDays=&groupby=Hostname -> + // AgeInDays=&Hostname= + + parentFindResource=childFindResource; // AgeInDays=&groupby=Hostname + + query.groupBy = 0; // create AgeInDays=&Hostname= + query.terms.AppendElement((void *)&ageterm); + query.terms.AppendElement((void *)&hostterm); + + GetFindUriPrefix(query, PR_FALSE, findUri); + gRDFService->GetResource(findUri.get(), getter_AddRefs(childFindResource)); + NotifyAssert(parentFindResource, kNC_child, childFindResource); + + query.terms.Clear(); + + // 4) AgeInDays=&Hostname= -> uri + parentFindResource = childFindResource; // AgeInDays=&hostname= + NotifyAssert(childFindResource, kNC_child, aSource); + + // 5) groupby=Hostname -> Hostname= + query.groupBy = kToken_HostnameColumn; // create groupby=Hostname + + GetFindUriPrefix(query, PR_TRUE, findUri); + gRDFService->GetResource(findUri.get(), getter_AddRefs(parentFindResource)); + + query.groupBy = 0; // create Hostname= + query.terms.AppendElement((void *)&hostterm); + GetFindUriPrefix(query, PR_FALSE, findUri); + findUri.Append(hostname); // append + gRDFService->GetResource(findUri.get(), getter_AddRefs(childFindResource)); + + NotifyAssert(parentFindResource, kNC_child, childFindResource); + + // 6) Hostname= -> uri + parentFindResource = childFindResource; // Hostname= + NotifyAssert(parentFindResource, kNC_child, aSource); + + return NS_OK; +} + + +// simpler than NotifyFindAssertions - basically just notifies +// unassertions from +// 1) NC:HistoryRoot -> uri +// 2) a&h -> uri +// 3) h -> uri + +nsresult +nsGlobalHistory::NotifyFindUnassertions(nsIRDFResource *aSource, + nsIMdbRow* aRow) +{ + // 1) NC:HistoryRoot + NotifyUnassert(kNC_HistoryRoot, kNC_child, aSource); + + // first get age in days + PRInt64 lastVisited; + GetRowValue(aRow, kToken_LastVisitDateColumn, &lastVisited); + PRInt32 ageInDays = GetAgeInDays(NormalizeTime(GetNow()), lastVisited); + nsCAutoString ageString; ageString.AppendInt(ageInDays); + + // now get hostname + nsCAutoString hostname; + GetRowValue(aRow, kToken_HostnameColumn, hostname); + + // construct some terms + // Hostname= + searchTerm hostterm("history", sizeof("history")-1, + "Hostname", sizeof("Hostname")-1, + "is", sizeof("is")-1, + hostname.get(), hostname.Length()); + + // AgeInDays= + searchTerm ageterm("history", sizeof("history") -1, + "AgeInDays", sizeof("AgeInDays")-1, + "is", sizeof("is")-1, + ageString.get(), ageString.Length()); + + searchQuery query; + query.groupBy = 0; + + nsCAutoString findUri; + nsCOMPtr findResource; + + // 2) AgeInDays=&Hostname= + query.terms.AppendElement((void *)&ageterm); + query.terms.AppendElement((void *)&hostterm); + GetFindUriPrefix(query, PR_FALSE, findUri); + + gRDFService->GetResource(findUri.get(), getter_AddRefs(findResource)); + + NotifyUnassert(findResource, kNC_child, aSource); + + // 3) Hostname= + query.terms.Clear(); + + query.terms.AppendElement((void *)&hostterm); + GetFindUriPrefix(query, PR_FALSE, findUri); + + gRDFService->GetResource(findUri.get(), getter_AddRefs(findResource)); + NotifyUnassert(findResource, kNC_child, aSource); + + return NS_OK; +} + +// +// get the user-visible "name" of a find resource +// we basically parse the string, and use the data stored in the last +// term to generate an appropriate string +// +nsresult +nsGlobalHistory::GetFindUriName(const char *aURL, nsIRDFNode **aResult) +{ + + nsresult rv; + + searchQuery query; + rv = FindUrlToSearchQuery(aURL, query); + + // can't exactly get a name if there's nothing to search for + if (query.terms.Count() < 1) + return NS_OK; + + // now build up a string from the query (using only the last term) + searchTerm *term = (searchTerm*)query.terms[query.terms.Count()-1]; + + // automatically build up string in the form + // findurl--[-] + // such as "finduri-AgeInDays-is" or "find-uri-AgeInDays-is-0" + nsAutoString stringName(NS_LITERAL_STRING("finduri-")); + + // property + stringName.Append(NS_ConvertASCIItoUCS2(term->property)); + stringName.Append(PRUnichar('-')); + + // and now the method, such as "is" or "isgreater" + stringName.Append(NS_ConvertASCIItoUCS2(term->method)); + + // try adding - to see if there's a match + // for example, to match + // finduri-LastVisitDate-is-0=Today + PRInt32 preTextLength = stringName.Length(); + stringName.Append(PRUnichar('-')); + stringName.Append(term->text); + stringName.Append(PRUnichar(0)); + + // try to find a localizable string + const PRUnichar *strings[] = { + term->text.get() + }; + nsXPIDLString value; + + // first with the search text + rv = mBundle->FormatStringFromName(stringName.get(), + strings, 1, getter_Copies(value)); + + // ok, try it without the -, to match + // finduri-LastVisitDate-is=%S days ago + if (NS_FAILED(rv)) { + stringName.Truncate(preTextLength); + rv = mBundle->FormatStringFromName(stringName.get(), + strings, 1, getter_Copies(value)); + } + + nsCOMPtr literal; + if (NS_SUCCEEDED(rv)) { + rv = gRDFService->GetLiteral(value, getter_AddRefs(literal)); + } else { + // ok, no such string, so just put the match text itself there + rv = gRDFService->GetLiteral(term->text.get(), + getter_AddRefs(literal)); + } + FreeSearchQuery(query); + + if (NS_FAILED(rv)) return rv; + + *aResult = literal; + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsGlobalHistory::Observe(nsISupports *aSubject, + const char *aTopic, + const PRUnichar *aSomeData) +{ + nsresult rv; + // pref changing - update member vars + if (!nsCRT::strcmp(aTopic, "nsPref:changed")) { + NS_ENSURE_STATE(gPrefBranch); + + // expiration date + if (!nsCRT::strcmp(aSomeData, NS_LITERAL_STRING(PREF_BROWSER_HISTORY_EXPIRE_DAYS).get())) { + gPrefBranch->GetIntPref(PREF_BROWSER_HISTORY_EXPIRE_DAYS, &mExpireDays); + } + else if (!nsCRT::strcmp(aSomeData, NS_LITERAL_STRING(PREF_AUTOCOMPLETE_ONLY_TYPED).get())) { + gPrefBranch->GetBoolPref(PREF_AUTOCOMPLETE_ONLY_TYPED, &mAutocompleteOnlyTyped); + } + } + else if (!nsCRT::strcmp(aTopic, "profile-before-change")) { + rv = CloseDB(); + if (!nsCRT::strcmp(aSomeData, NS_LITERAL_STRING("shutdown-cleanse").get())) { + nsCOMPtr historyFile; + rv = NS_GetSpecialDirectory(NS_APP_HISTORY_50_FILE, getter_AddRefs(historyFile)); + if (NS_SUCCEEDED(rv)) + rv = historyFile->Remove(PR_FALSE); + } + } + else if (!nsCRT::strcmp(aTopic, "profile-do-change")) + rv = OpenDB(); + + return NS_OK; +} + +//---------------------------------------------------------------------- +// +// nsGlobalHistory::URLEnumerator +// +// Implementation + +nsGlobalHistory::URLEnumerator::~URLEnumerator() +{ + nsMemory::Free(mSelectValue); +} + + +PRBool +nsGlobalHistory::URLEnumerator::IsResult(nsIMdbRow* aRow) +{ + if (HasCell(mEnv, aRow, mHiddenColumn)) + return PR_FALSE; + + if (mSelectColumn) { + mdb_err err; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, mURLColumn, &yarn); + if (err != 0) return PR_FALSE; + + // Do bitwise comparison + PRInt32 count = PRInt32(yarn.mYarn_Fill); + if (count != mSelectValueLen) + return PR_FALSE; + + const char* p = (const char*) yarn.mYarn_Buf; + const char* q = (const char*) mSelectValue; + + while (--count >= 0) { + if (*p++ != *q++) + return PR_FALSE; + } + } + + return PR_TRUE; +} + +nsresult +nsGlobalHistory::URLEnumerator::ConvertToISupports(nsIMdbRow* aRow, nsISupports** aResult) +{ + mdb_err err; + + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, mURLColumn, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + // Since the URLEnumerator always returns the value of the URL + // column, we create an RDF resource. + nsresult rv; + nsCOMPtr resource; + const char* startPtr = (const char*) yarn.mYarn_Buf; + rv = gRDFService->GetResource( + nsCAutoString(Substring(startPtr, startPtr+yarn.mYarn_Fill)).get(), + getter_AddRefs(resource)); + if (NS_FAILED(rv)) return rv; + + *aResult = resource; + NS_ADDREF(*aResult); + return NS_OK; +} + +//---------------------------------------------------------------------- +// nsGlobalHistory::SearchEnumerator +// +// Implementation + +nsGlobalHistory::SearchEnumerator::~SearchEnumerator() +{ + nsGlobalHistory::FreeSearchQuery(*mQuery); + delete mQuery; +} + + +// convert the query in mQuery into a find URI +// if there is a groupby= in the query, then convert that +// into the start of another search term +// for example, in the following query with one term: +// +// term[0] = { history, AgeInDays, is, 0 } +// groupby = Hostname +// +// we generate the following uri: +// +// find:datasource=history&match=AgeInDays&method=is&text=0&datasource=history +// &match=Hostname&method=is&text= +// +// and then the caller will append some text after after the "text=" +// +void +nsGlobalHistory::GetFindUriPrefix(const searchQuery& aQuery, + const PRBool aDoGroupBy, + nsACString& aResult) +{ + mdb_err err; + + aResult.Assign("find:"); + PRUint32 length = aQuery.terms.Count(); + PRUint32 i; + + for (i=0; i" + if (aDoGroupBy) { + aResult.Append("&groupby="); + if (err == 0) + aResult.Append((const char*)yarn.mYarn_Buf, yarn.mYarn_Fill); + } + + // put &datasource=history&match=&method=is&text= + else { + // if the query has a groupby= then we want to append that + // field as the last field to match.. caller has to be sure to + // append that! + aResult.Append("&datasource=history"); + + aResult.Append("&match="); + if (err == 0) + aResult.Append((const char*)yarn.mYarn_Buf, yarn.mYarn_Fill); + // herep + aResult.Append("&method=is"); + aResult.Append("&text="); + } + +} + +// +// determines if the given row matches all terms +// +// if there is a "groupBy" column, then we have to remember that we've +// seen a row with the given value in that column, and then make sure +// all future rows with that value in that column DON'T match, no +// matter if they match the terms or not. +PRBool +nsGlobalHistory::SearchEnumerator::IsResult(nsIMdbRow *aRow) +{ + if (HasCell(mEnv, aRow, mHiddenColumn)) + return PR_FALSE; + + mdb_err err; + + mdbYarn groupColumnValue = { nsnull, 0, 0, 0, 0, nsnull}; + if (mQuery->groupBy!=0) { + + // if we have a 'groupby', then we use the hashtable to make sure + // we only match the FIRST row with the column value that we're + // grouping by + + err = aRow->AliasCellYarn(mEnv, mQuery->groupBy, &groupColumnValue); + if (err!=0) return PR_FALSE; + if (!groupColumnValue.mYarn_Buf) return PR_FALSE; + + const char* startPtr = (const char*)groupColumnValue.mYarn_Buf; + nsCStringKey key(Substring(startPtr, + startPtr + groupColumnValue.mYarn_Fill)); + + void *otherRow = mUniqueRows.Get(&key); + + // Hey! we've seen this row before, so ignore it + if (otherRow) return PR_FALSE; + } + + // now do the actual match + if (!mHistory->RowMatches(aRow, mQuery)) + return PR_FALSE; + + if (mQuery->groupBy != 0) { + // we got this far, so we must have matched. + // add ourselves to the hashtable so we don't match rows like this + // in the future + const char* startPtr = (const char*)groupColumnValue.mYarn_Buf; + nsCStringKey key(Substring(startPtr, + startPtr + groupColumnValue.mYarn_Fill)); + + // note - weak ref, don't worry about releasing + mUniqueRows.Put(&key, (void *)aRow); + } + + return PR_TRUE; +} + +// +// determines if the row matches the given terms, used above +// +PRBool +nsGlobalHistory::RowMatches(nsIMdbRow *aRow, + searchQuery *aQuery) +{ + PRUint32 length = aQuery->terms.Count(); + PRUint32 i; + + for (i=0; iterms[i]; + + if (!term->datasource.Equals("history")) + continue; // we only match against history queries + + // use callback if it exists + if (term->match) { + // queue up some values just in case callback needs it + // (how would we do this dynamically?) + matchSearchTerm_t matchSearchTerm = { mEnv, mStore, term , PR_FALSE}; + + if (!term->match(aRow, (void *)&matchSearchTerm)) + return PR_FALSE; + } else { + mdb_err err; + + mdb_column property_column; + nsCAutoString property_name(term->property); + property_name.Append(char(0)); + + err = mStore->QueryToken(mEnv, property_name.get(), &property_column); + if (err != 0) { + NS_WARNING("Unrecognized column!"); + continue; // assume we match??? + } + + // match the term directly against the column? + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, property_column, &yarn); + if (err != 0 || !yarn.mYarn_Buf) return PR_FALSE; + + const char* startPtr; + PRInt32 yarnLength = yarn.mYarn_Fill;; + nsCAutoString titleStr; + if (property_column == kToken_NameColumn) { + titleStr = NS_ConvertUCS2toUTF8((const PRUnichar*)yarn.mYarn_Buf, yarnLength); + startPtr = titleStr.get(); + yarnLength = titleStr.Length(); + } + else { + // account for null strings + if (yarn.mYarn_Buf) + startPtr = (const char *)yarn.mYarn_Buf; + else + startPtr = ""; + } + + const nsASingleFragmentCString& rowVal = + Substring(startPtr, startPtr + yarnLength); + + // set up some iterators + nsASingleFragmentCString::const_iterator start, end; + rowVal.BeginReading(start); + rowVal.EndReading(end); + + NS_ConvertUCS2toUTF8 utf8Value(term->text); + + if (term->method.Equals("is")) { + + if (utf8Value != rowVal) + return PR_FALSE; + } + + else if (term->method.Equals("isnot")) { + if (utf8Value == rowVal) + return PR_FALSE; + } + + else if (term->method.Equals("contains")) { + if (!FindInReadable(utf8Value, start, end)) + return PR_FALSE; + } + + else if (term->method.Equals("doesntcontain")) { + if (FindInReadable(utf8Value, start, end)) + return PR_FALSE; + } + + else if (term->method.Equals("startswith")) { + // need to make sure that the found string is + // at the beginning of the string + nsACString::const_iterator real_start = start; + if (!(FindInReadable(utf8Value, start, end) && + real_start == start)) + return PR_FALSE; + } + + else if (term->method.Equals("endswith")) { + // need to make sure that the found string ends + // at the end of the string + nsACString::const_iterator real_end = end; + if (!(RFindInReadable(utf8Value, start, end) && + real_end == end)) + return PR_FALSE; + } + + else { + NS_WARNING("Unrecognized search method in SearchEnumerator::RowMatches"); + // don't handle other match types like isgreater/etc yet, + // so assume the match failed and bail + return PR_FALSE; + } + + } + } + + // we've gone through each term and didn't bail, so they must have + // all matched! + return PR_TRUE; +} + +// +// return either the row, or another find resource. +// if we're doing grouping, then we don't want to return a real row, +// instead we want to expand the current query into a deeper query +// where we match up the groupby attribute. +// if we're not doing grouping, then we just return the URL for the +// current row +nsresult +nsGlobalHistory::SearchEnumerator::ConvertToISupports(nsIMdbRow* aRow, + nsISupports** aResult) + +{ + mdb_err err; + nsresult rv; + + nsCOMPtr resource; + if (mQuery->groupBy == 0) { + // no column to group by + // just create a resource based on the URL of the current row + mdbYarn yarn; + err = aRow->AliasCellYarn(mEnv, mHistory->kToken_URLColumn, &yarn); + if (err != 0) return NS_ERROR_FAILURE; + + + const char* startPtr = (const char*)yarn.mYarn_Buf; + rv = gRDFService->GetResource( + nsCAutoString(Substring(startPtr, startPtr+yarn.mYarn_Fill)).get(), + getter_AddRefs(resource)); + if (NS_FAILED(rv)) return rv; + + *aResult = resource; + NS_ADDREF(*aResult); + return NS_OK; + } + + // we have a group by, so now we recreate the find url, but add a + // query for the row asked for by groupby + mdbYarn groupByValue; + err = aRow->AliasCellYarn(mEnv, mQuery->groupBy, &groupByValue); + if (err != 0) return NS_ERROR_FAILURE; + + if (mFindUriPrefix.IsEmpty()) + mHistory->GetFindUriPrefix(*mQuery, PR_FALSE, mFindUriPrefix); + + nsCAutoString findUri(mFindUriPrefix); + + const char* startPtr = (const char *)groupByValue.mYarn_Buf; + findUri.Append(Substring(startPtr, startPtr+groupByValue.mYarn_Fill)); + findUri.Append('\0'); + + rv = gRDFService->GetResource(findUri.get(), getter_AddRefs(resource)); + if (NS_FAILED(rv)) return rv; + + *aResult = resource; + NS_ADDREF(*aResult); + return NS_OK; +} + +/* +//---------------------------------------------------------------------- +// +// nsGlobalHistory::AutoCompleteEnumerator +// +// Implementation + +nsGlobalHistory::AutoCompleteEnumerator::~AutoCompleteEnumerator() +{ +} + + +PRBool +nsGlobalHistory::AutoCompleteEnumerator::IsResult(nsIMdbRow* aRow) +{ + if (!HasCell(mEnv, aRow, mTypedColumn)) { + if (mMatchOnlyTyped || HasCell(mEnv, aRow, mHiddenColumn)) + return PR_FALSE; + } + + nsCAutoString url; + mHistory->GetRowValue(aRow, mURLColumn, url); + + NS_ConvertUTF8toUCS2 utf8Url(url); + + PRBool result = mHistory->AutoCompleteCompare(utf8Url, mSelectValue, mExclude); + + return result; +} + +nsresult +nsGlobalHistory::AutoCompleteEnumerator::ConvertToISupports(nsIMdbRow* aRow, nsISupports** aResult) +{ + nsCAutoString url; + mHistory->GetRowValue(aRow, mURLColumn, url); + nsAutoString comments; + mHistory->GetRowValue(aRow, mCommentColumn, comments); + + nsCOMPtr newItem(do_CreateInstance(NS_AUTOCOMPLETEITEM_CONTRACTID)); + NS_ENSURE_TRUE(newItem, NS_ERROR_FAILURE); + + newItem->SetValue(NS_ConvertUTF8toUCS2(url.get())); + newItem->SetParam(aRow); + newItem->SetComment(comments.get()); + + *aResult = newItem; + NS_ADDREF(*aResult); + return NS_OK; +}*/ + +//---------------------------------------------------------------------- +// +// nsIAutoCompleteSession implementation +// + +//---------------------------------------------------------------------- +// +// nsIAutoCompleteSession implementation +// + +NS_IMETHODIMP +nsGlobalHistory::StartSearch(const nsAString &aSearchString, + const nsAString &aSearchParam, + nsIAutoCompleteResult *aPreviousResult, + nsIAutoCompleteObserver *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + NS_ENSURE_STATE(gPrefBranch); + + NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE); + + // if the search string is empty after it has had prefixes removed, then + // there is no need to proceed with the search + nsAutoString cut(aSearchString); + AutoCompleteCutPrefix(cut, nsnull); + if (cut.Length() == 0) + return NS_ERROR_ILLEGAL_VALUE; + + // pass string through filter and then determine which prefixes to exclude + // when chopping prefixes off of history urls during comparison + nsSharableString filtered = AutoCompletePrefilter(aSearchString); + AutocompleteExclude exclude; + AutoCompleteGetExcludeInfo(filtered, &exclude); + + // perform the actual search here + nsIAutoCompleteMdbResult *result = nsnull; + nsresult rv = AutoCompleteSearch(filtered, &exclude, NS_STATIC_CAST(nsIAutoCompleteMdbResult *, aPreviousResult), &result); + NS_ENSURE_SUCCESS(rv, rv); + + aListener->OnSearchResult(this, result); + + return NS_OK; +} + + +NS_IMETHODIMP +nsGlobalHistory::StopSearch() +{ + return NS_OK; +} + +//---------------------------------------------------------------------- +// +// AutoComplete stuff +// + +nsresult +nsGlobalHistory::AutoCompleteSearch(const nsAString &aSearchString, + AutocompleteExclude *aExclude, + nsIAutoCompleteMdbResult *aPrevResult, + nsIAutoCompleteMdbResult **aResult) +{ + // determine if we can skip searching the whole history and only search + // through the previous search results + PRBool searchPrevious = PR_FALSE; + if (aPrevResult) { + nsAutoString prevURLStr; + aPrevResult->GetSearchString(prevURLStr); + // if search string begins with the previous search string, it's a go + searchPrevious = Substring(aSearchString, 0, prevURLStr.Length()).Equals(prevURLStr); + } + + if (searchPrevious) { + // Search through the previous result + PRUint32 matchCount; + aPrevResult->GetMatchCount(&matchCount); + for (PRInt32 i = matchCount-1; i >= 0; --i) { + // Make a copy of the value because AutoCompleteCompare is destructive + nsAutoString url; + aPrevResult->GetValueAt(i, url); + + if (!AutoCompleteCompare(url, aSearchString, aExclude)) + aPrevResult->RemoveRowAt(i); + } + + *aResult = aPrevResult; + } else { + // Search through the entire history + + // Create and initialize a new result object + nsresult rv = NS_OK; + nsCOMPtr result = do_CreateInstance("@mozilla.org/autocomplete/mdb-result;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + result->Init(mEnv, mTable); + result->SetTokens(kToken_URLColumn, nsIAutoCompleteMdbResult::kCharType, kToken_NameColumn, nsIAutoCompleteMdbResult::kUnicharType); + result->SetSearchString(aSearchString); + + // Get a cursor to iterate through all rows in the database + nsCOMPtr rowCursor; + mdb_err err = mTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(rowCursor)); + NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE); + + // Store hits in an auto array initially + nsAutoVoidArray array; + + nsIMdbRow *row = nsnull; + mdb_pos pos; + do { + rowCursor->NextRow(mEnv, &row, &pos); + if (!row) break; + + if (!HasCell(mEnv, row, kToken_TypedColumn)) + if (mAutocompleteOnlyTyped || HasCell(mEnv, row, kToken_HiddenColumn)) + continue; + + nsCAutoString url; + GetRowValue(row, kToken_URLColumn, url); + + NS_ConvertUTF8toUCS2 utf8Url(url); + if (AutoCompleteCompare(utf8Url, aSearchString, aExclude)) + array.AppendElement(row); + } while (row); + + // Setup the structure we pass into the sort function, + // including a set of url prefixes to ignore. These prefixes + // must match with the logic in nsGlobalHistory::nsGlobalHistory(). + NS_NAMED_LITERAL_STRING(prefixHWStr, "http://www."); + NS_NAMED_LITERAL_STRING(prefixHStr, "http://"); + NS_NAMED_LITERAL_STRING(prefixHSWStr, "https://www."); + NS_NAMED_LITERAL_STRING(prefixHSStr, "https://"); + NS_NAMED_LITERAL_STRING(prefixFFStr, "ftp://ftp."); + NS_NAMED_LITERAL_STRING(prefixFStr, "ftp://"); + + // note: the number of prefixes stored in the closure below + // must match with the constant AUTOCOMPLETE_PREFIX_LIST_COUNT + AutoCompleteSortClosure closure; + closure.history = this; + closure.prefixCount = AUTOCOMPLETE_PREFIX_LIST_COUNT; + closure.prefixes[0] = NS_STATIC_CAST(nsAFlatString*, &prefixHWStr); + closure.prefixes[1] = NS_STATIC_CAST(nsAFlatString*, &prefixHStr); + closure.prefixes[2] = NS_STATIC_CAST(nsAFlatString*, &prefixHSWStr); + closure.prefixes[3] = NS_STATIC_CAST(nsAFlatString*, &prefixHSStr); + closure.prefixes[4] = NS_STATIC_CAST(nsAFlatString*, &prefixFFStr); + closure.prefixes[5] = NS_STATIC_CAST(nsAFlatString*, &prefixFStr); + + // Turn auto array into flat array for quick sort, now that we + // know how many items there are + PRUint32 count = array.Count(); + nsIMdbRow** items = new nsIMdbRow*[count]; + PRUint32 i; + for (i = 0; i < count; ++i) + items[i] = (nsIMdbRow*)array.ElementAt(i); + + // sort it + NS_QuickSort(items, count, sizeof(nsIMdbRow*), + AutoCompleteSortComparison, + NS_STATIC_CAST(void*, &closure)); + + // place the sorted array into the autocomplete results + for (i = 0; i < count; ++i) { + result->AddRow(items[i]); + } + + // Determine the result of the search + PRUint32 matchCount; + rv = result->GetMatchCount(&matchCount); + if (matchCount > 0) { + result->SetSearchResult(nsIAutoCompleteResult::RESULT_SUCCESS); + result->SetDefaultIndex(0); + } else { + result->SetSearchResult(nsIAutoCompleteResult::RESULT_NOMATCH); + result->SetDefaultIndex(-1); + } + + delete[] items; + + *aResult = result; + NS_ADDREF(*aResult); + } + + return NS_OK; +} + +// If aURL begins with a protocol or domain prefix from our lists, +// then mark their index in an AutocompleteExclude struct. +void +nsGlobalHistory::AutoCompleteGetExcludeInfo(const nsAString& aURL, AutocompleteExclude* aExclude) +{ + aExclude->schemePrefix = -1; + aExclude->hostnamePrefix = -1; + + PRInt32 index = 0; + PRInt32 i; + for (i = 0; i < mIgnoreSchemes.Count(); ++i) { + nsString* string = mIgnoreSchemes.StringAt(i); + if (Substring(aURL, 0, string->Length()).Equals(*string)) { + aExclude->schemePrefix = i; + index = string->Length(); + break; + } + } + + for (i = 0; i < mIgnoreHostnames.Count(); ++i) { + nsString* string = mIgnoreHostnames.StringAt(i); + if (Substring(aURL, index, string->Length()).Equals(*string)) { + aExclude->hostnamePrefix = i; + break; + } + } +} + +// Cut any protocol and domain prefixes from aURL, except for those which +// are specified in aExclude +void +nsGlobalHistory::AutoCompleteCutPrefix(nsAString& aURL, AutocompleteExclude* aExclude) +{ + // This comparison is case-sensitive. Therefore, it assumes that aUserURL is a + // potential URL whose host name is in all lower case. + PRInt32 idx = 0; + PRInt32 i; + for (i = 0; i < mIgnoreSchemes.Count(); ++i) { + if (aExclude && i == aExclude->schemePrefix) + continue; + nsString* string = mIgnoreSchemes.StringAt(i); + if (Substring(aURL, 0, string->Length()).Equals(*string)) { + idx = string->Length(); + break; + } + } + + if (idx > 0) + aURL.Cut(0, idx); + + idx = 0; + for (i = 0; i < mIgnoreHostnames.Count(); ++i) { + if (aExclude && i == aExclude->hostnamePrefix) + continue; + nsString* string = mIgnoreHostnames.StringAt(i); + if (Substring(aURL, 0, string->Length()).Equals(*string)) { + idx = string->Length(); + break; + } + } + + if (idx > 0) + aURL.Cut(0, idx); +} + +nsSharableString +nsGlobalHistory::AutoCompletePrefilter(const nsAString& aSearchString) +{ + nsAutoString url(aSearchString); + + PRInt32 slash = url.FindChar('/', 0); + if (slash >= 0) { + // if user is typing a url but has already typed past the host, + // then convert the host to lowercase + nsAutoString host; + url.Left(host, slash); + ToLowerCase(host); + url.Assign(host + Substring(url, slash, url.Length()-slash)); + } else { + // otherwise, assume the user could still be typing the host, and + // convert everything to lowercase + ToLowerCase(url); + } + + return nsSharableString(url); +} + +PRBool +nsGlobalHistory::AutoCompleteCompare(nsAString& aHistoryURL, + const nsAString& aUserURL, + AutocompleteExclude* aExclude) +{ + AutoCompleteCutPrefix(aHistoryURL, aExclude); + + return Substring(aHistoryURL, 0, aUserURL.Length()).Equals(aUserURL); +} + +int PR_CALLBACK +nsGlobalHistory::AutoCompleteSortComparison(const void *v1, const void *v2, + void *closureVoid) +{ + // + // NOTE: The design and reasoning behind the following autocomplete + // sort implementation is documented in bug 78270. + // + // cast our function parameters back into their real form + nsIMdbRow *row1 = *(nsIMdbRow**) v1; + nsIMdbRow *row2 = *(nsIMdbRow**) v2; + AutoCompleteSortClosure* closure = + NS_STATIC_CAST(AutoCompleteSortClosure*, closureVoid); + + // get visit counts - we're ignoring all errors from GetRowValue(), + // and relying on default values + PRInt32 item1visits = 0, item2visits = 0; + closure->history->GetRowValue(row1, + closure->history->kToken_VisitCountColumn, + &item1visits); + closure->history->GetRowValue(row2, + closure->history->kToken_VisitCountColumn, + &item2visits); + + // get URLs + nsAutoString url1, url2; + closure->history->GetRowValue(row1, closure->history->kToken_URLColumn, url1); + closure->history->GetRowValue(row2, closure->history->kToken_URLColumn, url2); + + // Favour websites and webpaths more than webpages by boosting + // their visit counts. This assumes that URLs have been normalized, + // appending a trailing '/'. + // + // We use addition to boost the visit count rather than multiplication + // since we want URLs with large visit counts to remain pretty much + // in raw visit count order - we assume the user has visited these urls + // often for a reason and there shouldn't be a problem with putting them + // high in the autocomplete list regardless of whether they are sites/ + // paths or pages. However for URLs visited only a few times, sites + // & paths should be presented before pages since they are generally + // more likely to be visited again. + // + PRBool isPath1 = PR_FALSE, isPath2 = PR_FALSE; + if (!url1.IsEmpty()) + { + // url is a site/path if it has a trailing slash + isPath1 = (url1.Last() == PRUnichar('/')); + if (isPath1) + item1visits += AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST; + } + if (!url2.IsEmpty()) + { + // url is a site/path if it has a trailing slash + isPath2 = (url2.Last() == PRUnichar('/')); + if (isPath2) + item2visits += AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST; + } + + // primary sort by visit count + if (item1visits != item2visits) + { + // return visit count comparison + return item2visits - item1visits; + } + else + { + // Favour websites and webpaths more than webpages + if (isPath1 && !isPath2) return -1; // url1 is a website/path, url2 isn't + if (!isPath1 && isPath2) return 1; // url1 isn't a website/path, url2 is + + // We have two websites/paths.. ignore "http[s]://[www.]" & "ftp://[ftp.]" + // prefixes. Find a starting position in the string, just past any of the + // above prefixes. Only check for the prefix once, in the far left of the + // string - it is assumed there is no whitespace. + PRInt32 postPrefix1 = 0, postPrefix2 = 0; + + size_t i; + // iterate through our prefixes looking for a match + for (i=0; iprefixCount; i++) + { + // Check if string is prefixed. Note: the parameters of the Find() + // method specify the url is searched at the 0th character and if there + // is no match the rest of the url is not searched. + if (url1.Find((*closure->prefixes[i]), 0, 1) == 0) + { + // found a match - record post prefix position + postPrefix1 = closure->prefixes[i]->Length(); + // bail out of the for loop + break; + } + } + + // iterate through our prefixes looking for a match + for (i=0; iprefixCount; i++) + { + // Check if string is prefixed. Note: the parameters of the Find() + // method specify the url is searched at the 0th character and if there + // is no match the rest of the url is not searched. + if (url2.Find((*closure->prefixes[i]), 0, 1) == 0) + { + // found a match - record post prefix position + postPrefix2 = closure->prefixes[i]->Length(); + // bail out of the for loop + break; + } + } + + // compare non-prefixed urls + PRInt32 ret = Compare( + Substring(url1, postPrefix1, url1.Length()), + Substring(url2, postPrefix2, url2.Length())); + if (ret != 0) return ret; + + // sort http://xyz.com before http://www.xyz.com + return postPrefix1 - postPrefix2; + } + return 0; +} diff --git a/mozilla/browser/components/history/src/nsGlobalHistory.h b/mozilla/browser/components/history/src/nsGlobalHistory.h new file mode 100644 index 00000000000..17396109657 --- /dev/null +++ b/mozilla/browser/components/history/src/nsGlobalHistory.h @@ -0,0 +1,465 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Chris Waterson + * Blake Ross + * Joe Hewitt + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the NPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef nsglobalhistory__h____ +#define nsglobalhistory__h____ + +#include "nsIGlobalHistory.h" +#include "nsIBrowserHistory.h" + +#include "mdb.h" +#include "nsIObserver.h" +#include "nsIPrefBranch.h" +#include "nsIRDFDataSource.h" +#include "nsIRDFRemoteDataSource.h" +#include "nsIRDFService.h" +#include "nsISupportsArray.h" +#include "nsIStringBundle.h" +#include "nsWeakReference.h" +#include "nsVoidArray.h" +#include "nsHashtable.h" +#include "nsCOMPtr.h" +#include "nsAString.h" +#include "nsSharableString.h" +#include "nsITimer.h" +#include "nsIAutoCompleteSearch.h" +#include "nsIAutoCompleteResult.h" +#include "nsIAutoCompleteResultTypes.h" + +//---------------------------------------------------------------------- +// +// nsMdbTableEnumerator +// +// An nsISimpleEnumerator implementation that returns the value of +// a column as an nsISupports. Allows for some simple selection. +// + +class nsMdbTableEnumerator : public nsISimpleEnumerator +{ +protected: + nsMdbTableEnumerator(); + virtual ~nsMdbTableEnumerator(); + + nsIMdbEnv* mEnv; + +private: + // subclasses should not tweak these + nsIMdbTable* mTable; + nsIMdbTableRowCursor* mCursor; + nsIMdbRow* mCurrent; + +public: + // nsISupports methods + NS_DECL_ISUPPORTS + + // nsISimpleEnumeratorMethods + NS_IMETHOD HasMoreElements(PRBool* _result); + NS_IMETHOD GetNext(nsISupports** _result); + + // Implementation methods + virtual nsresult Init(nsIMdbEnv* aEnv, nsIMdbTable* aTable); + +protected: + virtual PRBool IsResult(nsIMdbRow* aRow) = 0; + virtual nsresult ConvertToISupports(nsIMdbRow* aRow, nsISupports** aResult) = 0; +}; + +typedef PRBool (*rowMatchCallback)(nsIMdbRow *aRow, void *closure); + +struct matchHost_t; +struct searchQuery; +class searchTerm; + +// Number of prefixes used in the autocomplete sort comparison function +#define AUTOCOMPLETE_PREFIX_LIST_COUNT 6 +// Size of visit count boost to give to urls which are sites or paths +#define AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST 5 + +//---------------------------------------------------------------------- +// +// nsGlobalHistory +// +// This class is the browser's implementation of the +// nsIGlobalHistory interface. +// + + +// Used to describe what prefixes shouldn't be cut from +// history urls when doing an autocomplete url comparison. +struct AutocompleteExclude { + PRInt32 schemePrefix; + PRInt32 hostnamePrefix; +}; + +class nsGlobalHistory : nsSupportsWeakReference, + public nsIGlobalHistory, + public nsIBrowserHistory, + public nsIObserver, + public nsIRDFDataSource, + public nsIRDFRemoteDataSource, + public nsIAutoCompleteSearch +{ +public: + // nsISupports methods + NS_DECL_ISUPPORTS + + NS_DECL_NSIGLOBALHISTORY + NS_DECL_NSIBROWSERHISTORY + NS_DECL_NSIOBSERVER + NS_DECL_NSIRDFDATASOURCE + NS_DECL_NSIRDFREMOTEDATASOURCE + NS_DECL_NSIAUTOCOMPLETESEARCH + + NS_METHOD Init(); + + nsGlobalHistory(void); + virtual ~nsGlobalHistory(); + + // these must be public so that the callbacks can call them + PRBool MatchExpiration(nsIMdbRow *row, PRInt64* expirationDate); + PRBool MatchHost(nsIMdbRow *row, matchHost_t *hostInfo); + PRBool RowMatches(nsIMdbRow* aRow, searchQuery *aQuery); + +protected: + + // + // database junk + // + enum eCommitType + { + kLargeCommit = 0, + kSessionCommit = 1, + kCompressCommit = 2 + }; + + PRInt64 mFileSizeOnDisk; + nsresult OpenDB(); + nsresult OpenExistingFile(nsIMdbFactory *factory, const char *filePath); + nsresult OpenNewFile(nsIMdbFactory *factory, const char *filePath); + nsresult CreateTokens(); + nsresult CloseDB(); + nsresult CheckHostnameEntries(); + nsresult Commit(eCommitType commitType); + + // + // expiration/removal stuff + // + PRInt32 mExpireDays; + nsresult ExpireEntries(PRBool notify); + nsresult RemoveMatchingRows(rowMatchCallback aMatchFunc, + void *aClosure, PRBool notify); + + // + // search stuff - find URL stuff, etc + // + nsresult GetRootDayQueries(nsISimpleEnumerator **aResult); + nsresult GetFindUriName(const char *aURL, nsIRDFNode **aResult); + nsresult CreateFindEnumerator(nsIRDFResource *aSource, + nsISimpleEnumerator **aResult); + + static nsresult FindUrlToTokenList(const char *aURL, nsVoidArray& aResult); + static void FreeTokenList(nsVoidArray& tokens); + static void FreeSearchQuery(searchQuery& aQuery); + static PRBool IsFindResource(nsIRDFResource *aResource); + void GetFindUriPrefix(const searchQuery& aQuery, + const PRBool aDoGroupBy, + nsACString& aResult); + + nsresult TokenListToSearchQuery(const nsVoidArray& tokens, + searchQuery& aResult); + nsresult FindUrlToSearchQuery(const char *aURL, searchQuery& aResult); + nsresult NotifyFindAssertions(nsIRDFResource *aSource, nsIMdbRow *aRow); + nsresult NotifyFindUnassertions(nsIRDFResource *aSource, nsIMdbRow *aRow); + + // + // autocomplete stuff + // + PRBool mAutocompleteOnlyTyped; + nsStringArray mIgnoreSchemes; + nsStringArray mIgnoreHostnames; + + nsresult AutoCompleteSearch(const nsAString& aSearchString, + AutocompleteExclude* aExclude, + nsIAutoCompleteMdbResult* aPrevResult, + nsIAutoCompleteMdbResult** aResult); + void AutoCompleteCutPrefix(nsAString& aURL, AutocompleteExclude* aExclude); + void AutoCompleteGetExcludeInfo(const nsAString& aURL, AutocompleteExclude* aExclude); + nsSharableString AutoCompletePrefilter(const nsAString& aSearchString); + PRBool AutoCompleteCompare(nsAString& aHistoryURL, + const nsAString& aUserURL, + AutocompleteExclude* aExclude); + PR_STATIC_CALLBACK(int) + AutoCompleteSortComparison(const void *v1, const void *v2, void *closureVoid); + + // AutoCompleteSortClosure - used to pass info into + // AutoCompleteSortComparison from the NS_QuickSort() function + struct AutoCompleteSortClosure + { + nsGlobalHistory* history; + size_t prefixCount; + nsAFlatString* prefixes[AUTOCOMPLETE_PREFIX_LIST_COUNT]; + }; + + // caching of PR_Now() so we don't call it every time we do + // a history query + PRInt64 mLastNow; // cache the last PR_Now() + PRInt32 mBatchesInProgress; + PRBool mNowValid; // is mLastNow valid? + nsCOMPtr mExpireNowTimer; + + PRInt64 GetNow(); + void ExpireNow(); + + static void expireNowTimer(nsITimer *aTimer, void *aClosure) + {((nsGlobalHistory *)aClosure)->ExpireNow(); } + + // + // sync stuff to write the db to disk every so often + // + PRBool mDirty; // if we've changed history + nsCOMPtr mSyncTimer; + + void Sync(); + nsresult SetDirty(); + + static void fireSyncTimer(nsITimer *aTimer, void *aClosure) + {((nsGlobalHistory *)aClosure)->Sync(); } + + // + // RDF stuff + // + nsCOMPtr mObservers; + + PRBool IsURLInHistory(nsIRDFResource* aResource); + + nsresult NotifyAssert(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aValue); + nsresult NotifyUnassert(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aValue); + nsresult NotifyChange(nsIRDFResource* aSource, nsIRDFResource* aProperty, nsIRDFNode* aOldValue, nsIRDFNode* aNewValue); + + // + // row-oriented stuff + // + + // N.B., these are MDB interfaces, _not_ XPCOM interfaces. + nsIMdbEnv* mEnv; // OWNER + nsIMdbStore* mStore; // OWNER + nsIMdbTable* mTable; // OWNER + + nsCOMPtr mMetaRow; + + mdb_scope kToken_HistoryRowScope; + mdb_kind kToken_HistoryKind; + + mdb_column kToken_URLColumn; + mdb_column kToken_ReferrerColumn; + mdb_column kToken_LastVisitDateColumn; + mdb_column kToken_FirstVisitDateColumn; + mdb_column kToken_VisitCountColumn; + mdb_column kToken_NameColumn; + mdb_column kToken_HostnameColumn; + mdb_column kToken_HiddenColumn; + mdb_column kToken_TypedColumn; + + // meta-data tokens + mdb_column kToken_LastPageVisited; + + // + // AddPage-oriented stuff + // + nsresult AddPageToDatabase(const char *aURL, + PRInt64 aDate); + nsresult AddExistingPageToDatabase(nsIMdbRow *row, + PRInt64 aDate, + PRInt64 *aOldDate, + PRInt32 *aOldCount); + nsresult AddNewPageToDatabase(const char *aURL, + PRInt64 aDate, + nsIMdbRow **aResult); + // + // generic routines for setting/retrieving various datatypes + // + nsresult SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRInt64& aValue); + nsresult SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRInt32 aValue); + nsresult SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const char *aValue); + nsresult SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRUnichar *aValue); + + nsresult GetRowValue(nsIMdbRow *aRow, mdb_column aCol, nsAString& aResult); + nsresult GetRowValue(nsIMdbRow *aRow, mdb_column aCol, nsACString& aResult); + nsresult GetRowValue(nsIMdbRow *aRow, mdb_column aCol, PRInt64* aResult); + nsresult GetRowValue(nsIMdbRow *aRow, mdb_column aCol, PRInt32* aResult); + + nsresult FindRow(mdb_column aCol, const char *aURL, nsIMdbRow **aResult); + + // + // misc unrelated stuff + // + nsCOMPtr mBundle; + nsresult SaveLastPageVisited(const char *); + + // pseudo-constants. although the global history really is a + // singleton, we'll use this metaphor to be consistent. + static PRInt32 gRefCnt; + static nsIRDFService* gRDFService; + static nsIRDFResource* kNC_Page; // XXX do we need? + static nsIRDFResource* kNC_Date; + static nsIRDFResource* kNC_FirstVisitDate; + static nsIRDFResource* kNC_VisitCount; + static nsIRDFResource* kNC_AgeInDays; + static nsIRDFResource* kNC_Name; + static nsIRDFResource* kNC_NameSort; + static nsIRDFResource* kNC_Hostname; + static nsIRDFResource* kNC_Referrer; + static nsIRDFResource* kNC_child; + static nsIRDFResource* kNC_URL; // XXX do we need? + static nsIRDFResource* kNC_HistoryRoot; + static nsIRDFResource* kNC_HistoryByDate; + + static nsIMdbFactory* gMdbFactory; + static nsIPrefBranch* gPrefBranch; + // + // custom enumerators + // + + // URLEnumerator - for searching for a specific set of rows which + // match a particular column + class URLEnumerator : public nsMdbTableEnumerator + { + protected: + mdb_column mURLColumn; + mdb_column mHiddenColumn; + mdb_column mSelectColumn; + void* mSelectValue; + PRInt32 mSelectValueLen; + + virtual ~URLEnumerator(); + + public: + URLEnumerator(mdb_column aURLColumn, + mdb_column aHiddenColumn, + mdb_column aSelectColumn = mdb_column(0), + void* aSelectValue = nsnull, + PRInt32 aSelectValueLen = 0) : + mURLColumn(aURLColumn), + mHiddenColumn(aHiddenColumn), + mSelectColumn(aSelectColumn), + mSelectValue(aSelectValue), + mSelectValueLen(aSelectValueLen) + {} + + protected: + virtual PRBool IsResult(nsIMdbRow* aRow); + virtual nsresult ConvertToISupports(nsIMdbRow* aRow, nsISupports** aResult); + }; + + // SearchEnumerator - for matching a set of rows based on a search query + class SearchEnumerator : public nsMdbTableEnumerator + { + public: + SearchEnumerator(searchQuery *aQuery, + mdb_column aHiddenColumn, + nsGlobalHistory *aHistory) : + mQuery(aQuery), + mHiddenColumn(aHiddenColumn), + mHistory(aHistory) + {} + + virtual ~SearchEnumerator(); + + protected: + searchQuery *mQuery; + mdb_column mHiddenColumn; + nsGlobalHistory *mHistory; + nsHashtable mUniqueRows; + + nsCString mFindUriPrefix; + + virtual PRBool IsResult(nsIMdbRow* aRow); + virtual nsresult ConvertToISupports(nsIMdbRow* aRow, + nsISupports** aResult); + + PRBool RowMatches(nsIMdbRow* aRow, searchQuery *aQuery); + }; + + /*// AutoCompleteEnumerator - for searching for a partial url match + class AutoCompleteEnumerator : public nsMdbTableEnumerator + { + protected: + nsGlobalHistory* mHistory; + mdb_column mURLColumn; + mdb_column mHiddenColumn; + mdb_column mTypedColumn; + mdb_column mCommentColumn; + AutocompleteExclude* mExclude; + const nsAString& mSelectValue; + PRBool mMatchOnlyTyped; + + virtual ~AutoCompleteEnumerator(); + + public: + AutoCompleteEnumerator(nsGlobalHistory* aHistory, + mdb_column aURLColumn, + mdb_column aCommentColumn, + mdb_column aHiddenColumn, + mdb_column aTypedColumn, + PRBool aMatchOnlyTyped, + const nsAString& aSelectValue, + AutocompleteExclude* aExclude) : + mHistory(aHistory), + mURLColumn(aURLColumn), + mHiddenColumn(aHiddenColumn), + mTypedColumn(aTypedColumn), + mCommentColumn(aCommentColumn), + mExclude(aExclude), + mSelectValue(aSelectValue), + mMatchOnlyTyped(aMatchOnlyTyped) {} + + protected: + virtual PRBool IsResult(nsIMdbRow* aRow); + virtual nsresult ConvertToISupports(nsIMdbRow* aRow, nsISupports** aResult); + };*/ + + + friend class URLEnumerator; + friend class SearchEnumerator; +// friend class AutoCompleteEnumerator; +}; + + +#endif // nsglobalhistory__h____ diff --git a/mozilla/browser/components/history/src/nsHistoryLoadListener.cpp b/mozilla/browser/components/history/src/nsHistoryLoadListener.cpp new file mode 100644 index 00000000000..bb021143727 --- /dev/null +++ b/mozilla/browser/components/history/src/nsHistoryLoadListener.cpp @@ -0,0 +1,177 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are + * Copyright (C) 1998 Netscape Communications Corporation. All + * Rights Reserved. + * + * Original Author(s): + * Alec Flett + * + * Contributor(s): + */ + +#include "nsHistoryLoadListener.h" +#include "nsIGlobalHistory.h" + +#include "nsIServiceManager.h" +#include "nsICategoryManager.h" +#include "nsString.h" +#include "nsXPIDLString.h" + +#include "nsIDocumentLoader.h" +#include "nsIWebProgress.h" +#include "nsIRequestObserver.h" +#include "nsCURILoader.h" + + +#include "nsIDOMWindow.h" +#include "nsIDOMHTMLDocument.h" +#include "nsIDocument.h" +#include "nsIURI.h" + +static NS_DEFINE_IID(kDocLoaderServiceCID, NS_DOCUMENTLOADER_SERVICE_CID); + +nsHistoryLoadListener::nsHistoryLoadListener(nsIBrowserHistory *aHistory) +{ + NS_INIT_ISUPPORTS(); + mHistory = aHistory; + printf("Creating nsHistoryLoadListener\n"); +} + +nsHistoryLoadListener::~nsHistoryLoadListener() +{ + +} + +nsresult +nsHistoryLoadListener::Init() +{ + nsresult rv; + + // the global docloader + nsCOMPtr docLoaderService = + do_GetService(kDocLoaderServiceCID, &rv); + if (NS_FAILED(rv)) return rv; + + printf("Have docloader\n"); + + nsCOMPtr progress = + do_QueryInterface(docLoaderService, &rv); + if (NS_FAILED(rv)) return rv; + + printf("have web progress\n"); + + NS_ADDREF_THIS(); + rv = progress->AddProgressListener(NS_STATIC_CAST(nsIWebProgressListener*, + this), + nsIWebProgress::NOTIFY_STATE_DOCUMENT); + printf("\tSuccess: %8.8X\n", rv); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS2(nsHistoryLoadListener, + nsIWebProgressListener, + nsISupportsWeakReference) + +/* void onStateChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aStateFlags, in nsresult aStatus); */ +NS_IMETHODIMP +nsHistoryLoadListener::OnStateChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + PRUint32 aStateFlags, nsresult aStatus) +{ + // we only care about finishing up documents + if (! (aStateFlags & nsIWebProgressListener::STATE_STOP)) + return NS_OK; + + nsresult rv; + + nsCOMPtr window; + rv = aWebProgress->GetDOMWindow(getter_AddRefs(window)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr domDoc; + + rv = window->GetDocument(getter_AddRefs(domDoc)); + nsCOMPtr doc = do_QueryInterface(domDoc); + if (!doc) return NS_OK; + + nsCOMPtr uri; + rv = doc->GetDocumentURL(getter_AddRefs(uri)); + if (NS_FAILED(rv)) return rv; + + nsXPIDLCString urlString; + uri->GetSpec(getter_Copies(urlString)); + + if (aStatus == NS_BINDING_REDIRECTED) { + + mHistory->HidePage(urlString); + + } else { + nsCOMPtr htmlDoc = do_QueryInterface(doc); + + // there should be a better way to handle non-html docs + if (!htmlDoc) return NS_OK; + // somehow get the title, and store it in history + nsAutoString title; + htmlDoc->GetTitle(title); + + mHistory->SetPageTitle(urlString, title.get()); + +#if 0 + nsAutoString referrer; + htmlDoc->GetReferrer(referrer); + mHistory->SetReferrer(urlString, referrer.get()); +#endif + } + printf("nsHistoryLoadListener::OnStateChange(w,r, %8.8X, %d)\n", aStateFlags, aStatus); + return NS_OK; +} + +/* void onProgressChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in long aCurSelfProgress, in long aMaxSelfProgress, in long aCurTotalProgress, in long aMaxTotalProgress); */ +NS_IMETHODIMP +nsHistoryLoadListener::OnProgressChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + PRInt32 aCurSelfProgress, + PRInt32 aMaxSelfProgress, + PRInt32 aCurTotalProgress, + PRInt32 aMaxTotalProgress) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +/* void onLocationChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsIURI location); */ +NS_IMETHODIMP nsHistoryLoadListener::OnLocationChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsIURI *location) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +/* void onStatusChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in nsresult aStatus, in wstring aMessage); */ +NS_IMETHODIMP nsHistoryLoadListener::OnStatusChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, nsresult aStatus, const PRUnichar *aMessage) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +/* void onSecurityChange (in nsIWebProgress aWebProgress, in nsIRequest aRequest, in unsigned long state); */ +NS_IMETHODIMP nsHistoryLoadListener::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRUint32 state) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + diff --git a/mozilla/browser/components/history/src/nsHistoryLoadListener.h b/mozilla/browser/components/history/src/nsHistoryLoadListener.h new file mode 100644 index 00000000000..a99c7d15660 --- /dev/null +++ b/mozilla/browser/components/history/src/nsHistoryLoadListener.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Mozilla Communicator client code. + * + * The Initial Developer of the Original Code is Netscape Communications + * Corporation. Portions created by Netscape are + * Copyright (C) 1998 Netscape Communications Corporation. All + * Rights Reserved. + * + * Original Author(s): + * Alec Flett + * + * Contributor(s): + */ + + +#include "nsIWebProgressListener.h" +#include "nsIBrowserHistory.h" +#include "nsCOMPtr.h" +#include "nsIComponentManager.h" +#include "nsWeakReference.h" +#include "nsIGenericFactory.h" + +class nsHistoryLoadListener : public nsIWebProgressListener, + public nsSupportsWeakReference +{ + public: + nsHistoryLoadListener(nsIBrowserHistory *); + virtual ~nsHistoryLoadListener(); + + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBPROGRESSLISTENER + + protected: + nsCOMPtr mHistory; + +}; + diff --git a/mozilla/browser/components/history/src/nsMdbPtr.h b/mozilla/browser/components/history/src/nsMdbPtr.h new file mode 100644 index 00000000000..0259f3afd40 --- /dev/null +++ b/mozilla/browser/components/history/src/nsMdbPtr.h @@ -0,0 +1,244 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: NPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Netscape Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the NPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the NPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/* + + DO NOT USE THIS. IT IS INTENDED FOR TEMPORARY USE BY GLOBAL HISTORY, + PENDING CONVERSION OF THE MDB INTERFACES TO XPCOM. + +*/ + + +#ifndef nsMdbPtr_h__ +#define nsMdbPtr_h__ + +#include "mdb.h" + +template +class nsMdbDerivedSafe : public T +{ +private: + virtual mdb_err AddStrongRef(nsIMdbEnv* aEnv) { return 0; } // NOT TO BE IMPLEMENTED + virtual mdb_err CutStrongRef(nsIMdbEnv* aEnv) { return 0; } // NOT TO BE IMPLEMENTED + virtual nsMdbDerivedSafe& operator=(const T&) { return *this; } // NOT TO BE IMPLEMENTED + void operator delete(void*, size_t) {} // NOT TO BE IMPLEMENTED +}; + + +template +class nsMdbPtr +{ +private: + nsIMdbEnv* mEnv; + T* mRawPtr; + +public: + nsMdbPtr(nsIMdbEnv* aEnv) : mEnv(aEnv), mRawPtr(0) + { + NS_PRECONDITION(aEnv != 0, "null ptr"); + } + + nsMdbPtr(nsIMdbEnv* aEnv, T* aRawPtr) : mEnv(aEnv), mRawPtr(0) + { + NS_PRECONDITION(aEnv != 0, "null ptr"); + if (mEnv) { + if (aRawPtr) { + mRawPtr = aRawPtr; + mRawPtr->AddStrongRef(mEnv); + } + } + } + + nsMdbPtr(const nsMdbPtr& aSmartPtr) : mEnv(aSmartPtr.mEnv), mRawPtr(0) + { + if (mEnv) { + if (aSmartPtr) { + mRawPtr = aSmartPtr; + mRawPtr->AddStrongRef(mEnv); + } + } + } + + nsMdbPtr& + operator=(const nsMdbPtr& aSmartPtr) + { + if (mEnv) { + if (mRawPtr) { + mRawPtr->CutStrongRef(mEnv); + mRawPtr = 0; + } + } + mEnv = aSmartPtr.mEnv; + if (mEnv) { + mRawPtr = aSmartPtr.mRawPtr; + if (mRawPtr) + mRawPtr->AddStrongRef(mEnv); + } + + return *this; + } + + ~nsMdbPtr() + { + if (mEnv) { + if (mRawPtr) + mRawPtr->CutStrongRef(mEnv); + } + } + + nsMdbDerivedSafe* + get() const + { + return NS_REINTERPRET_CAST(nsMdbDerivedSafe*, mRawPtr); + } + + nsMdbDerivedSafe* + operator->() const + { + return get(); + } + + + nsMdbDerivedSafe& + operator*() const + { + return *get(); + } + + operator nsMdbDerivedSafe*() const + { + return get(); + } + + + T** + StartAssignment() + { + if (mRawPtr) { + mRawPtr->CutStrongRef(mEnv); + mRawPtr = 0; + } + return &mRawPtr; + } +}; + +template +inline +PRBool +operator==(const nsMdbPtr& lhs, const nsMdbPtr& rhs) +{ + return NS_STATIC_CAST(const void*, lhs.get()) == NS_STATIC_CAST(const void*, rhs.get()); +} + +template +inline +PRBool +operator==(const nsMdbPtr& lhs, const U* rhs) +{ + return NS_STATIC_CAST(const void*, lhs.get()) == NS_STATIC_CAST(const void*, rhs); +} + + +template +inline +PRBool +operator==(const U* lhs, const nsMdbPtr& rhs) +{ + return NS_STATIC_CAST(const void*, lhs) == NS_STATIC_CAST(const void*, rhs.get()); +} + + + + +template +inline +PRBool +operator!=(const nsMdbPtr& lhs, const nsMdbPtr& rhs) +{ + return NS_STATIC_CAST(const void*, lhs.get()) != NS_STATIC_CAST(const void*, rhs.get()); +} + +template +inline +PRBool +operator!=(const nsMdbPtr& lhs, const U* rhs) +{ + return NS_STATIC_CAST(const void*, lhs.get()) != NS_STATIC_CAST(const void*, rhs); +} + + +template +inline +PRBool +operator!=(const U* lhs, const nsMdbPtr& rhs) +{ + return NS_STATIC_CAST(const void*, lhs) != NS_STATIC_CAST(const void*, rhs.get()); +} + + + +template +class nsGetterAcquires +{ +private: + nsMdbPtr& mTargetSmartPtr; + +public: + explicit + nsGetterAcquires(nsMdbPtr& aSmartPtr) : mTargetSmartPtr(aSmartPtr) + { + // nothing else to do + } + + operator T**() + { + return mTargetSmartPtr.StartAssignment(); + } +}; + + +template +inline +nsGetterAcquires +getter_Acquires(nsMdbPtr& aSmartPtr) +{ + return nsGetterAcquires(aSmartPtr); +} + + +#endif // nsMdbPtr_h__ +