mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-09-19 19:50:18 +02:00
4632 lines
133 KiB
C++
4632 lines
133 KiB
C++
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||
|
/* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||
|
*
|
||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||
|
* the License. You may obtain a copy of the License at
|
||
|
* http://www.mozilla.org/MPL/
|
||
|
*
|
||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||
|
* for the specific language governing rights and limitations under the
|
||
|
* License.
|
||
|
*
|
||
|
* The Original Code is Mozilla 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 <waterson@netscape.com>
|
||
|
* Pierre Phaneuf <pp@ludusdesign.com>
|
||
|
* Joe Hewitt <hewitt@netscape.com>
|
||
|
* Blake Ross <blaker@netscape.com>
|
||
|
* Chris Sears <cbsears_sf@yahoo.com>
|
||
|
* Michael Lowe <michael.lowe@bigfoot.com>
|
||
|
*
|
||
|
* Alternatively, the contents of this file may be used under the terms of
|
||
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||
|
* of those above. If you wish to allow use of your version of this file only
|
||
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||
|
* use your version of this file under the terms of the MPL, indicate your
|
||
|
* decision by deleting the provisions above and replace them with the notice
|
||
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||
|
* the provisions above, a recipient may use your version of this file under
|
||
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
||
|
*
|
||
|
* ***** END LICENSE BLOCK ***** */
|
||
|
|
||
|
/*
|
||
|
|
||
|
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 "nsCRT.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 "nsCOMArray.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 "nsIPrefBranch2.h"
|
||
|
|
||
|
#include "nsIObserverService.h"
|
||
|
#include "nsITextToSubURI.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_HistoryByDateAndSite;
|
||
|
nsIRDFResource* nsGlobalHistory::kNC_HistoryByDate;
|
||
|
nsIRDFResource* nsGlobalHistory::kNC_DayFolderIndex;
|
||
|
nsIMdbFactory* nsGlobalHistory::gMdbFactory = nsnull;
|
||
|
nsIPrefBranch* nsGlobalHistory::gPrefBranch = nsnull;
|
||
|
|
||
|
#define PREF_BRANCH_BASE "browser."
|
||
|
#define PREF_BROWSER_HISTORY_EXPIRE_DAYS "history_expire_days"
|
||
|
#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="
|
||
|
|
||
|
// see bug #319004 -- clamp title and URL to generously-large but not too large
|
||
|
// length
|
||
|
#define HISTORY_URI_LENGTH_MAX 65536
|
||
|
#define HISTORY_TITLE_LENGTH_MAX 4096
|
||
|
|
||
|
// 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 {
|
||
|
PRTime *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?
|
||
|
PRTime 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);
|
||
|
nsresult rv;
|
||
|
nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
|
||
|
if (NS_SUCCEEDED(rv))
|
||
|
textToSubURI->UnEscapeAndConvert("UTF-8", PromiseFlatCString(Substring(aText, aText + aTextLen)).get(), getter_Copies(text));
|
||
|
}
|
||
|
~searchTerm() {
|
||
|
MOZ_COUNT_DTOR(searchTerm);
|
||
|
}
|
||
|
|
||
|
nsDependentCSubstring datasource; // should always be "history" ?
|
||
|
nsDependentCSubstring property; // AgeInDays, Hostname, etc
|
||
|
nsDependentCSubstring method; // is, isgreater, isless
|
||
|
nsXPIDLString 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 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 PRTime
|
||
|
NormalizeTime(PRTime 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(PRTime aNormalizedNow, PRTime aDate)
|
||
|
{
|
||
|
PRTime dateMidnight = NormalizeTime(aDate);
|
||
|
|
||
|
PRTime 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, PRTime* 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;
|
||
|
|
||
|
PRTime 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;
|
||
|
// Need to create an nsAutoString to use ToInteger
|
||
|
matchSearchTerm->intValue = nsAutoString(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
|
||
|
|
||
|
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;
|
||
|
|
||
|
PRTime rowDate;
|
||
|
PR_sscanf((const char*)yarn.mYarn_Buf, "%lld", &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, PR_TRUE);
|
||
|
}
|
||
|
//----------------------------------------------------------------------
|
||
|
|
||
|
nsMdbTableEnumerator::nsMdbTableEnumerator()
|
||
|
: mEnv(nsnull),
|
||
|
mTable(nsnull),
|
||
|
mCursor(nsnull),
|
||
|
mCurrent(nsnull)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
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."));
|
||
|
|
||
|
mTypedHiddenURIs.Init(3);
|
||
|
}
|
||
|
|
||
|
nsGlobalHistory::~nsGlobalHistory()
|
||
|
{
|
||
|
gRDFService->UnregisterDataSource(this);
|
||
|
|
||
|
nsresult rv;
|
||
|
rv = CloseDB();
|
||
|
|
||
|
NS_IF_RELEASE(mTable);
|
||
|
NS_IF_RELEASE(mStore);
|
||
|
|
||
|
if (--gRefCnt == 0) {
|
||
|
NS_IF_RELEASE(gRDFService);
|
||
|
|
||
|
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_HistoryByDateAndSite);
|
||
|
NS_IF_RELEASE(kNC_HistoryByDate);
|
||
|
NS_IF_RELEASE(kNC_DayFolderIndex);
|
||
|
|
||
|
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,
|
||
|
nsIGlobalHistory2,
|
||
|
nsIBrowserHistory,
|
||
|
nsIObserver,
|
||
|
nsISupportsWeakReference,
|
||
|
nsIRDFDataSource,
|
||
|
nsIRDFRemoteDataSource,
|
||
|
nsIAutoCompleteSearch)
|
||
|
|
||
|
//----------------------------------------------------------------------
|
||
|
//
|
||
|
// nsGlobalHistory
|
||
|
//
|
||
|
// nsIGlobalHistory2 methods
|
||
|
//
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsGlobalHistory::AddURI(nsIURI *aURI, PRBool aRedirect, PRBool aTopLevel, nsIURI *aReferrer)
|
||
|
{
|
||
|
PRTime now = GetNow();
|
||
|
|
||
|
return AddPageToDatabase(aURI, aRedirect, aTopLevel, now, aReferrer);
|
||
|
}
|
||
|
|
||
|
nsresult
|
||
|
nsGlobalHistory::AddPageToDatabase(nsIURI* aURI, PRBool aRedirect, PRBool aTopLevel,
|
||
|
PRTime aLastVisitDate, nsIURI *aReferrer)
|
||
|
{
|
||
|
nsresult rv;
|
||
|
NS_ENSURE_ARG_POINTER(aURI);
|
||
|
|
||
|
// If history is set to expire after 0 days,
|
||
|
// then it's technically disabled. Don't even
|
||
|
// bother adding the page
|
||
|
if (mExpireDays == 0) {
|
||
|
NS_WARNING("mExpireDays == 0");
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
// filter out unwanted URIs such as chrome: mailbox: etc
|
||
|
// The model is really if we don't know differently then add which basically
|
||
|
// means we are suppose to try all the things we know not to allow in and
|
||
|
// then if we don't bail go on and allow it in. But here lets compare
|
||
|
// against the most common case we know to allow in and go on and say yes
|
||
|
// to it.
|
||
|
|
||
|
PRBool isHTTP = PR_FALSE;
|
||
|
PRBool isHTTPS = PR_FALSE;
|
||
|
|
||
|
NS_ENSURE_SUCCESS(rv = aURI->SchemeIs("http", &isHTTP), rv);
|
||
|
NS_ENSURE_SUCCESS(rv = aURI->SchemeIs("https", &isHTTPS), rv);
|
||
|
|
||
|
if (!isHTTP && !isHTTPS) {
|
||
|
PRBool isAbout, isImap, isNews, isMailbox, isViewSource, isChrome, isData;
|
||
|
|
||
|
rv = aURI->SchemeIs("about", &isAbout);
|
||
|
rv |= aURI->SchemeIs("imap", &isImap);
|
||
|
rv |= aURI->SchemeIs("news", &isNews);
|
||
|
rv |= aURI->SchemeIs("mailbox", &isMailbox);
|
||
|
rv |= aURI->SchemeIs("view-source", &isViewSource);
|
||
|
rv |= aURI->SchemeIs("chrome", &isChrome);
|
||
|
rv |= aURI->SchemeIs("data", &isData);
|
||
|
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
|
||
|
|
||
|
if (isAbout || isImap || isNews || isMailbox || isViewSource || isChrome || isData) {
|
||
|
#ifdef DEBUG_bsmedberg
|
||
|
printf("Filtering out unwanted scheme.\n");
|
||
|
#endif
|
||
|
return NS_OK;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rv = OpenDB();
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
nsCAutoString URISpec;
|
||
|
rv = aURI->GetSpec(URISpec);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
if (URISpec.Length() > HISTORY_URI_LENGTH_MAX)
|
||
|
return NS_OK;
|
||
|
|
||
|
#ifdef DEBUG_bsmedberg
|
||
|
printf("AddURI: %s%s%s",
|
||
|
URISpec.get(),
|
||
|
aRedirect ? ", redirect" : "",
|
||
|
aTopLevel ? ", toplevel" : "");
|
||
|
#endif
|
||
|
|
||
|
nsCOMPtr<nsIMdbRow> row;
|
||
|
rv = FindRow(kToken_URLColumn, URISpec.get(), getter_AddRefs(row));
|
||
|
|
||
|
if (NS_SUCCEEDED(rv)) {
|
||
|
// update the database, and get the old info back
|
||
|
PRTime oldDate;
|
||
|
PRInt32 oldCount;
|
||
|
rv = AddExistingPageToDatabase(row, aLastVisitDate, aReferrer, &oldDate, &oldCount);
|
||
|
NS_ASSERTION(NS_SUCCEEDED(rv), "AddExistingPageToDatabase failed; see bug 88961");
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
#ifdef DEBUG_bsmedberg
|
||
|
printf("Existing page succeeded.\n");
|
||
|
#endif
|
||
|
}
|
||
|
else {
|
||
|
rv = AddNewPageToDatabase(aURI, aLastVisitDate, aRedirect,
|
||
|
aTopLevel, aReferrer, getter_AddRefs(row));
|
||
|
NS_ASSERTION(NS_SUCCEEDED(rv), "AddNewPageToDatabase failed; see bug 88961");
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
#ifdef DEBUG_bsmedberg
|
||
|
printf("New page succeeded.\n");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// Store last visited page if we have the pref set accordingly
|
||
|
if (aTopLevel) {
|
||
|
PRInt32 choice = 0;
|
||
|
if (NS_SUCCEEDED(gPrefBranch->GetIntPref("startup.page", &choice))) {
|
||
|
if (choice != 2) {
|
||
|
if (NS_SUCCEEDED(gPrefBranch->GetIntPref("windows.loadOnNewWindow", &choice))) {
|
||
|
if (choice != 2) {
|
||
|
gPrefBranch->GetIntPref("tabs.loadOnNewTab", &choice);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (choice == 2) {
|
||
|
NS_ENSURE_STATE(mMetaRow);
|
||
|
|
||
|
SetRowValue(mMetaRow, kToken_LastPageVisited, URISpec.get());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SetDirty();
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
nsresult
|
||
|
nsGlobalHistory::AddExistingPageToDatabase(nsIMdbRow *row,
|
||
|
PRTime aDate,
|
||
|
nsIURI* aReferrer,
|
||
|
PRTime *aOldDate,
|
||
|
PRInt32 *aOldCount)
|
||
|
{
|
||
|
nsresult rv;
|
||
|
nsCAutoString oldReferrer;
|
||
|
|
||
|
nsCAutoString URISpec;
|
||
|
rv = GetRowValue(row, kToken_URLColumn, URISpec);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
nsCAutoString referrerSpec;
|
||
|
if (aReferrer) {
|
||
|
rv = aReferrer->GetSpec(referrerSpec);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
}
|
||
|
|
||
|
// if the page was typed, unhide it now because it's
|
||
|
// known to be valid
|
||
|
if (HasCell(mEnv, row, kToken_TypedColumn)) {
|
||
|
mTypedHiddenURIs.Remove(URISpec);
|
||
|
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);
|
||
|
|
||
|
if (aReferrer) {
|
||
|
rv = GetRowValue(row, kToken_ReferrerColumn, oldReferrer);
|
||
|
// No referrer? Now there is!
|
||
|
if ((NS_FAILED(rv) || oldReferrer.IsEmpty()))
|
||
|
SetRowValue(row, kToken_ReferrerColumn, referrerSpec.get());
|
||
|
}
|
||
|
|
||
|
// Notify observers
|
||
|
nsCOMPtr<nsIRDFResource> url;
|
||
|
rv = gRDFService->GetResource(URISpec, getter_AddRefs(url));
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
nsCOMPtr<nsIRDFDate> date;
|
||
|
rv = gRDFService->GetDateLiteral(aDate, getter_AddRefs(date));
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
// visit date
|
||
|
nsCOMPtr<nsIRDFDate> oldDateLiteral;
|
||
|
rv = gRDFService->GetDateLiteral(*aOldDate, getter_AddRefs(oldDateLiteral));
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
rv = NotifyChange(url, kNC_Date, oldDateLiteral, date);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
// visit count
|
||
|
nsCOMPtr<nsIRDFInt> oldCountLiteral;
|
||
|
rv = gRDFService->GetIntLiteral(*aOldCount, getter_AddRefs(oldCountLiteral));
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
nsCOMPtr<nsIRDFInt> newCountLiteral;
|
||
|
rv = gRDFService->GetIntLiteral(*aOldCount+1,
|
||
|
getter_AddRefs(newCountLiteral));
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
rv = NotifyChange(url, kNC_VisitCount, oldCountLiteral, newCountLiteral);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
nsresult
|
||
|
nsGlobalHistory::AddNewPageToDatabase(nsIURI* aURI,
|
||
|
PRTime aDate,
|
||
|
PRBool aRedirect,
|
||
|
PRBool aTopLevel,
|
||
|
nsIURI* aReferrer,
|
||
|
nsIMdbRow **aResult)
|
||
|
{
|
||
|
mdb_err err;
|
||
|
|
||
|
NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_NOT_INITIALIZED);
|
||
|
|
||
|
nsCAutoString URISpec;
|
||
|
nsresult rv = aURI->GetSpec(URISpec);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
nsCAutoString referrerSpec;
|
||
|
if (aReferrer) {
|
||
|
rv = aReferrer->GetSpec(referrerSpec);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
}
|
||
|
|
||
|
// 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<nsIMdbRow> row;
|
||
|
err = mTable->NewRow(mEnv, &rowId, getter_AddRefs(row));
|
||
|
if (err != 0) return NS_ERROR_FAILURE;
|
||
|
|
||
|
// Set the URL
|
||
|
SetRowValue(row, kToken_URLColumn, URISpec.get());
|
||
|
|
||
|
// Set the date.
|
||
|
SetRowValue(row, kToken_LastVisitDateColumn, aDate);
|
||
|
SetRowValue(row, kToken_FirstVisitDateColumn, aDate);
|
||
|
|
||
|
// Set the referrer if there is one.
|
||
|
if (aReferrer)
|
||
|
SetRowValue(row, kToken_ReferrerColumn, referrerSpec.get());
|
||
|
|
||
|
nsCOMPtr<nsIURI> uri;
|
||
|
NS_NewURI(getter_AddRefs(uri), URISpec, nsnull, nsnull);
|
||
|
nsCAutoString hostname;
|
||
|
if (uri)
|
||
|
uri->GetHost(hostname);
|
||
|
|
||
|
// Strip www.
|
||
|
if (Substring(hostname, 0, 4).EqualsLiteral("www."))
|
||
|
hostname.Cut(0, 4);
|
||
|
|
||
|
SetRowValue(row, kToken_HostnameColumn, hostname.get());
|
||
|
|
||
|
*aResult = row;
|
||
|
NS_ADDREF(*aResult);
|
||
|
|
||
|
PRBool isJavascript;
|
||
|
rv = aURI->SchemeIs("javascript", &isJavascript);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
if (isJavascript || aRedirect || !aTopLevel) {
|
||
|
// if this is a JS url, or a redirected URI or in a frame, hide it in
|
||
|
// global history so that it doesn't show up in the autocomplete
|
||
|
// dropdown. AddExistingPageToDatabase has logic to override this
|
||
|
// behavior for URIs which were typed. See bug 197127 and bug 161531
|
||
|
// for details.
|
||
|
rv = SetRowValue(row, kToken_HiddenColumn, 1);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
}
|
||
|
else {
|
||
|
// Notify observers
|
||
|
nsCOMPtr<nsIRDFResource> url;
|
||
|
rv = gRDFService->GetResource(URISpec, getter_AddRefs(url));
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
nsCOMPtr<nsIRDFDate> date;
|
||
|
rv = gRDFService->GetDateLiteral(aDate, getter_AddRefs(date));
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
nsresult
|
||
|
nsGlobalHistory::RemovePageInternal(const char *aSpec)
|
||
|
{
|
||
|
if (!mTable) return NS_ERROR_NOT_INITIALIZED;
|
||
|
// find the old row, ignore it if we don't have it
|
||
|
nsCOMPtr<nsIMdbRow> row;
|
||
|
nsresult rv = FindRow(kToken_URLColumn, aSpec, getter_AddRefs(row));
|
||
|
if (NS_FAILED(rv)) return NS_OK;
|
||
|
|
||
|
// remove the row
|
||
|
mdb_err 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<nsIRDFResource> oldRowResource;
|
||
|
gRDFService->GetResource(nsDependentCString(aSpec), 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");
|
||
|
|
||
|
// Sigh. This is pretty bad - if the user selects a bunch of things then
|
||
|
// hits delete we'll be re-writing history over and over. Still, we will
|
||
|
// be whacking global history pretty hard after 1.0 so I don't feel too
|
||
|
// bad.
|
||
|
return Commit(kCompressCommit);
|
||
|
}
|
||
|
|
||
|
nsresult
|
||
|
nsGlobalHistory::SetRowValue(nsIMdbRow *aRow, mdb_column aCol, const PRTime& aValue)
|
||
|
{
|
||
|
mdb_err err;
|
||
|
nsCAutoString val;
|
||
|
val.AppendInt(aValue);
|
||
|
|
||
|
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));
|
||
|
PRUnichar *swapval = nsnull;
|
||
|
|
||
|
// 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
|
||
|
|
||
|
if (mReverseByteOrder) {
|
||
|
// The file is other-endian. Byte-swap the value.
|
||
|
swapval = (PRUnichar *)malloc(len);
|
||
|
if (!swapval)
|
||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||
|
SwapBytes(aValue, swapval, len / sizeof(PRUnichar));
|
||
|
aValue = swapval;
|
||
|
}
|
||
|
mdbYarn yarn = { (void *)aValue, len, len, 0, 0, nsnull };
|
||
|
|
||
|
#endif
|
||
|
err = aRow->AddColumn(mEnv, aCol, &yarn);
|
||
|
if (swapval)
|
||
|
free(swapval);
|
||
|
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
|
||
|
if (mReverseByteOrder) {
|
||
|
// The file is other-endian; we must byte-swap the result.
|
||
|
PRUnichar *swapval;
|
||
|
int len = yarn.mYarn_Fill / sizeof(PRUnichar);
|
||
|
swapval = (PRUnichar *)malloc(yarn.mYarn_Fill);
|
||
|
if (!swapval)
|
||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||
|
SwapBytes((const PRUnichar *)yarn.mYarn_Buf, swapval, len);
|
||
|
aResult.Assign(swapval, len);
|
||
|
free(swapval);
|
||
|
}
|
||
|
else
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
// Copy an array of 16-bit values, reversing the byte order.
|
||
|
void
|
||
|
nsGlobalHistory::SwapBytes(const PRUnichar *source, PRUnichar *dest,
|
||
|
PRInt32 aLen)
|
||
|
{
|
||
|
PRUint16 c;
|
||
|
const PRUnichar *inp;
|
||
|
PRUnichar *outp;
|
||
|
PRInt32 i;
|
||
|
|
||
|
inp = source;
|
||
|
outp = dest;
|
||
|
for (i = 0; i < aLen; i++) {
|
||
|
c = *inp++;
|
||
|
*outp++ = (((c >> 8) & 0xff) | (c << 8));
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
nsresult
|
||
|
nsGlobalHistory::GetRowValue(nsIMdbRow *aRow, mdb_column aCol,
|
||
|
PRTime *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;
|
||
|
|
||
|
PR_sscanf((const char*)yarn.mYarn_Buf, "%lld", aResult);
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
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::AddPageWithDetails(nsIURI *aURI, const PRUnichar *aTitle,
|
||
|
PRTime aLastVisitDate)
|
||
|
{
|
||
|
nsresult rv = AddPageToDatabase(aURI, PR_FALSE, PR_TRUE, aLastVisitDate, nsnull);
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
return SetPageTitle(aURI, nsDependentString(aTitle));
|
||
|
}
|
||
|
|
||
|
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(nsIURI *aURI, const nsAString& aTitle)
|
||
|
{
|
||
|
nsresult rv;
|
||
|
NS_ENSURE_ARG_POINTER(aURI);
|
||
|
|
||
|
nsAutoString titleString(StringHead(aTitle, HISTORY_TITLE_LENGTH_MAX));
|
||
|
|
||
|
// skip about: URIs to avoid reading in the db (about:blank, especially)
|
||
|
PRBool isAbout;
|
||
|
rv = aURI->SchemeIs("about", &isAbout);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
if (isAbout) return NS_OK;
|
||
|
|
||
|
NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
|
||
|
|
||
|
nsCAutoString URISpec;
|
||
|
rv = aURI->GetSpec(URISpec);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
nsCOMPtr<nsIMdbRow> row;
|
||
|
rv = FindRow(kToken_URLColumn, URISpec.get(), getter_AddRefs(row));
|
||
|
|
||
|
// if the row doesn't exist, we silently succeed
|
||
|
if (rv == NS_ERROR_NOT_AVAILABLE) return NS_OK;
|
||
|
NS_ENSURE_SUCCESS(rv, 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<nsIRDFLiteral> oldname;
|
||
|
if (!oldtitle.IsEmpty()) {
|
||
|
rv = gRDFService->GetLiteral(oldtitle.get(), getter_AddRefs(oldname));
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
}
|
||
|
|
||
|
SetRowValue(row, kToken_NameColumn, titleString.get());
|
||
|
|
||
|
// ...and update observers
|
||
|
nsCOMPtr<nsIRDFResource> url;
|
||
|
rv = gRDFService->GetResource(URISpec, getter_AddRefs(url));
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
nsCOMPtr<nsIRDFLiteral> name;
|
||
|
rv = gRDFService->GetLiteral(titleString.get(), 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(nsIURI *aURI)
|
||
|
{
|
||
|
nsCAutoString spec;
|
||
|
nsresult rv = aURI->GetSpec(spec);
|
||
|
if (NS_SUCCEEDED(rv))
|
||
|
rv = RemovePageInternal(spec.get());
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsGlobalHistory::RemovePagesFromHost(const nsACString &aHost, PRBool aEntireDomain)
|
||
|
{
|
||
|
const nsCString &host = PromiseFlatCString(aHost);
|
||
|
|
||
|
matchHost_t hostInfo;
|
||
|
hostInfo.history = this;
|
||
|
hostInfo.entireDomain = aEntireDomain;
|
||
|
hostInfo.host = host.get();
|
||
|
|
||
|
nsresult rv = RemoveMatchingRows(matchHostCallback, (void *)&hostInfo, PR_TRUE);
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
return Commit(kCompressCommit);
|
||
|
}
|
||
|
|
||
|
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<nsIURI> 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
|
||
|
const 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;
|
||
|
|
||
|
// Reset the file byte order.
|
||
|
rv = InitByteOrder(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;
|
||
|
|
||
|
BeginUpdateBatch();
|
||
|
|
||
|
// 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<nsIRDFResource> resource;
|
||
|
// XXX from here until end batch, no early returns!
|
||
|
for (mdb_pos pos = count - 1; pos >= 0; --pos) {
|
||
|
nsCOMPtr<nsIMdbRow> 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, 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");
|
||
|
|
||
|
EndUpdateBatch();
|
||
|
|
||
|
return ( err == 0) ? NS_OK : NS_ERROR_FAILURE;
|
||
|
}
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsGlobalHistory::IsVisited(nsIURI* aURI, PRBool *_retval)
|
||
|
{
|
||
|
NS_ENSURE_ARG_POINTER(aURI);
|
||
|
|
||
|
nsresult rv;
|
||
|
NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_NOT_INITIALIZED);
|
||
|
|
||
|
nsCAutoString URISpec;
|
||
|
rv = aURI->GetSpec(URISpec);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
rv = FindRow(kToken_URLColumn, URISpec.get(), nsnull);
|
||
|
*_retval = NS_SUCCEEDED(rv);
|
||
|
|
||
|
// Hidden, typed URIs haven't really been visited yet. They've only
|
||
|
// been typed in and the actual load hasn't happened yet. We maintain
|
||
|
// the list of hidden+typed URIs in memory in mTypedHiddenURIs because
|
||
|
// the list will usually be small and checking the actual Mork row
|
||
|
// would require several dynamic memory allocations.
|
||
|
if (*_retval && mTypedHiddenURIs.Contains(URISpec))
|
||
|
{
|
||
|
*_retval = PR_FALSE;
|
||
|
}
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsGlobalHistory::GetLastPageVisited(nsACString& _retval)
|
||
|
{
|
||
|
NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
|
||
|
|
||
|
NS_ENSURE_STATE(mMetaRow);
|
||
|
|
||
|
mdb_err err = GetRowValue(mMetaRow, kToken_LastPageVisited, _retval);
|
||
|
NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE);
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
// Set the byte order in the history file. The given string value should
|
||
|
// be either "BE" (big-endian) or "LE" (little-endian).
|
||
|
nsresult
|
||
|
nsGlobalHistory::SaveByteOrder(const char *aByteOrder)
|
||
|
{
|
||
|
if (PL_strcmp(aByteOrder, "BE") != 0 && PL_strcmp(aByteOrder, "LE") != 0) {
|
||
|
NS_WARNING("Invalid byte order argument.");
|
||
|
return NS_ERROR_INVALID_ARG;
|
||
|
}
|
||
|
NS_ENSURE_STATE(mMetaRow);
|
||
|
|
||
|
mdb_err err = SetRowValue(mMetaRow, kToken_ByteOrder, aByteOrder);
|
||
|
NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE);
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
// Get the file byte order.
|
||
|
nsresult
|
||
|
nsGlobalHistory::GetByteOrder(char **_retval)
|
||
|
{
|
||
|
NS_ENSURE_SUCCESS(OpenDB(), NS_ERROR_FAILURE);
|
||
|
|
||
|
NS_ENSURE_ARG_POINTER(_retval);
|
||
|
NS_ENSURE_STATE(mMetaRow);
|
||
|
|
||
|
nsCAutoString byteOrder;
|
||
|
mdb_err err = GetRowValue(mMetaRow, kToken_ByteOrder, byteOrder);
|
||
|
NS_ENSURE_TRUE(err == 0, NS_ERROR_FAILURE);
|
||
|
|
||
|
*_retval = ToNewCString(byteOrder);
|
||
|
NS_ENSURE_TRUE(*_retval, NS_ERROR_OUT_OF_MEMORY);
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsGlobalHistory::HidePage(nsIURI *aURI)
|
||
|
{
|
||
|
nsresult rv;
|
||
|
NS_ENSURE_ARG_POINTER(aURI);
|
||
|
|
||
|
nsCAutoString URISpec;
|
||
|
rv = aURI->GetSpec(URISpec);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
if (URISpec.Length() > HISTORY_URI_LENGTH_MAX)
|
||
|
return NS_OK;
|
||
|
|
||
|
#ifdef DEBUG_bsmedberg
|
||
|
printf("nsGlobalHistory::HidePage: %s\n", URISpec.get());
|
||
|
#endif
|
||
|
|
||
|
nsCOMPtr<nsIMdbRow> row;
|
||
|
|
||
|
rv = FindRow(kToken_URLColumn, URISpec.get(), 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 = AddURI(aURI, PR_FALSE, PR_FALSE, nsnull);
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
rv = FindRow(kToken_URLColumn, URISpec.get(), 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<nsIRDFResource> urlResource;
|
||
|
rv = gRDFService->GetResource(URISpec, getter_AddRefs(urlResource));
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
return NotifyFindUnassertions(urlResource, row);
|
||
|
}
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsGlobalHistory::MarkPageAsTyped(nsIURI *aURI)
|
||
|
{
|
||
|
NS_ENSURE_ARG_POINTER(aURI);
|
||
|
nsCAutoString spec;
|
||
|
nsresult rv = aURI->GetSpec(spec);
|
||
|
if (NS_FAILED(rv))
|
||
|
return rv;
|
||
|
|
||
|
if (spec.Length() > HISTORY_URI_LENGTH_MAX)
|
||
|
return NS_OK;
|
||
|
|
||
|
nsCOMPtr<nsIMdbRow> row;
|
||
|
rv = FindRow(kToken_URLColumn, spec.get(), getter_AddRefs(row));
|
||
|
if (NS_FAILED(rv)) {
|
||
|
rv = AddNewPageToDatabase(aURI, GetNow(), PR_FALSE, PR_TRUE, nsnull, getter_AddRefs(row));
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
|
||
|
// We don't know if this is a valid URI yet. Hide it until it finishes
|
||
|
// loading.
|
||
|
SetRowValue(row, kToken_HiddenColumn, 1);
|
||
|
mTypedHiddenURIs.Put(spec);
|
||
|
}
|
||
|
|
||
|
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<nsIRDFResource> 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<nsISimpleEnumerator> 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<nsISupports> 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<nsIRDFResource> 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<nsIRDFDate> date = do_QueryInterface(aTarget);
|
||
|
if (date) {
|
||
|
PRInt64 n;
|
||
|
|
||
|
rv = date->GetValue(&n);
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
nsCAutoString valueStr;
|
||
|
valueStr.AppendInt(n);
|
||
|
|
||
|
value = (void *)ToNewCString(valueStr);
|
||
|
if (aProperty == kNC_Date)
|
||
|
col = kToken_LastVisitDateColumn;
|
||
|
else
|
||
|
col = kToken_FirstVisitDateColumn;
|
||
|
}
|
||
|
}
|
||
|
// PRInt32 properties
|
||
|
else if (aProperty == kNC_VisitCount) {
|
||
|
nsCOMPtr<nsIRDFInt> 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<nsIRDFLiteral> 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<nsIRDFResource> 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_HistoryByDateAndSite ||
|
||
|
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<nsISimpleEnumerator> 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<nsISupports> 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) ||
|
||
|
(aProperty == kNC_DayFolderIndex)) {
|
||
|
|
||
|
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<nsIRDFLiteral> 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 (IsFindResource(aSource)) {
|
||
|
if (aProperty == kNC_Name)
|
||
|
return GetFindUriName(uri, aTarget);
|
||
|
|
||
|
if (aProperty == kNC_NameSort) {
|
||
|
// parse out the 'text' token
|
||
|
nsVoidArray tokenList;
|
||
|
FindUrlToTokenList(uri, tokenList);
|
||
|
|
||
|
nsCOMPtr<nsIRDFLiteral> literal;
|
||
|
|
||
|
for (PRInt32 i = 0; i < tokenList.Count(); ++i) {
|
||
|
tokenPair* token = NS_STATIC_CAST(tokenPair*, tokenList[i]);
|
||
|
|
||
|
if (!strncmp(token->tokenName, "text", token->tokenNameLength)) {
|
||
|
rv = gRDFService->GetLiteral(NS_ConvertUTF8toUCS2(Substring(token->tokenValue, token->tokenValue + token->tokenValueLength)).get(),
|
||
|
getter_AddRefs(literal));
|
||
|
// We don't break out of the loop here because there could be other text tokens in the string.
|
||
|
// The last one is the most specific so wait and see if we've got one...
|
||
|
}
|
||
|
}
|
||
|
|
||
|
FreeTokenList(tokenList);
|
||
|
|
||
|
if (literal && NS_SUCCEEDED(rv)) {
|
||
|
*aTarget = literal;
|
||
|
NS_ADDREF(*aTarget);
|
||
|
return NS_OK;
|
||
|
}
|
||
|
*aTarget = nsnull;
|
||
|
return rv;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ok, we got this far, so we have to retrieve something from
|
||
|
// the row in the database
|
||
|
nsCOMPtr<nsIMdbRow> 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
|
||
|
PRTime 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<nsIRDFDate> 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<nsIRDFInt> visitCountLiteral;
|
||
|
rv = gRDFService->GetIntLiteral(visitCount,
|
||
|
getter_AddRefs(visitCountLiteral));
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
return CallQueryInterface(visitCountLiteral, aTarget);
|
||
|
}
|
||
|
else if (aProperty == kNC_AgeInDays) {
|
||
|
PRTime lastVisitDate;
|
||
|
rv = GetRowValue(row, kToken_LastVisitDateColumn, &lastVisitDate);
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
PRInt32 days = GetAgeInDays(NormalizeTime(GetNow()), lastVisitDate);
|
||
|
|
||
|
nsCOMPtr<nsIRDFInt> 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<nsIURI> aUri;
|
||
|
rv = NS_NewURI(getter_AddRefs(aUri), uri);
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
nsCOMPtr<nsIURL> 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
|
||
|
rv = GetRowValue(row, kToken_HostnameColumn, filename);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
// assume the url is in UTF8
|
||
|
title = NS_ConvertUTF8toUCS2(filename);
|
||
|
}
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
nsCOMPtr<nsIRDFLiteral> 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;
|
||
|
// Avoid trying to create a resource from an empty string, which
|
||
|
// will raise an exception
|
||
|
if (str.IsEmpty()) return NS_RDF_NO_VALUE;
|
||
|
|
||
|
nsCOMPtr<nsIRDFResource> resource;
|
||
|
rv = gRDFService->GetResource(str, 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
|
||
|
PRTime
|
||
|
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_HistoryByDateAndSite) &&
|
||
|
(aProperty == kNC_child)) {
|
||
|
|
||
|
return GetRootDayQueries(aTargets, PR_TRUE);
|
||
|
}
|
||
|
else if ((aSource == kNC_HistoryByDate) &&
|
||
|
(aProperty == kNC_child)) {
|
||
|
|
||
|
return GetRootDayQueries(aTargets, PR_FALSE);
|
||
|
}
|
||
|
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) ||
|
||
|
(aProperty == kNC_DayFolderIndex)) {
|
||
|
nsresult rv;
|
||
|
|
||
|
nsCOMPtr<nsIRDFNode> 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_HistoryByDateAndSite || aSource == kNC_HistoryByDate
|
||
|
|| IsFindResource(aSource)) &&
|
||
|
aProperty == kNC_child) {
|
||
|
|
||
|
nsCOMPtr<nsIRDFResource> 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 = RemovePageInternal(targetUrl);
|
||
|
if (NS_FAILED(rv)) return NS_RDF_ASSERTION_REJECTED;
|
||
|
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
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<nsIRDFResource> 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<nsIMdbRow> 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, PR_TRUE);
|
||
|
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<nsISimpleEnumerator> 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<nsISupports> isupports;
|
||
|
rv = targets->GetNext(getter_AddRefs(isupports));
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
nsCOMPtr<nsIRDFNode> 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<nsIRDFResource> 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_HistoryByDateAndSite) ||
|
||
|
(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 ||
|
||
|
aArc == kNC_DayFolderIndex);
|
||
|
}
|
||
|
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<nsIRDFResource> 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_HistoryByDateAndSite) ||
|
||
|
(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<nsISupportsArray> 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<nsISupportsArray> 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);
|
||
|
array->AppendElement(kNC_DayFolderIndex);
|
||
|
|
||
|
return NS_NewArrayEnumerator(aLabels, array);
|
||
|
}
|
||
|
else {
|
||
|
return NS_NewEmptyEnumerator(aLabels);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsGlobalHistory::GetAllCmds(nsIRDFResource* aSource,
|
||
|
nsISimpleEnumerator/*<nsIRDFResource>*/** aCommands)
|
||
|
{
|
||
|
return NS_NewEmptyEnumerator(aCommands);
|
||
|
}
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsGlobalHistory::IsCommandEnabled(nsISupportsArray/*<nsIRDFResource>*/* aSources,
|
||
|
nsIRDFResource* aCommand,
|
||
|
nsISupportsArray/*<nsIRDFResource>*/* aArguments,
|
||
|
PRBool* aResult)
|
||
|
{
|
||
|
NS_NOTYETIMPLEMENTED("sorry");
|
||
|
return NS_ERROR_NOT_IMPLEMENTED;
|
||
|
}
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsGlobalHistory::DoCommand(nsISupportsArray/*<nsIRDFResource>*/* aSources,
|
||
|
nsIRDFResource* aCommand,
|
||
|
nsISupportsArray/*<nsIRDFResource>*/* 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;
|
||
|
}
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsGlobalHistory::BeginUpdateBatch()
|
||
|
{
|
||
|
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->OnBeginUpdateBatch(this);
|
||
|
NS_RELEASE(observer);
|
||
|
}
|
||
|
}
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsGlobalHistory::EndUpdateBatch()
|
||
|
{
|
||
|
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->OnEndUpdateBatch(this);
|
||
|
NS_RELEASE(observer);
|
||
|
}
|
||
|
}
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////
|
||
|
// 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::FlushTo(const char* aURI)
|
||
|
{
|
||
|
// Do not ever implement this (security)
|
||
|
return NS_ERROR_NOT_IMPLEMENTED;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------
|
||
|
//
|
||
|
// 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<nsIPrefService> 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<nsIPrefBranch2> 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 = CallGetService(kRDFServiceCID, &gRDFService);
|
||
|
|
||
|
NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service");
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Page"), &kNC_Page);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Date"), &kNC_Date);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "FirstVisitDate"),
|
||
|
&kNC_FirstVisitDate);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "VisitCount"), &kNC_VisitCount);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "AgeInDays"), &kNC_AgeInDays);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name"), &kNC_Name);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Name?sort=true"), &kNC_NameSort);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Hostname"), &kNC_Hostname);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "Referrer"), &kNC_Referrer);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "child"), &kNC_child);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "URL"), &kNC_URL);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING(NC_NAMESPACE_URI "DayFolderIndex"), &kNC_DayFolderIndex);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING("NC:HistoryRoot"), &kNC_HistoryRoot);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING("NC:HistoryByDateAndSite"), &kNC_HistoryByDateAndSite);
|
||
|
gRDFService->GetResource(NS_LITERAL_CSTRING("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<nsIStringBundleService> bundleService =
|
||
|
do_GetService(kStringBundleServiceCID, &rv);
|
||
|
|
||
|
if (NS_SUCCEEDED(rv)) {
|
||
|
rv = bundleService->CreateBundle("chrome://global/locale/history/history.properties", getter_AddRefs(mBundle));
|
||
|
}
|
||
|
|
||
|
// register to observe profile changes
|
||
|
nsCOMPtr<nsIObserverService> 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);
|
||
|
observerService->AddObserver(this, "quit-application", PR_TRUE);
|
||
|
}
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
nsresult
|
||
|
nsGlobalHistory::OpenDB()
|
||
|
{
|
||
|
nsresult rv;
|
||
|
|
||
|
if (mStore) return NS_OK;
|
||
|
|
||
|
nsCOMPtr <nsIFile> 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<nsIMdbFactoryFactory> factoryfactory =
|
||
|
do_CreateInstance(kMorkCID, &rv);
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
// See if we need to byte-swap.
|
||
|
InitByteOrder(PR_FALSE);
|
||
|
|
||
|
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<nsIMdbFile> 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<nsIMdbFile> 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;
|
||
|
|
||
|
// Create the meta row.
|
||
|
mdbOid oid = { kToken_HistoryRowScope, 1 };
|
||
|
err = mTable->GetMetaRow(mEnv, &oid, nsnull, getter_AddRefs(mMetaRow));
|
||
|
if (err != 0)
|
||
|
NS_WARNING("Could not get meta row\n");
|
||
|
|
||
|
// Force a commit now to get it written out.
|
||
|
nsCOMPtr<nsIMdbThumb> 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;
|
||
|
}
|
||
|
|
||
|
// Set the history file byte order if necessary, and determine if
|
||
|
// we need to byte-swap Unicode values.
|
||
|
// If the force argument is true, the file byte order will be set
|
||
|
// to that of this machine.
|
||
|
nsresult
|
||
|
nsGlobalHistory::InitByteOrder(PRBool aForce)
|
||
|
{
|
||
|
#ifdef IS_LITTLE_ENDIAN
|
||
|
NS_NAMED_LITERAL_CSTRING(machine_byte_order, "LE");
|
||
|
#endif
|
||
|
#ifdef IS_BIG_ENDIAN
|
||
|
NS_NAMED_LITERAL_CSTRING(machine_byte_order, "BE");
|
||
|
#endif
|
||
|
nsXPIDLCString file_byte_order;
|
||
|
nsresult rv = NS_OK;
|
||
|
|
||
|
if (!aForce)
|
||
|
rv = GetByteOrder(getter_Copies(file_byte_order));
|
||
|
if (aForce || NS_FAILED(rv) ||
|
||
|
!(file_byte_order.Equals(NS_LITERAL_CSTRING("BE")) ||
|
||
|
file_byte_order.Equals(NS_LITERAL_CSTRING("LE")))) {
|
||
|
// Byte order is not yet set, or needs to be reset; initialize it.
|
||
|
mReverseByteOrder = PR_FALSE;
|
||
|
rv = SaveByteOrder(machine_byte_order.get());
|
||
|
if (NS_FAILED(rv))
|
||
|
return rv;
|
||
|
}
|
||
|
else
|
||
|
mReverseByteOrder = !file_byte_order.Equals(machine_byte_order);
|
||
|
|
||
|
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<nsIMdbTableRowCursor> cursor;
|
||
|
nsCOMPtr<nsIMdbRow> 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<nsIIOService> 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);
|
||
|
err = mStore->StringToToken(mEnv, "ByteOrder", &kToken_ByteOrder);
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
nsresult nsGlobalHistory::Commit(eCommitType commitType)
|
||
|
{
|
||
|
if (!mStore || !mTable)
|
||
|
return NS_OK;
|
||
|
|
||
|
nsresult err = NS_OK;
|
||
|
nsCOMPtr<nsIMdbThumb> 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<nsIMdbRow> row;
|
||
|
if (aResult) {
|
||
|
err = mStore->FindRow(mEnv, kToken_HistoryRowScope,
|
||
|
aCol, &yarn, &rowId, getter_AddRefs(row));
|
||
|
|
||
|
if (!row) return NS_ERROR_NOT_AVAILABLE;
|
||
|
} else {
|
||
|
err = mStore->FindRow(mEnv, kToken_HistoryRowScope,
|
||
|
aCol, &yarn, &rowId, nsnull);
|
||
|
}
|
||
|
|
||
|
// make sure it's actually stored in the main table
|
||
|
mdb_bool hasRow;
|
||
|
mTable->HasOid(mEnv, &rowId, &hasRow);
|
||
|
|
||
|
if (!hasRow) return NS_ERROR_NOT_AVAILABLE;
|
||
|
|
||
|
if (aResult) {
|
||
|
*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;
|
||
|
|
||
|
rv = FindRow(kToken_URLColumn, url, nsnull);
|
||
|
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, PRBool aBySite)
|
||
|
{
|
||
|
nsresult rv;
|
||
|
nsCOMPtr<nsISupportsArray> dayArray;
|
||
|
NS_NewISupportsArray(getter_AddRefs(dayArray));
|
||
|
|
||
|
PRInt32 i;
|
||
|
nsCOMPtr<nsIRDFResource> finduri;
|
||
|
nsDependentCString
|
||
|
prefix(FIND_BY_AGEINDAYS_PREFIX "is" "&text=");
|
||
|
nsCAutoString uri;
|
||
|
nsCOMPtr<nsISimpleEnumerator> findEnumerator;
|
||
|
PRBool hasMore = PR_FALSE;
|
||
|
for (i=0; i<7; i++) {
|
||
|
uri = prefix;
|
||
|
uri.AppendInt(i);
|
||
|
if (aBySite)
|
||
|
uri.Append("&groupby=Hostname");
|
||
|
rv = gRDFService->GetResource(uri, 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);
|
||
|
if (aBySite)
|
||
|
uri.Append("&groupby=Hostname");
|
||
|
rv = gRDFService->GetResource(uri, 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);
|
||
|
if (tokenStruct)
|
||
|
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; i<length; i++) {
|
||
|
tokenPair *token = (tokenPair*)tokens[i];
|
||
|
delete token;
|
||
|
}
|
||
|
tokens.Clear();
|
||
|
}
|
||
|
|
||
|
void nsGlobalHistory::FreeSearchQuery(searchQuery& aQuery)
|
||
|
{
|
||
|
// free up the token pairs
|
||
|
PRInt32 i;
|
||
|
for (i=0; i<aQuery.terms.Count(); i++) {
|
||
|
searchTerm *term = (searchTerm*)aQuery.terms.ElementAt(i);
|
||
|
delete term;
|
||
|
}
|
||
|
// clean out the array, just for good measure
|
||
|
aQuery.terms.Clear();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// helper function to figure out if something starts with "find"
|
||
|
//
|
||
|
PRBool
|
||
|
nsGlobalHistory::IsFindResource(nsIRDFResource *aResource)
|
||
|
{
|
||
|
nsresult rv;
|
||
|
const char *value;
|
||
|
rv = aResource->GetValueConst(&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; i<length; i++) {
|
||
|
tokenPair *token = (tokenPair *)aTokens[i];
|
||
|
|
||
|
// per-term tokens
|
||
|
const nsASingleFragmentCString& tokenName =
|
||
|
Substring(token->tokenName, token->tokenName + token->tokenNameLength);
|
||
|
if (tokenName.EqualsLiteral("datasource")) {
|
||
|
datasource = token->tokenValue;
|
||
|
datasourceLen = token->tokenValueLength;
|
||
|
}
|
||
|
else if (tokenName.EqualsLiteral("match")) {
|
||
|
if (Substring(token->tokenValue, token->tokenValue+token->tokenValueLength).Equals("AgeInDays"))
|
||
|
matchCallback = matchAgeInDaysCallback;
|
||
|
|
||
|
property = token->tokenValue;
|
||
|
propertyLen = token->tokenValueLength;
|
||
|
}
|
||
|
else if (tokenName.EqualsLiteral("method")) {
|
||
|
method = token->tokenValue;
|
||
|
methodLen = token->tokenValueLength;
|
||
|
}
|
||
|
else if (tokenName.EqualsLiteral("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.EqualsLiteral("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
|
||
|
PRTime 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=<hostname>
|
||
|
searchTerm hostterm("history", sizeof("history")-1,
|
||
|
"Hostname", sizeof("Hostname")-1,
|
||
|
"is", sizeof("is")-1,
|
||
|
hostname.get(), hostname.Length());
|
||
|
|
||
|
// AgeInDays=<age>
|
||
|
searchTerm ageterm("history", sizeof("history") -1,
|
||
|
"AgeInDays", sizeof("AgeInDays")-1,
|
||
|
"is", sizeof("is")-1,
|
||
|
ageString.get(), ageString.Length());
|
||
|
|
||
|
searchQuery query;
|
||
|
nsCAutoString findUri;
|
||
|
nsCOMPtr<nsIRDFResource> childFindResource;
|
||
|
nsCOMPtr<nsIRDFResource> parentFindResource;
|
||
|
|
||
|
// 2) NC:HistoryByDate -> AgeInDays=<age>&groupby=Hostname
|
||
|
query.groupBy = kToken_HostnameColumn;
|
||
|
query.terms.AppendElement((void *)&ageterm);
|
||
|
|
||
|
GetFindUriPrefix(query, PR_TRUE, findUri);
|
||
|
gRDFService->GetResource(findUri, getter_AddRefs(childFindResource));
|
||
|
NotifyAssert(kNC_HistoryByDateAndSite, kNC_child, childFindResource);
|
||
|
parentFindResource = childFindResource;
|
||
|
|
||
|
query.terms.Clear();
|
||
|
|
||
|
query.groupBy = 0;
|
||
|
query.terms.AppendElement((void *)&ageterm);
|
||
|
|
||
|
GetFindUriPrefix(query, PR_TRUE, findUri);
|
||
|
gRDFService->GetResource(findUri, getter_AddRefs(childFindResource));
|
||
|
NotifyAssert(kNC_HistoryByDate, kNC_child, childFindResource);
|
||
|
|
||
|
query.terms.Clear();
|
||
|
|
||
|
|
||
|
query.groupBy = 0;
|
||
|
query.terms.AppendElement((void *)&ageterm);
|
||
|
|
||
|
GetFindUriPrefix(query, PR_TRUE, findUri);
|
||
|
gRDFService->GetResource(findUri, getter_AddRefs(childFindResource));
|
||
|
NotifyAssert(childFindResource, kNC_child, aSource);
|
||
|
|
||
|
query.terms.Clear();
|
||
|
|
||
|
// 3) AgeInDays=<age>&groupby=Hostname ->
|
||
|
// AgeInDays=<age>&Hostname=<host>
|
||
|
|
||
|
|
||
|
query.groupBy = 0; // create AgeInDays=<age>&Hostname=<host>
|
||
|
query.terms.AppendElement((void *)&ageterm);
|
||
|
query.terms.AppendElement((void *)&hostterm);
|
||
|
|
||
|
GetFindUriPrefix(query, PR_FALSE, findUri);
|
||
|
gRDFService->GetResource(findUri, getter_AddRefs(childFindResource));
|
||
|
NotifyAssert(parentFindResource, kNC_child, childFindResource);
|
||
|
|
||
|
query.terms.Clear();
|
||
|
|
||
|
// 4) AgeInDays=<age>&Hostname=<host> -> uri
|
||
|
parentFindResource = childFindResource; // AgeInDays=<age>&hostname=<host>
|
||
|
NotifyAssert(childFindResource, kNC_child, aSource);
|
||
|
|
||
|
// 5) groupby=Hostname -> Hostname=<host>
|
||
|
query.groupBy = kToken_HostnameColumn; // create groupby=Hostname
|
||
|
|
||
|
GetFindUriPrefix(query, PR_TRUE, findUri);
|
||
|
gRDFService->GetResource(findUri, getter_AddRefs(parentFindResource));
|
||
|
|
||
|
query.groupBy = 0; // create Hostname=<host>
|
||
|
query.terms.AppendElement((void *)&hostterm);
|
||
|
GetFindUriPrefix(query, PR_FALSE, findUri);
|
||
|
findUri.Append(hostname); // append <host>
|
||
|
gRDFService->GetResource(findUri, getter_AddRefs(childFindResource));
|
||
|
|
||
|
NotifyAssert(parentFindResource, kNC_child, childFindResource);
|
||
|
|
||
|
// 6) Hostname=<host> -> uri
|
||
|
parentFindResource = childFindResource; // Hostname=<host>
|
||
|
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
|
||
|
PRTime 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=<hostname>
|
||
|
searchTerm hostterm("history", sizeof("history")-1,
|
||
|
"Hostname", sizeof("Hostname")-1,
|
||
|
"is", sizeof("is")-1,
|
||
|
hostname.get(), hostname.Length());
|
||
|
|
||
|
// AgeInDays=<age>
|
||
|
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<nsIRDFResource> findResource;
|
||
|
|
||
|
// 2) AgeInDays=<age>&Hostname=<host>
|
||
|
query.terms.AppendElement((void *)&ageterm);
|
||
|
query.terms.AppendElement((void *)&hostterm);
|
||
|
GetFindUriPrefix(query, PR_FALSE, findUri);
|
||
|
|
||
|
gRDFService->GetResource(findUri, getter_AddRefs(findResource));
|
||
|
|
||
|
NotifyUnassert(findResource, kNC_child, aSource);
|
||
|
|
||
|
// 3) Hostname=<host>
|
||
|
query.terms.Clear();
|
||
|
|
||
|
query.terms.AppendElement((void *)&hostterm);
|
||
|
GetFindUriPrefix(query, PR_FALSE, findUri);
|
||
|
|
||
|
gRDFService->GetResource(findUri, getter_AddRefs(findResource));
|
||
|
NotifyUnassert(findResource, kNC_child, aSource);
|
||
|
|
||
|
query.terms.Clear();
|
||
|
|
||
|
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-<property>-<method>[-<text>]
|
||
|
// 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 -<text> 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 -<text>, 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<nsIRDFLiteral> 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 <nsIFile> 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();
|
||
|
else if (!nsCRT::strcmp(aTopic, "quit-application"))
|
||
|
rv = Flush();
|
||
|
|
||
|
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<nsIRDFResource> resource;
|
||
|
const char* startPtr = (const char*) yarn.mYarn_Buf;
|
||
|
rv = gRDFService->GetResource(
|
||
|
Substring(startPtr, startPtr+yarn.mYarn_Fill),
|
||
|
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<length; i++) {
|
||
|
searchTerm *term = (searchTerm*)aQuery.terms[i];
|
||
|
if (i != 0)
|
||
|
aResult.Append('&');
|
||
|
aResult.Append("datasource=");
|
||
|
aResult.Append(term->datasource);
|
||
|
|
||
|
aResult.Append("&match=");
|
||
|
aResult.Append(term->property);
|
||
|
|
||
|
aResult.Append("&method=");
|
||
|
aResult.Append(term->method);
|
||
|
|
||
|
aResult.Append("&text=");
|
||
|
aResult.Append(NS_ConvertUCS2toUTF8(term->text));
|
||
|
}
|
||
|
|
||
|
if (aQuery.groupBy == 0) return;
|
||
|
|
||
|
// find out the name of the column we're grouping by
|
||
|
char groupby[100];
|
||
|
mdbYarn yarn = { groupby, 0, sizeof(groupby), 0, 0, nsnull };
|
||
|
err = mStore->TokenToString(mEnv, aQuery.groupBy, &yarn);
|
||
|
|
||
|
// put a "groupby=<colname>"
|
||
|
if (aDoGroupBy) {
|
||
|
aResult.Append("&groupby=");
|
||
|
if (err == 0)
|
||
|
aResult.Append((const char*)yarn.mYarn_Buf, yarn.mYarn_Fill);
|
||
|
}
|
||
|
|
||
|
// put &datasource=history&match=<colname>&method=is&text=
|
||
|
else {
|
||
|
// if the query has a groupby=<foo> 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, PR_FALSE))
|
||
|
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,
|
||
|
PRBool caseSensitive)
|
||
|
{
|
||
|
PRUint32 length = aQuery->terms.Count();
|
||
|
PRUint32 i;
|
||
|
|
||
|
for (i=0; i<length; i++) {
|
||
|
|
||
|
searchTerm *term = (searchTerm*)aQuery->terms[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;
|
||
|
|
||
|
nsAutoString rowVal;
|
||
|
|
||
|
PRInt32 yarnLength = yarn.mYarn_Fill;;
|
||
|
if (property_column == kToken_NameColumn) {
|
||
|
// The name column (page title) is stored as UTF-16.
|
||
|
rowVal.Assign((const PRUnichar*)yarn.mYarn_Buf, yarnLength / 2);
|
||
|
}
|
||
|
else {
|
||
|
// Other columns are stored as UTF-8 and can be null.
|
||
|
if (yarn.mYarn_Buf)
|
||
|
rowVal = NS_ConvertUTF8toUTF16((const char*)yarn.mYarn_Buf, yarnLength);
|
||
|
}
|
||
|
|
||
|
// set up some iterators
|
||
|
nsString::const_iterator start, end;
|
||
|
rowVal.BeginReading(start);
|
||
|
rowVal.EndReading(end);
|
||
|
|
||
|
const nsXPIDLString& searchText = term->text;
|
||
|
|
||
|
if (term->method.Equals("is")) {
|
||
|
if (caseSensitive) {
|
||
|
if (!searchText.Equals(rowVal, nsDefaultStringComparator()))
|
||
|
return PR_FALSE;
|
||
|
}
|
||
|
else {
|
||
|
if (!searchText.Equals(rowVal, nsCaseInsensitiveStringComparator()))
|
||
|
return PR_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
else if (term->method.Equals("isnot")) {
|
||
|
if (caseSensitive) {
|
||
|
if (searchText.Equals(rowVal, nsDefaultStringComparator()))
|
||
|
return PR_FALSE;
|
||
|
}
|
||
|
else {
|
||
|
if (searchText.Equals(rowVal, nsCaseInsensitiveStringComparator()))
|
||
|
return PR_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
else if (term->method.Equals("contains")) {
|
||
|
if (caseSensitive) {
|
||
|
if (!FindInReadable(searchText, start, end, nsDefaultStringComparator()))
|
||
|
return PR_FALSE;
|
||
|
}
|
||
|
else {
|
||
|
if (!FindInReadable(searchText, start, end, nsCaseInsensitiveStringComparator()))
|
||
|
return PR_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
else if (term->method.Equals("doesntcontain")) {
|
||
|
if (caseSensitive) {
|
||
|
if (FindInReadable(searchText, start, end, nsDefaultStringComparator()))
|
||
|
return PR_FALSE;
|
||
|
}
|
||
|
else {
|
||
|
if (FindInReadable(searchText, start, end, nsCaseInsensitiveStringComparator()))
|
||
|
return PR_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
else if (term->method.Equals("startswith")) {
|
||
|
// need to make sure that the found string is
|
||
|
// at the beginning of the string
|
||
|
nsAString::const_iterator real_start = start;
|
||
|
if (caseSensitive) {
|
||
|
if (!(FindInReadable(searchText, start, end, nsDefaultStringComparator()) && real_start == start))
|
||
|
return PR_FALSE;
|
||
|
}
|
||
|
else {
|
||
|
if (!(FindInReadable(searchText, start, end, nsCaseInsensitiveStringComparator()) &&
|
||
|
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
|
||
|
nsAString::const_iterator real_end = end;
|
||
|
if (caseSensitive) {
|
||
|
if (!(RFindInReadable(searchText, start, end, nsDefaultStringComparator()) && real_end == end))
|
||
|
return PR_FALSE;
|
||
|
}
|
||
|
else {
|
||
|
if (!(RFindInReadable(searchText, start, end, nsCaseInsensitiveStringComparator()) &&
|
||
|
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<nsIRDFResource> 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(
|
||
|
Substring(startPtr, startPtr+yarn.mYarn_Fill),
|
||
|
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, getter_AddRefs(resource));
|
||
|
if (NS_FAILED(rv)) return rv;
|
||
|
|
||
|
*aResult = resource;
|
||
|
NS_ADDREF(*aResult);
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------
|
||
|
//
|
||
|
// 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);
|
||
|
|
||
|
nsCOMPtr<nsIAutoCompleteMdbResult2> result;
|
||
|
if (aSearchString.IsEmpty()) {
|
||
|
AutoCompleteTypedSearch(getter_AddRefs(result));
|
||
|
} else {
|
||
|
// if the search string is empty after it has had prefixes removed, then
|
||
|
// we need to ignore the previous result set
|
||
|
nsAutoString cut(aSearchString);
|
||
|
AutoCompleteCutPrefix(cut, nsnull);
|
||
|
if (cut.Length() == 0)
|
||
|
aPreviousResult = nsnull;
|
||
|
|
||
|
// pass string through filter and then determine which prefixes to exclude
|
||
|
// when chopping prefixes off of history urls during comparison
|
||
|
nsString filtered = AutoCompletePrefilter(aSearchString);
|
||
|
AutocompleteExclude exclude;
|
||
|
AutoCompleteGetExcludeInfo(filtered, &exclude);
|
||
|
|
||
|
// perform the actual search here
|
||
|
nsresult rv = AutoCompleteSearch(filtered, &exclude,
|
||
|
NS_STATIC_CAST(nsIAutoCompleteMdbResult2 *,
|
||
|
aPreviousResult),
|
||
|
getter_AddRefs(result));
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
}
|
||
|
|
||
|
aListener->OnSearchResult(this, result);
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsGlobalHistory::StopSearch()
|
||
|
{
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
//----------------------------------------------------------------------
|
||
|
//
|
||
|
// AutoComplete stuff
|
||
|
//
|
||
|
|
||
|
nsresult
|
||
|
nsGlobalHistory::AutoCompleteTypedSearch(nsIAutoCompleteMdbResult2 **aResult)
|
||
|
{
|
||
|
mdb_count count;
|
||
|
mdb_err err = mTable->GetCount(mEnv, &count);
|
||
|
|
||
|
// Get a cursor to iterate through all rows in the database
|
||
|
nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
|
||
|
err = mTable->GetTableRowCursor(mEnv, count, getter_AddRefs(rowCursor));
|
||
|
NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
|
||
|
|
||
|
nsresult rv;
|
||
|
nsCOMPtr<nsIAutoCompleteMdbResult2> result = do_CreateInstance("@mozilla.org/autocomplete/mdb-result;1", &rv);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
result->Init(mEnv, mTable);
|
||
|
result->SetTokens(kToken_URLColumn, nsIAutoCompleteMdbResult2::kCharType, kToken_NameColumn, nsIAutoCompleteMdbResult2::kUnicharType);
|
||
|
result->SetReverseByteOrder(mReverseByteOrder);
|
||
|
|
||
|
nsCOMPtr<nsIMdbRow> row;
|
||
|
mdb_pos pos;
|
||
|
do {
|
||
|
rowCursor->PrevRow(mEnv, getter_AddRefs(row), &pos);
|
||
|
if (!row) break;
|
||
|
|
||
|
if (HasCell(mEnv, row, kToken_TypedColumn)) {
|
||
|
result->AddRow(row);
|
||
|
}
|
||
|
} while (row);
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
|
||
|
*aResult = result;
|
||
|
NS_ADDREF(*aResult);
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
nsresult
|
||
|
nsGlobalHistory::AutoCompleteSearch(const nsAString &aSearchString,
|
||
|
AutocompleteExclude *aExclude,
|
||
|
nsIAutoCompleteMdbResult2 *aPrevResult,
|
||
|
nsIAutoCompleteMdbResult2 **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->RemoveValueAt(i, PR_FALSE);
|
||
|
}
|
||
|
|
||
|
NS_ADDREF(*aResult = aPrevResult);
|
||
|
} else {
|
||
|
// Search through the entire history
|
||
|
|
||
|
// Create and initialize a new result object
|
||
|
nsresult rv = NS_OK;
|
||
|
nsCOMPtr<nsIAutoCompleteMdbResult2> result = do_CreateInstance("@mozilla.org/autocomplete/mdb-result;1", &rv);
|
||
|
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
result->Init(mEnv, mTable);
|
||
|
result->SetTokens(kToken_URLColumn, nsIAutoCompleteMdbResult2::kCharType, kToken_NameColumn, nsIAutoCompleteMdbResult2::kUnicharType);
|
||
|
result->SetReverseByteOrder(mReverseByteOrder);
|
||
|
result->SetSearchString(aSearchString);
|
||
|
|
||
|
// Get a cursor to iterate through all rows in the database
|
||
|
nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
|
||
|
mdb_err err = mTable->GetTableRowCursor(mEnv, -1, getter_AddRefs(rowCursor));
|
||
|
NS_ENSURE_TRUE(!err, NS_ERROR_FAILURE);
|
||
|
|
||
|
// Store hits in an nsIArray initially
|
||
|
nsCOMArray<nsIMdbRow> resultArray;
|
||
|
|
||
|
nsCOMPtr<nsIMdbRow> row;
|
||
|
mdb_pos pos;
|
||
|
do {
|
||
|
rowCursor->NextRow(mEnv, getter_AddRefs(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))
|
||
|
resultArray.AppendObject(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] = &prefixHWStr;
|
||
|
closure.prefixes[1] = &prefixHStr;
|
||
|
closure.prefixes[2] = &prefixHSWStr;
|
||
|
closure.prefixes[3] = &prefixHSStr;
|
||
|
closure.prefixes[4] = &prefixFFStr;
|
||
|
closure.prefixes[5] = &prefixFStr;
|
||
|
|
||
|
// sort it
|
||
|
resultArray.Sort(AutoCompleteSortComparison, NS_STATIC_CAST(void*, &closure));
|
||
|
|
||
|
// place the sorted array into the autocomplete results
|
||
|
PRUint32 count = resultArray.Count();
|
||
|
PRUint32 i;
|
||
|
for (i = 0; i < count; ++i) {
|
||
|
result->AddRow(resultArray[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);
|
||
|
}
|
||
|
|
||
|
*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);
|
||
|
}
|
||
|
|
||
|
nsString
|
||
|
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 nsString(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(nsIMdbRow *row1, nsIMdbRow *row2,
|
||
|
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
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
if (HasCell(closure->history->mEnv, row1, closure->history->kToken_TypedColumn))
|
||
|
item1visits += AUTOCOMPLETE_NONPAGE_VISIT_COUNT_BOOST;
|
||
|
if (HasCell(closure->history->mEnv, row2, closure->history->kToken_TypedColumn))
|
||
|
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; i<closure->prefixCount; 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; i<closure->prefixCount; 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;
|
||
|
}
|