зеркало из https://github.com/mozilla/gecko-dev.git
3316 строки
90 KiB
C
3316 строки
90 KiB
C
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is mozilla.org code.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*/
|
|
|
|
/*
|
|
*/
|
|
#include "privacy.h"
|
|
#include "glhist.h"
|
|
#include "xp_hash.h"
|
|
#include "net.h"
|
|
#include "xp.h"
|
|
#include "mcom_db.h"
|
|
#include <time.h>
|
|
#include "merrors.h"
|
|
#include "xpgetstr.h"
|
|
#include "prefapi.h"
|
|
#include "xplocale.h"
|
|
#include "libi18n.h"
|
|
#include "xp_qsort.h"
|
|
|
|
#if defined(XP_MAC)
|
|
#include "extcache.h"
|
|
#endif
|
|
|
|
extern int XP_GLHIST_INFO_HTML;
|
|
extern int XP_GLHIST_DATABASE_CLOSED;
|
|
extern int XP_GLHIST_UNKNOWN;
|
|
extern int XP_GLHIST_DATABASE_EMPTY;
|
|
extern int XP_GLHIST_HTML_DATE ;
|
|
extern int XP_GLHIST_HTML_TOTAL_ENTRIES;
|
|
extern int XP_HISTORY_SAVE;
|
|
|
|
/*PRIVATE XP_HashList * global_history_list = 0;*/
|
|
PRIVATE Bool global_history_has_changed = FALSE;
|
|
PRIVATE time_t gh_cur_date = 0;
|
|
PRIVATE int32 global_history_timeout_interval = -1;
|
|
|
|
/* Autocomplete stuff */
|
|
PRIVATE Bool urlLookupGlobalHistHasChanged = FALSE;
|
|
PRIVATE int32 entriesToSearch = 100;
|
|
PRIVATE Bool enableUrlMatch=TRUE;
|
|
|
|
|
|
PRIVATE DB * gh_database = 0;
|
|
PRIVATE HASHINFO gh_hashinfo;
|
|
|
|
#ifdef XP_MAC
|
|
/* The implementation for ppc/mac std lib does b - a for difftime.. weird */
|
|
#define difftime(a,b) ((double)((double)(a)-(double)(b)))
|
|
#endif
|
|
|
|
#define SYNC_RATE 30 /* number of stores before sync */
|
|
|
|
/*
|
|
* Flags for individual records
|
|
*/
|
|
#define GH_FLAGS_SHOW 0x00000001
|
|
#define GH_FLAGS_FRAMECELL 0x00000002
|
|
|
|
static const char *pref_link_expiration = "browser.link_expiration";
|
|
|
|
/*------------------------------------------------------------------------------
|
|
//
|
|
// Global History context/cursor structure local types
|
|
//
|
|
------------------------------------------------------------------------------*/
|
|
|
|
/* Structure defining an array element for the Sort
|
|
*/
|
|
typedef struct _gh_HistList
|
|
{
|
|
void * pKeyData;
|
|
void * pData;
|
|
}gh_HistList;
|
|
|
|
/* Structure defining a list of elements
|
|
*/
|
|
typedef struct _gh_RecordList
|
|
{
|
|
struct _gh_RecordList * pNext;
|
|
|
|
DBT key;
|
|
DBT data;
|
|
|
|
} gh_RecordList;
|
|
|
|
/* Structure defining a node in an undo/redo list.
|
|
*/
|
|
typedef struct _gh_URList
|
|
{
|
|
struct _gh_URList * pNext;
|
|
|
|
gh_RecordList *pURItem;
|
|
|
|
} gh_URList;
|
|
|
|
/* Structure defining the Global History undo/redo context.
|
|
*/
|
|
typedef struct _gh_URContext
|
|
{
|
|
gh_URList * pUndoList;
|
|
gh_URList * pRedoList;
|
|
|
|
} gh_URContext;
|
|
|
|
/* Structure defining the Global History context/cursor.
|
|
// This is the handle passed back and forth for all the Global History
|
|
// Context functions.
|
|
*/
|
|
typedef struct _gh_HistContext
|
|
{
|
|
struct _gh_HistContext * pNext;
|
|
struct _gh_HistContext * pPrev;
|
|
|
|
uint32 uNumRecords;
|
|
gh_Filter * pFilter;
|
|
gh_SortColumn enGHSort;
|
|
gh_HistList XP_HUGE ** pHistSort;
|
|
|
|
GHISTORY_NOTIFYPROC pfNotifyProc;
|
|
|
|
gh_URContext * pURContext;
|
|
|
|
void * pUserData;
|
|
|
|
} gh_HistContext;
|
|
|
|
/* The list of all contexts in use. */
|
|
static gh_HistContext *pHistContextList = NULL;
|
|
|
|
#define GLHIST_COOKIE "<!DOCTYPE NETSCAPE-history-file-1>"
|
|
|
|
#ifndef BYTE_ORDER
|
|
Error! byte order must be defined
|
|
#endif
|
|
|
|
#if !defined(XP_MAC)
|
|
#define COPY_INT32(_a,_b) XP_MEMCPY(_a, _b, sizeof(int32));
|
|
#endif
|
|
|
|
PUBLIC void GH_CollectGarbage(void);
|
|
|
|
PRIVATE void GH_CreateURContext( gh_HistContext *hGHContext );
|
|
PRIVATE void GH_NotifyContexts( int32 iNotifyMsg, char *pszKey );
|
|
PRIVATE gh_RecordList * GH_CreateRecordNode( DBT *pKey, DBT *pData );
|
|
PRIVATE void GH_PushRecord( gh_RecordList **ppRecordList, gh_RecordList *pRecordNode );
|
|
PRIVATE void GH_DeleteRecordList( gh_RecordList *pRecordList );
|
|
PRIVATE char * GH_GetTitleFromURL( char *pszURL );
|
|
PRIVATE void GH_PushGroupUndo( gh_URContext *pURContext, gh_RecordList *pRecordNode );
|
|
PRIVATE gh_URList * GH_CreateURNode( gh_RecordList *pRecordList );
|
|
PRIVATE void GH_PushUR( gh_URList **ppURList, gh_URList *pURNode );
|
|
PRIVATE gh_URList * GH_PopUR( gh_URList **ppURList );
|
|
PRIVATE void GH_DeleteURList( gh_URList *pURList );
|
|
|
|
static int
|
|
gh_write_ok(const char* str, int length, XP_File fp)
|
|
{
|
|
if (length < 0) length = XP_STRLEN(str);
|
|
if ((int)XP_FileWrite(str, length, fp) < length) return -1;
|
|
return 0;
|
|
}
|
|
#define WRITE(str, length, fp) \
|
|
if (gh_write_ok((str), (length), (fp)) < 0) return -1
|
|
|
|
#if defined(XP_MAC) || defined(XP_UNIX)
|
|
/* set the maximum time for an object in the Global history in
|
|
* number of seconds
|
|
*/
|
|
PUBLIC void
|
|
GH_SetGlobalHistoryTimeout(int32 timeout_interval)
|
|
{
|
|
global_history_timeout_interval = timeout_interval;
|
|
}
|
|
#endif
|
|
|
|
PRIVATE void
|
|
gh_set_hash_options(void)
|
|
{
|
|
gh_hashinfo.bsize = 4*1024;
|
|
gh_hashinfo.nelem = 0;
|
|
gh_hashinfo.hash = NULL;
|
|
gh_hashinfo.ffactor = 0;
|
|
gh_hashinfo.cachesize = 64 * 1024U;
|
|
gh_hashinfo.lorder = 0;
|
|
}
|
|
|
|
|
|
PRIVATE void
|
|
gh_open_database(void)
|
|
{
|
|
#ifndef NO_DBM
|
|
static Bool have_tried_open=FALSE;
|
|
|
|
if (PRVCY_IsAnonymous()) {
|
|
return;
|
|
}
|
|
|
|
if(gh_database)
|
|
{
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
char* filename;
|
|
gh_set_hash_options();
|
|
filename = WH_FileName("", xpGlobalHistory);
|
|
gh_database = dbopen(filename,
|
|
O_RDWR | O_CREAT,
|
|
0600,
|
|
DB_HASH,
|
|
&gh_hashinfo);
|
|
if (filename) XP_FREE(filename);
|
|
|
|
if(!have_tried_open && !gh_database)
|
|
{
|
|
XP_StatStruct stat_entry;
|
|
|
|
have_tried_open = TRUE; /* only try this once */
|
|
|
|
TRACEMSG(("Could not open gh database -- errno: %d", errno));
|
|
|
|
|
|
/* if the file is zero length remove it
|
|
*/
|
|
if(XP_Stat("", &stat_entry, xpGlobalHistory) != -1)
|
|
{
|
|
if(stat_entry.st_size <= 0)
|
|
{
|
|
char* filename = WH_FileName("", xpGlobalHistory);
|
|
if (!filename) return;
|
|
XP_FileRemove(filename, xpGlobalHistory);
|
|
XP_FREE(filename);
|
|
}
|
|
else
|
|
{
|
|
XP_File fp;
|
|
#define BUFF_SIZE 1024
|
|
char buffer[BUFF_SIZE];
|
|
|
|
/* open the file and look for
|
|
* the old magic cookie. If it's
|
|
* there delete the file
|
|
*/
|
|
fp = XP_FileOpen("", xpGlobalHistory, XP_FILE_READ);
|
|
|
|
if(fp)
|
|
{
|
|
XP_FileReadLine(buffer, BUFF_SIZE, fp);
|
|
|
|
XP_FileClose(fp);
|
|
|
|
if(XP_STRSTR(buffer, "Global-history-file")) {
|
|
char* filename = WH_FileName("", xpGlobalHistory);
|
|
if (!filename) return;
|
|
XP_FileRemove(filename, xpGlobalHistory);
|
|
XP_FREE(filename);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* try it again */
|
|
filename = WH_FileName("", xpGlobalHistory);
|
|
gh_database = dbopen(filename,
|
|
O_RDWR | O_CREAT,
|
|
0600,
|
|
DB_HASH,
|
|
&gh_hashinfo);
|
|
if (filename) XP_FREE(filename);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(gh_database && -1 == (*gh_database->sync)(gh_database, 0))
|
|
{
|
|
TRACEMSG(("Error syncing gh database"));
|
|
(*gh_database->close)(gh_database);
|
|
gh_database = 0;
|
|
}
|
|
}
|
|
#endif /* NO_DBM */
|
|
|
|
}
|
|
|
|
/* if the url was found in the global history then a number between
|
|
* 0 and 99 is returned representing the percentage of time that
|
|
* has elapsed in the expiration cycle.
|
|
* 0 means most recently accessed
|
|
* 99 means least recently accessed (about to be expired)
|
|
*
|
|
* If the url was not found -1 is returned
|
|
*
|
|
* define USE_PERCENTS if you want to get percent of time
|
|
* through expires cycle.
|
|
*/
|
|
|
|
PUBLIC void
|
|
GH_DeleteHistoryItem (char * url) {
|
|
DBT key;
|
|
if(url && gh_database) {
|
|
key.data = (void *) url;
|
|
key.size = (XP_STRLEN(url)+1) * sizeof(char);
|
|
(*gh_database->del)(gh_database, &key, 0);
|
|
(*gh_database->sync)(gh_database, 0);
|
|
}
|
|
}
|
|
|
|
|
|
PUBLIC int
|
|
GH_CheckGlobalHistory(char * url)
|
|
{
|
|
DBT key;
|
|
DBT data;
|
|
int status;
|
|
time_t entry_date;
|
|
|
|
if(!url)
|
|
return(-1);
|
|
|
|
if(!gh_database)
|
|
return(-1);
|
|
|
|
key.data = (void *) url;
|
|
key.size = (XP_STRLEN(url)+1) * sizeof(char);
|
|
|
|
status = (*gh_database->get)(gh_database, &key, &data, 0);
|
|
|
|
if(status < 0)
|
|
{
|
|
TRACEMSG(("Database ERROR retreiving global history entry"));
|
|
return(-1);
|
|
}
|
|
else if(status > 0)
|
|
{
|
|
return(-1);
|
|
}
|
|
|
|
/* otherwise */
|
|
|
|
/* object was found.
|
|
* check the time to make sure it hasn't expired
|
|
*/
|
|
COPY_INT32( &entry_date, data.data );
|
|
if(global_history_timeout_interval > 0
|
|
&& entry_date+global_history_timeout_interval < gh_cur_date)
|
|
{
|
|
/* remove the object
|
|
*/
|
|
(*gh_database->del)(gh_database, &key, 0);
|
|
|
|
/*
|
|
// Notify the contexts of the update
|
|
*/
|
|
GH_NotifyContexts( GH_NOTIFY_DELETE, (char *)key.data );
|
|
|
|
/* return not found
|
|
*/
|
|
return(-1);
|
|
}
|
|
|
|
return(1);
|
|
}
|
|
|
|
/* callback routine invoked by prefapi when the pref value changes */
|
|
/* fix Mac warning about missing prototype */
|
|
int PR_CALLBACK gh_link_expiration_changed(const char * newpref, void * data);
|
|
|
|
int PR_CALLBACK gh_link_expiration_changed(const char * newpref, void * data)
|
|
{
|
|
int32 iExp;
|
|
|
|
/* Get the number of days for link expiration */
|
|
PREF_GetIntPref(pref_link_expiration, &iExp);
|
|
|
|
/* Convert to seconds */
|
|
global_history_timeout_interval = iExp * 60 * 60 * 24;
|
|
|
|
if (iExp == 0)
|
|
GH_ClearGlobalHistory();
|
|
|
|
return PREF_NOERROR;
|
|
}
|
|
|
|
/* start global history tracking
|
|
*/
|
|
PUBLIC void
|
|
GH_InitGlobalHistory(void)
|
|
{
|
|
#if defined(XP_WIN) || defined(XP_OS2)
|
|
int32 iExp;
|
|
|
|
/* Get the number of days for link expiration */
|
|
PREF_GetIntPref(pref_link_expiration, &iExp);
|
|
|
|
/* Convert to seconds */
|
|
global_history_timeout_interval = iExp * 60 * 60 * 24;
|
|
|
|
/* Observe the preference */
|
|
PREF_RegisterCallback(pref_link_expiration, gh_link_expiration_changed, NULL);
|
|
#endif
|
|
|
|
gh_open_database();
|
|
}
|
|
|
|
PRIVATE void
|
|
gh_RemoveDatabase(void)
|
|
{
|
|
char* filename;
|
|
if(gh_database)
|
|
{
|
|
(*gh_database->close)(gh_database);
|
|
gh_database = 0;
|
|
}
|
|
filename = WH_FileName("", xpGlobalHistory);
|
|
if (!filename) return;
|
|
XP_FileRemove(filename, xpGlobalHistory);
|
|
XP_FREE(filename);
|
|
}
|
|
|
|
/* Notify all the contexts of something e.g., updates, deletions
|
|
*/
|
|
PRIVATE void
|
|
GH_NotifyContexts( int32 iNotifyMsg, char *pszKey )
|
|
{
|
|
gh_HistContext * pCsr = pHistContextList;
|
|
gh_HistContext * pCopy = NULL;
|
|
|
|
int32 iCount = 0;
|
|
int32 i = 0;
|
|
|
|
gh_NotifyMsg stNM;
|
|
XP_MEMSET( &stNM, 0, sizeof(gh_NotifyMsg) );
|
|
stNM.iNotifyMsg = iNotifyMsg;
|
|
stNM.pszKey = pszKey;
|
|
|
|
if( !pCsr )
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Note we must first make a copy of the context information before we notify.
|
|
The reason: If the notifyee decides to release his context during the notification,
|
|
the list is compromised i.e., GH_ReleaseContext() changes the list.
|
|
*/
|
|
|
|
/* Count the contexts first */
|
|
do
|
|
{
|
|
iCount++;
|
|
pCsr = pCsr->pNext;
|
|
|
|
}while( pCsr != pHistContextList );
|
|
|
|
/* Allocate the mem for the copy */
|
|
pCopy = (gh_HistContext *)XP_ALLOC( iCount * sizeof(gh_HistContext) );
|
|
XP_MEMSET( pCopy, 0, iCount * sizeof(gh_HistContext) );
|
|
|
|
/* Fill in the context copy */
|
|
pCsr = pHistContextList;
|
|
do
|
|
{
|
|
pCopy[i].pUserData = pCsr->pUserData;
|
|
pCopy[i].pfNotifyProc = pCsr->pfNotifyProc;
|
|
i++;
|
|
|
|
pCsr = pCsr->pNext;
|
|
|
|
}while( pCsr != pHistContextList );
|
|
|
|
/* Now notify the contexts */
|
|
for( i = 0; i < iCount; i++ )
|
|
{
|
|
if( !pCopy[i].pfNotifyProc )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
stNM.pUserData = pCopy[i].pUserData;
|
|
|
|
pCopy[i].pfNotifyProc( &stNM );
|
|
}
|
|
|
|
XP_FREE( pCopy );
|
|
}
|
|
|
|
PRBool blockedHistItem (char* url) ;
|
|
/* add or update the url in the global history
|
|
*/
|
|
PUBLIC void
|
|
GH_UpdateGlobalHistory(URL_Struct * URL_s)
|
|
{
|
|
char *url=NULL, *atSign=NULL, *passwordColon=NULL, *afterProtocol=NULL;
|
|
DBT key, data, dataComp;
|
|
int status;
|
|
static int32 count=0;
|
|
int8 *pData;
|
|
|
|
int32 iNameLen = 0;
|
|
|
|
int32 iDefault = 1;
|
|
int32 iCount;
|
|
|
|
/* check for NULL's
|
|
* and also don't allow ones with post-data in here
|
|
*/
|
|
if(!URL_s || !URL_s->address || URL_s->post_data)
|
|
return;
|
|
|
|
/* Never save these in the history database */
|
|
if (!strncasecomp(URL_s->address, "about:", 6) ||
|
|
!strncasecomp(URL_s->address, "javascript:", 11) ||
|
|
!strncasecomp(URL_s->address, "livescript:", 11) ||
|
|
!strncasecomp(URL_s->address, "mailbox:", 8) ||
|
|
!strncasecomp(URL_s->address, "imap:", 5) ||
|
|
!strncasecomp(URL_s->address, "mailto:", 7) ||
|
|
!strncasecomp(URL_s->address, "mocha:", 6) ||
|
|
!strncasecomp(URL_s->address, "news:", 5) ||
|
|
!strncasecomp(URL_s->address, "pop3:", 5) ||
|
|
!strncasecomp(URL_s->address, "snews:", 6) ||
|
|
!strncasecomp(URL_s->address, "view-source:", 12))
|
|
return;
|
|
|
|
gh_cur_date = time(NULL);
|
|
|
|
/* BM_UpdateBookmarksTime(URL_s, gh_cur_date); */
|
|
|
|
if(global_history_timeout_interval == 0)
|
|
return; /* don't add ever */
|
|
|
|
gh_open_database();
|
|
|
|
if(!gh_database)
|
|
return;
|
|
|
|
global_history_has_changed = TRUE;
|
|
urlLookupGlobalHistHasChanged = TRUE;
|
|
|
|
count++; /* increment change count */
|
|
|
|
/* Don't allow passwords through. If there's an at sign, check for a password. */
|
|
if( (atSign = XP_STRCHR(URL_s->address, '@')) != NULL )
|
|
{
|
|
*atSign = '\0';
|
|
|
|
/* get a position beyond the protocol */
|
|
afterProtocol = XP_STRCHR(URL_s->address, ':');
|
|
if(afterProtocol
|
|
&& (afterProtocol[1] == '/')
|
|
&& (afterProtocol[2] == '/')) {
|
|
afterProtocol += 3;
|
|
}
|
|
else {
|
|
*atSign = '@';
|
|
return; /* url is in bad format */
|
|
}
|
|
|
|
if( (passwordColon = XP_STRCHR(afterProtocol, ':')) != NULL)
|
|
{
|
|
/* Copy everything up to the password colon */
|
|
*passwordColon = '\0';
|
|
StrAllocCopy(url, URL_s->address);
|
|
|
|
/* Put the stripped chars back */
|
|
*passwordColon = ':';
|
|
*atSign = '@';
|
|
|
|
if(!url)
|
|
return;
|
|
/* Concatenate everyting from the at sign on, skipping the password */
|
|
StrAllocCat(url, atSign);
|
|
if(!url)
|
|
return;
|
|
key.data = (void *) url;
|
|
key.size = (XP_STRLEN(url)+1) * sizeof(char);
|
|
}
|
|
/* There was no password, just a username perhaps */
|
|
else {
|
|
*atSign = '@';
|
|
key.data = (void *) URL_s->address;
|
|
key.size = (XP_STRLEN(URL_s->address)+1) * sizeof(char);
|
|
}
|
|
}
|
|
/* No at sign, no chance of a password. Business as usual */
|
|
else {
|
|
key.data = (void *) URL_s->address;
|
|
key.size = (XP_STRLEN(URL_s->address)+1) * sizeof(char);
|
|
}
|
|
|
|
#if 0 /* Old Format */
|
|
COPY_INT32(&date, &gh_cur_date);
|
|
data.data = (void *)&date;
|
|
data.size = sizeof(int32);
|
|
#else
|
|
iNameLen = (URL_s->content_name && *URL_s->content_name) ? XP_STRLEN( URL_s->content_name )+1 : 1;
|
|
data.size = sizeof(int32) + sizeof(int32) + sizeof(int32) + sizeof(int32) + iNameLen*sizeof(char);
|
|
|
|
pData = XP_ALLOC( data.size );
|
|
|
|
data.data = (void *)pData;
|
|
|
|
/*
|
|
// last_accessed...
|
|
*/
|
|
COPY_INT32( pData, &gh_cur_date );
|
|
|
|
/*
|
|
// iFlags
|
|
*/
|
|
XP_MEMSET( pData+3*sizeof(int32), 0, sizeof(int32) );
|
|
|
|
/*
|
|
// pszName...
|
|
//
|
|
// Note the content_name member is rarely if ever used, so the title is always blank
|
|
*/
|
|
if( iNameLen > 1 )
|
|
{
|
|
XP_STRCPY( (char *)pData+4*sizeof(int32), URL_s->content_name );
|
|
}
|
|
else
|
|
{
|
|
*(char *)(pData+4*sizeof(int32)) = 0;
|
|
}
|
|
|
|
if( 0 == (*gh_database->get)( gh_database, &key, &dataComp, 0 ) )
|
|
{
|
|
if( dataComp.size > sizeof(int32) )
|
|
{
|
|
/* New format...
|
|
|
|
//
|
|
// first_accessed
|
|
*/
|
|
|
|
COPY_INT32( pData+sizeof(int32), (int8 *)dataComp.data + sizeof(int32) );
|
|
|
|
/*
|
|
// iCount
|
|
*/
|
|
|
|
COPY_INT32(&iCount, (int8 *)dataComp.data + 2*sizeof(int32));
|
|
iCount++;
|
|
COPY_INT32( pData + 2*sizeof(int32), &iCount );
|
|
}
|
|
else
|
|
{
|
|
/* Old format...
|
|
|
|
//
|
|
// first_accessed
|
|
*/
|
|
|
|
COPY_INT32( pData+sizeof(int32), dataComp.data );
|
|
|
|
/*
|
|
// iCount
|
|
*/
|
|
|
|
COPY_INT32( pData+2*sizeof(int32), &iDefault );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* New record...
|
|
|
|
//
|
|
// first_accessed
|
|
*/
|
|
|
|
COPY_INT32( pData+sizeof(int32), &gh_cur_date );
|
|
|
|
/*
|
|
// iCount
|
|
*/
|
|
COPY_INT32( pData+2*sizeof(int32), &iDefault );
|
|
}
|
|
|
|
#endif
|
|
status = (*gh_database->put)( gh_database, &key, &data, 0 );
|
|
|
|
|
|
XP_FREE( pData );
|
|
XP_FREEIF(url);
|
|
|
|
if(status < 0)
|
|
{
|
|
TRACEMSG(("Global history update failed due to database error"));
|
|
gh_RemoveDatabase();
|
|
}
|
|
else if(count >= SYNC_RATE)
|
|
{
|
|
count = 0;
|
|
if( -1 == (*gh_database->sync)( gh_database, 0 ) )
|
|
{
|
|
TRACEMSG(("Error syncing gh database"));
|
|
}
|
|
}
|
|
#if 0 /* Not a good idea right now - with a large hash this could cause some lag.
|
|
Instead, do it during GH_UpdateURLTitle() since these are what we're interested in anyway */
|
|
/*
|
|
// Notify the contexts of the update
|
|
*/
|
|
GH_NotifyContexts( GH_NOTIFY_UPDATE, (char *)key.data );
|
|
#endif
|
|
}
|
|
|
|
|
|
#define MAX_HIST_DBT_SIZE 1024
|
|
|
|
PRIVATE DBT *
|
|
gh_HistDBTDup(DBT *obj)
|
|
{
|
|
DBT * rv = XP_NEW(DBT);
|
|
|
|
if(!rv || obj->size > MAX_HIST_DBT_SIZE)
|
|
return NULL;
|
|
|
|
rv->size = obj->size;
|
|
rv->data = XP_ALLOC(rv->size);
|
|
if(!rv->data)
|
|
{
|
|
XP_FREE(rv);
|
|
return NULL;
|
|
}
|
|
|
|
XP_MEMCPY(rv->data, obj->data, rv->size);
|
|
|
|
return(rv);
|
|
|
|
}
|
|
|
|
PRIVATE void
|
|
gh_FreeHistDBTdata(DBT *stuff)
|
|
{
|
|
XP_FREE(stuff->data);
|
|
XP_FREE(stuff);
|
|
}
|
|
|
|
|
|
/* runs through a portion of the global history
|
|
* database and removes all objects that have expired
|
|
*
|
|
*/
|
|
PUBLIC void
|
|
GH_CollectGarbage(void)
|
|
{
|
|
#define OLD_ENTRY_ARRAY_SIZE 100
|
|
DBT *old_entry_array[OLD_ENTRY_ARRAY_SIZE];
|
|
|
|
DBT key, data;
|
|
DBT *newkey;
|
|
time_t entry_date;
|
|
int i, old_entry_count=0;
|
|
|
|
if(!gh_database || global_history_timeout_interval < 1)
|
|
return;
|
|
|
|
gh_cur_date = time(NULL);
|
|
|
|
if(0 != (*gh_database->seq)(gh_database, &key, &data, R_FIRST))
|
|
return;
|
|
|
|
global_history_has_changed = TRUE;
|
|
urlLookupGlobalHistHasChanged = TRUE;
|
|
|
|
do
|
|
{
|
|
COPY_INT32(&entry_date, data.data);
|
|
if(global_history_timeout_interval > 0
|
|
&& entry_date+global_history_timeout_interval < gh_cur_date)
|
|
{
|
|
/* put the object on the delete list since it is expired
|
|
*/
|
|
if(old_entry_count < OLD_ENTRY_ARRAY_SIZE)
|
|
old_entry_array[old_entry_count++] = gh_HistDBTDup(&key);
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
while(0 == (gh_database->seq)(gh_database, &key, &data, R_NEXT));
|
|
|
|
for(i=0; i < old_entry_count; i++)
|
|
{
|
|
newkey = old_entry_array[i];
|
|
if(newkey)
|
|
{
|
|
(*gh_database->del)(gh_database, newkey, 0);
|
|
|
|
/*
|
|
// Notify the contexts of the update
|
|
*/
|
|
GH_NotifyContexts( GH_NOTIFY_DELETE, (char *)newkey->data );
|
|
|
|
gh_FreeHistDBTdata(newkey);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* save the global history to a file while leaving the object in memory
|
|
*/
|
|
PUBLIC void
|
|
GH_SaveGlobalHistory(void)
|
|
{
|
|
|
|
if(!gh_database)
|
|
return;
|
|
|
|
GH_CollectGarbage();
|
|
|
|
if(global_history_has_changed)
|
|
{
|
|
if(-1 == (*gh_database->sync)(gh_database, 0))
|
|
{
|
|
TRACEMSG(("Error syncing gh database"));
|
|
(*gh_database->close)(gh_database);
|
|
gh_database = 0;
|
|
}
|
|
global_history_has_changed = FALSE;
|
|
urlLookupGlobalHistHasChanged = TRUE;
|
|
}
|
|
}
|
|
|
|
/* free the global history list
|
|
*/
|
|
PUBLIC void
|
|
GH_FreeGlobalHistory(void)
|
|
{
|
|
if(!gh_database)
|
|
return;
|
|
|
|
if(-1 == (*gh_database->close)(gh_database))
|
|
{
|
|
TRACEMSG(("Error closing gh database"));
|
|
}
|
|
|
|
gh_database = 0;
|
|
|
|
}
|
|
|
|
/* clear the global history list
|
|
*/
|
|
PUBLIC void
|
|
GH_ClearGlobalHistory(void)
|
|
{
|
|
char* filename;
|
|
if(!gh_database)
|
|
return;
|
|
|
|
|
|
GH_FreeGlobalHistory();
|
|
|
|
gh_set_hash_options();
|
|
|
|
#ifndef NO_DBM
|
|
filename = WH_FileName("", xpGlobalHistory);
|
|
gh_database = dbopen(filename,
|
|
O_RDWR | O_TRUNC,
|
|
0600,
|
|
DB_HASH,
|
|
&gh_hashinfo);
|
|
if (filename) XP_FREE(filename);
|
|
#endif /* NO_DBM */
|
|
if(gh_database && -1 == (*gh_database->sync)(gh_database, 0))
|
|
{
|
|
TRACEMSG(("Error syncing gh database"));
|
|
(*gh_database->close)(gh_database);
|
|
gh_database = 0;
|
|
}
|
|
|
|
global_history_has_changed = FALSE;
|
|
urlLookupGlobalHistHasChanged = TRUE;
|
|
}
|
|
|
|
|
|
/* create an HTML stream and push a bunch of HTML about
|
|
* the global history
|
|
*/
|
|
MODULE_PRIVATE int
|
|
NET_DisplayGlobalHistoryInfoAsHTML(MWContext *context,
|
|
URL_Struct *URL_s,
|
|
int format_out)
|
|
{
|
|
char *buffer = (char*)XP_ALLOC(256);
|
|
NET_StreamClass * stream;
|
|
DBT key, data;
|
|
Bool long_form = FALSE;
|
|
time_t entry_date;
|
|
int status = MK_NO_DATA;
|
|
int32 count=0;
|
|
char *escaped;
|
|
static char LINK_START[] = "<A href=\"";
|
|
static char LINK_END[] = "\">";
|
|
static char END_LINK[] = "</A>";
|
|
|
|
if(!buffer)
|
|
{
|
|
return(MK_UNABLE_TO_CONVERT);
|
|
}
|
|
|
|
if(strcasestr(URL_s->address, "?long"))
|
|
long_form = TRUE;
|
|
|
|
StrAllocCopy(URL_s->content_type, TEXT_HTML);
|
|
|
|
format_out = CLEAR_CACHE_BIT(format_out);
|
|
stream = NET_StreamBuilder(format_out,
|
|
URL_s,
|
|
context);
|
|
|
|
if(!stream)
|
|
{
|
|
return(MK_UNABLE_TO_CONVERT);
|
|
}
|
|
|
|
|
|
/* define a macro to push a string up the stream
|
|
* and handle errors
|
|
*/
|
|
#define PUT_PART(part) \
|
|
status = (*stream->put_block)(stream, \
|
|
part ? part : XP_GetString(XP_GLHIST_UNKNOWN), \
|
|
part ? XP_STRLEN(part) : 7); \
|
|
if(status < 0) \
|
|
goto END;
|
|
|
|
XP_SPRINTF(buffer, XP_GetString(XP_GLHIST_INFO_HTML));
|
|
|
|
PUT_PART(buffer);
|
|
|
|
if(!gh_database)
|
|
{
|
|
XP_STRCPY(buffer, XP_GetString(XP_GLHIST_DATABASE_CLOSED));
|
|
PUT_PART(buffer);
|
|
goto END;
|
|
}
|
|
|
|
if(0 != (*gh_database->seq)(gh_database, &key, &data, R_FIRST))
|
|
{
|
|
XP_STRCPY(buffer, XP_GetString(XP_GLHIST_DATABASE_EMPTY));
|
|
PUT_PART(buffer);
|
|
goto END;
|
|
}
|
|
|
|
/* define some macros to help us output HTML tables
|
|
*/
|
|
#define TABLE_TOP(arg1) \
|
|
XP_SPRINTF(buffer, \
|
|
"<TR><TD ALIGN=RIGHT><b>%s</TD>\n" \
|
|
"<TD>", arg1); \
|
|
PUT_PART(buffer);
|
|
|
|
#define TABLE_BOTTOM \
|
|
XP_SPRINTF(buffer, \
|
|
"</TD></TR>"); \
|
|
PUT_PART(buffer);
|
|
|
|
do
|
|
{
|
|
count++;
|
|
|
|
COPY_INT32(&entry_date, data.data);
|
|
|
|
/* print url */
|
|
XP_STRCPY(buffer, "<TT> URL:</TT> ");
|
|
PUT_PART(buffer);
|
|
|
|
/* make the URL a link */
|
|
PUT_PART(LINK_START);
|
|
if(status < 0)
|
|
goto END;
|
|
|
|
escaped = NET_EscapeDoubleQuote((char*)key.data);
|
|
PUT_PART(escaped);
|
|
XP_FREE(escaped);
|
|
if(status < 0)
|
|
goto END;
|
|
|
|
PUT_PART(LINK_END);
|
|
if(status < 0)
|
|
goto END;
|
|
|
|
escaped = NET_EscapeHTML((char*)key.data);
|
|
PUT_PART(escaped);
|
|
XP_FREE(escaped);
|
|
if(status < 0)
|
|
goto END;
|
|
|
|
PUT_PART(END_LINK);
|
|
if(status < 0)
|
|
goto END;
|
|
|
|
XP_SPRINTF(buffer, XP_GetString(XP_GLHIST_HTML_DATE), ctime(&entry_date));
|
|
PUT_PART(buffer);
|
|
|
|
}
|
|
while(0 == (*gh_database->seq)(gh_database, &key, &data, R_NEXT));
|
|
|
|
|
|
XP_SPRINTF(buffer, XP_GetString(XP_GLHIST_HTML_TOTAL_ENTRIES), count);
|
|
PUT_PART(buffer);
|
|
|
|
END:
|
|
XP_FREE(buffer);
|
|
|
|
if(status < 0)
|
|
(*stream->abort)(stream, status);
|
|
else
|
|
(*stream->complete)(stream);
|
|
XP_FREE(stream);
|
|
|
|
return(status);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------
|
|
//
|
|
*/
|
|
PRIVATE void
|
|
GH_CreateURContext( gh_HistContext *hGHContext )
|
|
{
|
|
if( !hGHContext || hGHContext->pURContext )
|
|
{
|
|
return;
|
|
}
|
|
|
|
hGHContext->pURContext = XP_ALLOC( sizeof(gh_URContext) );
|
|
|
|
hGHContext->pURContext->pUndoList = hGHContext->pURContext->pRedoList = NULL;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------
|
|
//
|
|
*/
|
|
PRIVATE void
|
|
GH_ReleaseURContext( gh_URContext *pURContext )
|
|
{
|
|
if( !pURContext )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( pURContext->pUndoList )
|
|
{
|
|
GH_DeleteURList( pURContext->pUndoList );
|
|
}
|
|
|
|
if( pURContext->pRedoList )
|
|
{
|
|
GH_DeleteURList( pURContext->pRedoList );
|
|
}
|
|
|
|
XP_FREE( pURContext );
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------
|
|
//
|
|
// QSort compare callbacks
|
|
*/
|
|
static int QSortCompStr( const void *elem1, const void *elem2 )
|
|
{
|
|
gh_HistList ** p1 = (gh_HistList **)elem1;
|
|
gh_HistList ** p2 = (gh_HistList **)elem2;
|
|
return XP_StrColl( (char *)(*p1)->pData, (char *)(*p2)->pData );
|
|
}
|
|
static int QSortCompStr2( const void *elem1, const void *elem2 )
|
|
{
|
|
gh_HistList ** p1 = (gh_HistList **)elem1;
|
|
gh_HistList ** p2 = (gh_HistList **)elem2;
|
|
return XP_StrColl( (char *)(*p1)->pKeyData, (char *)(*p2)->pKeyData );
|
|
}
|
|
|
|
#ifdef SUNOS4
|
|
/* difftime() doesn't seem to exist on SunOS anywhere. -mcafee */
|
|
static double difftime(time_t time1, time_t time0)
|
|
{
|
|
return (double) (time1 - time0);
|
|
}
|
|
#endif
|
|
|
|
static int QSortCompDate( const void *elem1, const void *elem2 )
|
|
{
|
|
gh_HistList ** p1 = (gh_HistList **)elem1;
|
|
gh_HistList ** p2 = (gh_HistList **)elem2;
|
|
return difftime( *(time_t *)(*p2)->pData, *(time_t *)(*p1)->pData ) >= 0 ? 1 : -1;
|
|
}
|
|
static int QSortCompInt32( const void *elem1, const void *elem2 )
|
|
{
|
|
gh_HistList ** p1 = (gh_HistList **)elem1;
|
|
gh_HistList ** p2 = (gh_HistList **)elem2;
|
|
return (*(int32 *)(*p1)->pData >= *(int32 *)(*p2)->pData) ? 1 : -1;
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------
|
|
//
|
|
// Supplies a "context", or handle, to the hash table. The context serves as a
|
|
// quasi-cursor to the table, offering the ability to navigate and enumerate the
|
|
// records sorting on a specified column/field.
|
|
*/
|
|
#define SORT_ARRAY_GROW_SIZE 1024
|
|
PUBLIC GHHANDLE
|
|
GH_GetContext( enum gh_SortColumn enGHSort,
|
|
gh_Filter * pFilter,
|
|
GHISTORY_NOTIFYPROC pfNotifyProc,
|
|
GHURHANDLE hUR,
|
|
void * pUserData )
|
|
{
|
|
DBT key, data;
|
|
void XP_HUGE *pBase = NULL;
|
|
#ifdef XP_WIN16
|
|
void XP_HUGE *pBuf = NULL;
|
|
#endif
|
|
int16 csid = INTL_DefaultWinCharSetID( 0 );
|
|
|
|
gh_HistList *pNode;
|
|
gh_HistContext *hGHContext = XP_ALLOC( sizeof(gh_HistContext) );
|
|
XP_MEMSET( hGHContext, 0, sizeof(gh_HistContext) );
|
|
|
|
if( !gh_database || global_history_timeout_interval < 1 )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
hGHContext->uNumRecords = 0;
|
|
hGHContext->enGHSort = enGHSort;
|
|
hGHContext->pFilter = pFilter;
|
|
hGHContext->pfNotifyProc = pfNotifyProc;
|
|
hGHContext->pUserData = pUserData;
|
|
hGHContext->pURContext = (gh_URContext *)hUR;
|
|
|
|
/* Append the context to the list */
|
|
if( !pHistContextList )
|
|
{
|
|
pHistContextList = hGHContext;
|
|
pHistContextList->pNext = pHistContextList->pPrev = pHistContextList;
|
|
}
|
|
else
|
|
{
|
|
hGHContext->pPrev = pHistContextList->pPrev;
|
|
hGHContext->pNext = pHistContextList;
|
|
pHistContextList->pPrev->pNext = hGHContext;
|
|
pHistContextList->pPrev = hGHContext;
|
|
}
|
|
|
|
data.size = key.size = 0;
|
|
|
|
/*
|
|
// Build the array. We don't know the number of entries in the hash, so we
|
|
// have to grow the array as we read the entries. Gross.
|
|
*/
|
|
if( 0 != (*gh_database->seq)( gh_database, &key, &data, R_FIRST ) )
|
|
{
|
|
return hGHContext;
|
|
}
|
|
|
|
pBase = (void XP_HUGE *)XP_HUGE_ALLOC( SORT_ARRAY_GROW_SIZE*sizeof(gh_HistList *) );
|
|
do
|
|
{
|
|
if( data.size > sizeof(int32) )
|
|
{
|
|
/*
|
|
// The entry/record is of the new format...
|
|
//
|
|
|
|
// Ignore history records which are NOT flagged as having been explicitly loaded
|
|
// e.g., don't expose gif images that are only part of a page
|
|
*/
|
|
/*int32 iFlags = *(int32 *)((int8 *)data.data + 3*sizeof(int32));*/
|
|
int32 iFlags;
|
|
COPY_INT32( &iFlags, (int8 *)data.data + 3*sizeof(int32) );
|
|
|
|
if( !(iFlags & GH_FLAGS_SHOW) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
// Filter records according to the supplied filter struct.
|
|
*/
|
|
|
|
if( pFilter )
|
|
{
|
|
Bool bKeep = FALSE;
|
|
int i;
|
|
|
|
for( i = 0; i < pFilter->iNumConditions; i++ )
|
|
{
|
|
if( i > 0 )
|
|
{
|
|
if( pFilter->enOps[i-1] == eGH_FLOAnd )
|
|
{
|
|
if( !bKeep )
|
|
{
|
|
/* No need to evaluate logical-and expression if already false */
|
|
continue;
|
|
}
|
|
}
|
|
else /* eGH_FLOOr */
|
|
{
|
|
if( bKeep )
|
|
{
|
|
/* No need to evaluate logical-or expression if already true */
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Guilty until proven innocent */
|
|
bKeep = FALSE;
|
|
|
|
switch( pFilter->pConditions[i].enCol )
|
|
{
|
|
case eGH_LocationSort:
|
|
{
|
|
int iRes = XP_StrColl( pFilter->pConditions[i].tests.pszTest, key.data );
|
|
|
|
switch( pFilter->pConditions[i].enOp )
|
|
{
|
|
case eGH_FOEquals:
|
|
{
|
|
if( !iRes )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOEqualsNot:
|
|
{
|
|
if( iRes )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreater:
|
|
{
|
|
if( iRes > 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreaterEqual:
|
|
{
|
|
if( iRes >= 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLess:
|
|
{
|
|
if( iRes < 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLessEqual:
|
|
{
|
|
if( iRes <= 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOHas:
|
|
{
|
|
bKeep = INTL_Strcasestr( csid, key.data, pFilter->pConditions[i].tests.pszTest ) != NULL;
|
|
break;
|
|
}
|
|
case eGH_FOHasNot:
|
|
{
|
|
bKeep = INTL_Strcasestr( csid, key.data, pFilter->pConditions[i].tests.pszTest ) == NULL;
|
|
break;
|
|
}
|
|
default:
|
|
bKeep = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eGH_NameSort:
|
|
{
|
|
char *pText = (char *)((int8 *)data.data + 4*sizeof(int32));
|
|
|
|
int iRes = XP_StrColl( pFilter->pConditions[i].tests.pszTest, pText );
|
|
|
|
switch( pFilter->pConditions[i].enOp )
|
|
{
|
|
case eGH_FOEquals:
|
|
{
|
|
if( !iRes )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOEqualsNot:
|
|
{
|
|
if( iRes )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreater:
|
|
{
|
|
if( iRes > 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreaterEqual:
|
|
{
|
|
if( iRes >= 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLess:
|
|
{
|
|
if( iRes < 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLessEqual:
|
|
{
|
|
if( iRes <= 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOHas:
|
|
{
|
|
bKeep = INTL_Strcasestr( csid, pText, pFilter->pConditions[i].tests.pszTest ) != NULL;
|
|
break;
|
|
}
|
|
case eGH_FOHasNot:
|
|
{
|
|
bKeep = INTL_Strcasestr( csid, pText, pFilter->pConditions[i].tests.pszTest ) == NULL;
|
|
break;
|
|
}
|
|
default:
|
|
bKeep = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eGH_VisitCountSort:
|
|
{
|
|
int32 iCount;
|
|
COPY_INT32( &iCount, (int8 *)data.data + 2*sizeof(int32) );
|
|
|
|
switch( pFilter->pConditions[i].enOp )
|
|
{
|
|
case eGH_FOEquals:
|
|
{
|
|
if( iCount == pFilter->pConditions[i].tests.iTest )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOEqualsNot:
|
|
{
|
|
if( iCount != pFilter->pConditions[i].tests.iTest )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreater:
|
|
{
|
|
if( iCount > pFilter->pConditions[i].tests.iTest )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreaterEqual:
|
|
{
|
|
if( iCount >= pFilter->pConditions[i].tests.iTest )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLess:
|
|
{
|
|
if( iCount < pFilter->pConditions[i].tests.iTest )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLessEqual:
|
|
{
|
|
if( iCount <= pFilter->pConditions[i].tests.iTest )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
bKeep = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eGH_FirstDateSort:
|
|
case eGH_LastDateSort:
|
|
{
|
|
/*
|
|
// Assuming we are comparing ONLY the date, set the time to 00:00:00 for both time values
|
|
*/
|
|
|
|
time_t time0, time1 = pFilter->pConditions[i].tests.iTest;
|
|
struct tm *pTM;
|
|
struct tm tm0;
|
|
struct tm tm1;
|
|
|
|
if( pFilter->pConditions[i].enCol == eGH_FirstDateSort )
|
|
{
|
|
COPY_INT32( &time0, (int8 *)data.data + sizeof(int32) );
|
|
}
|
|
else
|
|
{
|
|
COPY_INT32( &time0, (int8 *)data.data );
|
|
}
|
|
|
|
pTM = localtime( &time0 );
|
|
XP_MEMSET( &tm0, 0, sizeof(struct tm) );
|
|
tm0.tm_mday = pTM->tm_mday;
|
|
tm0.tm_mon = pTM->tm_mon;
|
|
tm0.tm_year = pTM->tm_year;
|
|
pTM = localtime( &time1 );
|
|
XP_MEMSET( &tm1, 0, sizeof(struct tm) );
|
|
tm1.tm_mday = pTM->tm_mday;
|
|
tm1.tm_mon = pTM->tm_mon;
|
|
tm1.tm_year = pTM->tm_year;
|
|
|
|
time0 = mktime( &tm0 );
|
|
time1 = mktime( &tm1 );
|
|
|
|
switch( pFilter->pConditions[i].enOp )
|
|
{
|
|
case eGH_FOEquals:
|
|
{
|
|
if( time0 == time1 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOEqualsNot:
|
|
{
|
|
if( time0 != time1 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreater:
|
|
{
|
|
if( time0 > time1 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreaterEqual:
|
|
{
|
|
if( time0 >= time1 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLess:
|
|
{
|
|
if( time0 < time1 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLessEqual:
|
|
{
|
|
if( time0 <= time1 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
bKeep = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !bKeep )
|
|
{
|
|
/*
|
|
// Did NOT pass filter. Test next record.
|
|
*/
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
// The entry/record is of the old format...
|
|
//
|
|
|
|
// Try to be somewhat smart and filter the implicitly loaded stuff i.e., non-html docs
|
|
*/
|
|
char *pszExt = strrchr( key.data, '.' );
|
|
|
|
if( ((char *)key.data)[XP_STRLEN(key.data)-1] == '/' )
|
|
{
|
|
}
|
|
else if( XP_STRLEN(key.data) < 5 )
|
|
{
|
|
continue;
|
|
}
|
|
else if( pszExt )
|
|
{
|
|
pszExt++;
|
|
if( strncasecomp( pszExt, "htm", 3 ) )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
// Filter records according to the supplied filter struct
|
|
*/
|
|
|
|
if( pFilter )
|
|
{
|
|
Bool bKeep = FALSE;
|
|
int i;
|
|
|
|
for( i = 0; i < pFilter->iNumConditions; i++ )
|
|
{
|
|
|
|
if( i > 0 )
|
|
{
|
|
if( pFilter->enOps[i-1] == eGH_FLOAnd )
|
|
{
|
|
if( !bKeep )
|
|
{
|
|
/* No need to evaluate logical-and expression if already false */
|
|
continue;
|
|
}
|
|
}
|
|
else /* eGH_FLOOr */
|
|
{
|
|
if( bKeep )
|
|
{
|
|
/* No need to evaluate logical-or expression if already true */
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Guilty until proven innocent */
|
|
bKeep = FALSE;
|
|
|
|
switch( pFilter->pConditions[i].enCol )
|
|
{
|
|
case eGH_LocationSort:
|
|
{
|
|
int iRes = XP_StrColl( pFilter->pConditions[i].tests.pszTest, key.data );
|
|
|
|
switch( pFilter->pConditions[i].enOp )
|
|
{
|
|
case eGH_FOEquals:
|
|
{
|
|
if( !iRes )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOEqualsNot:
|
|
{
|
|
if( iRes )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreater:
|
|
{
|
|
if( iRes > 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreaterEqual:
|
|
{
|
|
if( iRes >= 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLess:
|
|
{
|
|
if( iRes < 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLessEqual:
|
|
{
|
|
if( iRes <= 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOHas:
|
|
{
|
|
bKeep = INTL_Strcasestr( csid, key.data, pFilter->pConditions[i].tests.pszTest ) != NULL;
|
|
break;
|
|
}
|
|
case eGH_FOHasNot:
|
|
{
|
|
bKeep = INTL_Strcasestr( csid, key.data, pFilter->pConditions[i].tests.pszTest ) == NULL;
|
|
break;
|
|
}
|
|
default:
|
|
bKeep = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eGH_NameSort:
|
|
{
|
|
/*
|
|
// Since there is no title available from the old format, let's try and pull
|
|
// a meaningful title from the URL.
|
|
*/
|
|
|
|
char *pszTitle = GH_GetTitleFromURL( key.data );
|
|
int iRes = XP_StrColl( pFilter->pConditions[i].tests.pszTest, pszTitle );
|
|
|
|
switch( pFilter->pConditions[i].enOp )
|
|
{
|
|
case eGH_FOEquals:
|
|
{
|
|
if( !iRes )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOEqualsNot:
|
|
{
|
|
if( iRes )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreater:
|
|
{
|
|
if( iRes > 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreaterEqual:
|
|
{
|
|
if( iRes >= 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLess:
|
|
{
|
|
if( iRes < 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLessEqual:
|
|
{
|
|
if( iRes <= 0 )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOHas:
|
|
{
|
|
bKeep = INTL_Strcasestr( csid, pszTitle, pFilter->pConditions[i].tests.pszTest ) != NULL;
|
|
break;
|
|
}
|
|
case eGH_FOHasNot:
|
|
{
|
|
bKeep = INTL_Strcasestr( csid, pszTitle, pFilter->pConditions[i].tests.pszTest ) == NULL;
|
|
break;
|
|
}
|
|
default:
|
|
bKeep = FALSE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case eGH_FirstDateSort:
|
|
case eGH_LastDateSort:
|
|
{
|
|
time_t time;
|
|
|
|
COPY_INT32( &time, (int8 *)data.data );
|
|
|
|
switch( pFilter->pConditions[i].enOp )
|
|
{
|
|
case eGH_FOEquals:
|
|
{
|
|
if( time == pFilter->pConditions[i].tests.iTest )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOEqualsNot:
|
|
{
|
|
if( time != pFilter->pConditions[i].tests.iTest )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreater:
|
|
{
|
|
if( time > pFilter->pConditions[i].tests.iTest )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOGreaterEqual:
|
|
{
|
|
if( time >= pFilter->pConditions[i].tests.iTest )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLess:
|
|
{
|
|
if( time < pFilter->pConditions[i].tests.iTest )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
case eGH_FOLessEqual:
|
|
{
|
|
if( time <= pFilter->pConditions[i].tests.iTest )
|
|
{
|
|
bKeep = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
bKeep = FALSE;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !bKeep )
|
|
{
|
|
/* Did NOT pass filter. Test next record. */
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
pNode = XP_ALLOC( sizeof(gh_HistList) );
|
|
pNode->pKeyData = XP_ALLOC( (XP_STRLEN(key.data)+1) * sizeof(char) );
|
|
XP_STRCPY( pNode->pKeyData, key.data );
|
|
|
|
switch( enGHSort )
|
|
{
|
|
case eGH_LocationSort:
|
|
{
|
|
pNode->pData = pNode->pKeyData;
|
|
break;
|
|
}
|
|
|
|
case eGH_NameSort:
|
|
{
|
|
if( data.size > sizeof(int32) )
|
|
{
|
|
/* The entry/record is of the new format i.e., record has title data */
|
|
|
|
pNode->pData = XP_ALLOC( (XP_STRLEN((char *)data.data + 4*sizeof(int32))+1) * sizeof(char) );
|
|
XP_STRCPY( pNode->pData, (char *)data.data + 4*sizeof(int32) );
|
|
}
|
|
else
|
|
{
|
|
#if 0
|
|
/*
|
|
// The entry/record is of the old format i.e., no title data available
|
|
// Note this is only for internal sorting to keep unititled records at bottom of A->Z sort
|
|
*/
|
|
pNode->pData = XP_ALLOC( (XP_STRLEN("~~")+1) * sizeof(char) );
|
|
XP_STRCPY( pNode->pData, "~~" );
|
|
#else
|
|
/*
|
|
// Since there is no title available from the old format, let's try and pull
|
|
// a meaningful title from the URL.
|
|
*/
|
|
char *pszTitle = GH_GetTitleFromURL( key.data );
|
|
pNode->pData = XP_ALLOC( (XP_STRLEN( pszTitle )+1) * sizeof(char) );
|
|
XP_STRCPY( pNode->pData, pszTitle );
|
|
pszTitle = strrchr( pNode->pData, '/' );
|
|
if( pszTitle )
|
|
{
|
|
/* Remove the trailing slash from the title. */
|
|
*pszTitle = 0;
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eGH_VisitCountSort:
|
|
{
|
|
if( data.size > sizeof(int32) )
|
|
{
|
|
/* The entry/record is of the new format i.e., record has a visit count */
|
|
|
|
pNode->pData = XP_ALLOC( sizeof(int32) );
|
|
COPY_INT32( pNode->pData, (int8 *)data.data + 2*sizeof(int32) );
|
|
}
|
|
else
|
|
{
|
|
/* The entry/record is of the old format i.e., no visit count available */
|
|
|
|
pNode->pData = XP_ALLOC( sizeof(int32) );
|
|
*(int32 *)pNode->pData = 1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eGH_FirstDateSort:
|
|
{
|
|
if( data.size > sizeof(int32) )
|
|
{
|
|
/* The entry/record is of the new format */
|
|
|
|
time_t date;
|
|
COPY_INT32( &date, data.data );
|
|
|
|
pNode->pData = XP_ALLOC( sizeof(int32) );
|
|
|
|
COPY_INT32( pNode->pData, (int8 *)data.data + sizeof(int32) );
|
|
|
|
break;
|
|
}
|
|
|
|
/* Fall through if old format (i.e., sort by last_accessed) */
|
|
}
|
|
|
|
case eGH_LastDateSort:
|
|
default:
|
|
{
|
|
/* Note the last_accessed field is at the beginning of the data regardless of new/old format */
|
|
|
|
time_t date;
|
|
COPY_INT32( &date, data.data );
|
|
|
|
pNode->pData = XP_ALLOC( sizeof(int32) );
|
|
|
|
COPY_INT32( pNode->pData, (int8 *)data.data );
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
((gh_HistList XP_HUGE **)pBase)[hGHContext->uNumRecords] = pNode;
|
|
|
|
hGHContext->uNumRecords++;
|
|
|
|
if( !(hGHContext->uNumRecords % SORT_ARRAY_GROW_SIZE) )
|
|
{
|
|
#ifndef XP_WIN16
|
|
pBase = (void *)XP_REALLOC( pBase, (hGHContext->uNumRecords + SORT_ARRAY_GROW_SIZE)*sizeof(gh_HistList *) );
|
|
#else
|
|
pBuf = (void *)XP_HUGE_ALLOC( (hGHContext->uNumRecords + SORT_ARRAY_GROW_SIZE)*sizeof(gh_HistList *) );
|
|
XP_HUGE_MEMCPY( pBuf, pBase, hGHContext->uNumRecords * sizeof(gh_HistList *) );
|
|
XP_HUGE_FREE( pBase );
|
|
pBase = pBuf;
|
|
#endif /* XP_WIN16 */
|
|
}
|
|
}while( 0 == (gh_database->seq)(gh_database, &key, &data, R_NEXT) );
|
|
|
|
/*
|
|
// Perform a quick sort on the array, sorting by the pData member.
|
|
*/
|
|
|
|
switch( enGHSort )
|
|
{
|
|
case eGH_NameSort:
|
|
case eGH_LocationSort:
|
|
{
|
|
int32 i;
|
|
gh_HistList XP_HUGE **pList;
|
|
|
|
XP_QSORT( pBase, hGHContext->uNumRecords, sizeof(void *), QSortCompStr );
|
|
|
|
if( enGHSort == eGH_LocationSort )
|
|
{
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Now perform a secondary sort on Location
|
|
*/
|
|
pList = (gh_HistList XP_HUGE **)pBase;
|
|
|
|
for( i = 1; i < (int32)hGHContext->uNumRecords; i++ )
|
|
{
|
|
int32 iStartPos = i-1;
|
|
int32 iBlockLen;
|
|
while( (i < (int32)hGHContext->uNumRecords) &&
|
|
!XP_StrColl( pList[i]->pData, pList[i-1]->pData ) )
|
|
{
|
|
i++;
|
|
}
|
|
iBlockLen = i - iStartPos;
|
|
if( iBlockLen > 1 )
|
|
{
|
|
XP_QSORT( pList+iStartPos, iBlockLen, sizeof(void *), QSortCompStr2 );
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case eGH_FirstDateSort:
|
|
case eGH_LastDateSort:
|
|
{
|
|
int32 i;
|
|
gh_HistList XP_HUGE **pList;
|
|
|
|
XP_QSORT( pBase, hGHContext->uNumRecords, sizeof(void *), QSortCompDate );
|
|
|
|
/*
|
|
* Now perform a secondary sort on Location
|
|
*/
|
|
pList = (gh_HistList XP_HUGE **)pBase;
|
|
|
|
for( i = 1; i < (int32)hGHContext->uNumRecords; i++ )
|
|
{
|
|
int32 iStartPos = i-1;
|
|
int32 iBlockLen;
|
|
while( (i < (int32)hGHContext->uNumRecords) &&
|
|
*(time_t *)pList[i]->pData == *(time_t *)pList[i-1]->pData )
|
|
{
|
|
i++;
|
|
}
|
|
iBlockLen = i - iStartPos;
|
|
if( iBlockLen > 1 )
|
|
{
|
|
XP_QSORT( pList+iStartPos, iBlockLen, sizeof(void *), QSortCompStr2 );
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case eGH_VisitCountSort:
|
|
{
|
|
int32 i;
|
|
gh_HistList XP_HUGE **pList;
|
|
|
|
XP_QSORT( pBase, hGHContext->uNumRecords, sizeof(void *), QSortCompInt32 );
|
|
|
|
/*
|
|
* Now perform a secondary sort on Location
|
|
*/
|
|
pList = (gh_HistList XP_HUGE **)pBase;
|
|
|
|
for( i = 1; i < (int32)hGHContext->uNumRecords; i++ )
|
|
{
|
|
int32 iStartPos = i-1;
|
|
int32 iBlockLen;
|
|
|
|
while( (i < (int32)hGHContext->uNumRecords) &&
|
|
*(int32 *)pList[i]->pData == *(int32 *)pList[i-1]->pData )
|
|
{
|
|
i++;
|
|
}
|
|
iBlockLen = i - iStartPos;
|
|
if( iBlockLen > 1 )
|
|
{
|
|
XP_QSORT( pList+iStartPos, iBlockLen, sizeof(void *), QSortCompStr2 );
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
hGHContext->pHistSort = (gh_HistList XP_HUGE **)pBase;
|
|
|
|
return hGHContext;
|
|
}
|
|
|
|
PUBLIC void
|
|
GH_ReleaseContext( GHHANDLE pContext, Bool bReleaseUR )
|
|
{
|
|
gh_HistContext *hGHContext = (gh_HistContext *)pContext;
|
|
uint32 uRow;
|
|
|
|
if( !pContext )
|
|
{
|
|
return;
|
|
}
|
|
|
|
XP_ASSERT( pHistContextList );
|
|
|
|
if( pHistContextList == hGHContext )
|
|
{
|
|
if( hGHContext->pNext == hGHContext )
|
|
{
|
|
pHistContextList = NULL;
|
|
}
|
|
else
|
|
{
|
|
pHistContextList = hGHContext->pNext;
|
|
}
|
|
}
|
|
hGHContext->pPrev->pNext = hGHContext->pNext;
|
|
hGHContext->pNext->pPrev = hGHContext->pPrev;
|
|
|
|
/*
|
|
// Release all the memory associated with the History Context
|
|
*/
|
|
|
|
for( uRow = 0; uRow < hGHContext->uNumRecords; uRow++ )
|
|
{
|
|
XP_FREE( hGHContext->pHistSort[uRow]->pKeyData );
|
|
|
|
if( hGHContext->enGHSort != eGH_LocationSort )
|
|
{
|
|
/* The pData member points to the same storage as pKeyData for Location sort */
|
|
|
|
XP_FREE( hGHContext->pHistSort[uRow]->pData );
|
|
}
|
|
|
|
XP_FREE( hGHContext->pHistSort[uRow] );
|
|
}
|
|
|
|
XP_HUGE_FREE( hGHContext->pHistSort );
|
|
|
|
if( bReleaseUR )
|
|
{
|
|
GH_ReleaseURContext( hGHContext->pURContext );
|
|
}
|
|
}
|
|
|
|
PUBLIC uint32
|
|
GH_GetNumRecords( GHHANDLE pContext )
|
|
{
|
|
gh_HistContext *hGHContext = (gh_HistContext *)pContext;
|
|
|
|
return hGHContext ? hGHContext->uNumRecords : 0;
|
|
}
|
|
|
|
PUBLIC gh_SortColumn
|
|
GH_GetSortField( GHHANDLE pContext )
|
|
{
|
|
gh_HistContext *hGHContext = (gh_HistContext *)pContext;
|
|
|
|
return hGHContext ? hGHContext->enGHSort : eGH_NoSort;
|
|
}
|
|
|
|
|
|
PR_PUBLIC_API(void) updateNewHistItem (DBT *key, DBT *data);
|
|
|
|
|
|
PUBLIC int
|
|
GH_UpdateURLTitle( URL_Struct *pUrl, char *pszTitle, Bool bFrameCell )
|
|
{
|
|
DBT key, data, dataNew;
|
|
int status;
|
|
int iNameLen;
|
|
int8 *pData;
|
|
static int32 count=0;
|
|
int32 iFlags = bFrameCell ? (GH_FLAGS_SHOW | GH_FLAGS_FRAMECELL) : GH_FLAGS_SHOW;
|
|
|
|
if( !pUrl || !pUrl->address || !pszTitle )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if( !gh_database )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
gh_open_database();
|
|
|
|
if( !gh_database )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
global_history_has_changed = TRUE;
|
|
urlLookupGlobalHistHasChanged = TRUE;
|
|
|
|
count++; /* Increment change count */
|
|
|
|
key.data = (void *)pUrl->address;
|
|
key.size = (XP_STRLEN(pUrl->address)+1) * sizeof(char);
|
|
|
|
status = (*gh_database->get)(gh_database, &key, &data, 0);
|
|
|
|
if( status < 0 )
|
|
{
|
|
TRACEMSG(("Database ERROR retreiving global history entry"));
|
|
return -1;
|
|
}
|
|
else if( status > 0 )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
/* Object was found */
|
|
|
|
iNameLen = XP_STRLEN( pszTitle )+1;
|
|
dataNew.size = sizeof(int32) + sizeof(int32) + sizeof(int32) + sizeof(int32) + iNameLen*sizeof(char);
|
|
|
|
pData = XP_ALLOC( dataNew.size );
|
|
|
|
dataNew.data = (void *)pData;
|
|
|
|
/*
|
|
// Copy the record's data into the new buffer
|
|
*/
|
|
XP_MEMCPY( pData, data.data, (data.size < dataNew.size) ? data.size : dataNew.size );
|
|
|
|
/*
|
|
// Now overwrite the old title with the new
|
|
*/
|
|
if( iNameLen > 1 )
|
|
{
|
|
XP_STRCPY( (char *)pData+4*sizeof(int32), pszTitle );
|
|
}
|
|
else
|
|
{
|
|
*(char *)(pData+4*sizeof(int32)) = 0;
|
|
}
|
|
|
|
/*
|
|
// Mark this record for global history viewing
|
|
*/
|
|
COPY_INT32( pData+3*sizeof(int32), &iFlags );
|
|
|
|
/*
|
|
// Update the table
|
|
*/
|
|
status = (*gh_database->put)( gh_database, &key, &dataNew, 0 );
|
|
|
|
|
|
/* update the history display in nav center if its open */
|
|
if (iFlags == 1) updateNewHistItem(&key, &dataNew);
|
|
|
|
|
|
XP_FREE( pData );
|
|
|
|
if( status < 0 )
|
|
{
|
|
TRACEMSG(("Global history update failed due to database error"));
|
|
gh_RemoveDatabase();
|
|
}
|
|
else if( count >= SYNC_RATE )
|
|
{
|
|
count = 0;
|
|
if( -1 == (*gh_database->sync)( gh_database, 0 ) )
|
|
{
|
|
TRACEMSG(("Error syncing gh database"));
|
|
}
|
|
}
|
|
|
|
/*
|
|
// Notify the contexts of the update
|
|
*/
|
|
GH_NotifyContexts( GH_NOTIFY_UPDATE, (char *)key.data );
|
|
return 0;
|
|
}
|
|
|
|
PUBLIC gh_HistEntry *
|
|
GH_GetRecord( GHHANDLE pContext, uint32 uRow )
|
|
{
|
|
DBT key, data;
|
|
static gh_HistEntry ghEntry;
|
|
static char szTitle[1024];
|
|
int32 iDefault = 1;
|
|
|
|
gh_HistContext *hGHContext = (gh_HistContext *)pContext;
|
|
|
|
if( !hGHContext || !hGHContext->uNumRecords )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if( uRow > (hGHContext->uNumRecords-1) )
|
|
{
|
|
/* Row is out of range (cannot be less than 0 since it is unsigned */
|
|
return NULL;
|
|
}
|
|
|
|
key.data = (void *)hGHContext->pHistSort[uRow]->pKeyData;
|
|
key.size = (XP_STRLEN(key.data)+1) * sizeof(char);
|
|
|
|
ghEntry.address = key.data;
|
|
|
|
if( 0 == (*gh_database->get)( gh_database, &key, &data, 0 ) )
|
|
{
|
|
if( data.size > sizeof(int32) )
|
|
{
|
|
/* The entry/record is of the new format */
|
|
|
|
COPY_INT32( &ghEntry.first_accessed, (int8 *)data.data + sizeof(int32) );
|
|
|
|
COPY_INT32( &ghEntry.last_accessed, data.data );
|
|
|
|
COPY_INT32( &ghEntry.iCount, (int8 *)data.data + 2*sizeof(int32) );
|
|
|
|
XP_STRNCPY_SAFE( szTitle, (char *)data.data + 4*sizeof(int32), sizeof(szTitle)-1 );
|
|
ghEntry.pszName = szTitle;
|
|
}
|
|
else
|
|
{
|
|
/* The entry/record is of the old format i.e., only last_accessed date available */
|
|
|
|
char *pszTitle = GH_GetTitleFromURL( ghEntry.address );
|
|
XP_STRNCPY_SAFE( szTitle, pszTitle, sizeof(szTitle)-1 );
|
|
pszTitle = strrchr( szTitle, '/' );
|
|
if( pszTitle )
|
|
{
|
|
/* Remove the trailing slash from the title */
|
|
*pszTitle = 0;
|
|
}
|
|
ghEntry.pszName = szTitle;
|
|
|
|
COPY_INT32( &ghEntry.first_accessed, data.data );
|
|
|
|
COPY_INT32( &ghEntry.last_accessed, data.data );
|
|
|
|
COPY_INT32( &ghEntry.iCount, &iDefault );
|
|
}
|
|
return &ghEntry;
|
|
|
|
}
|
|
else
|
|
{
|
|
/* The entry is not there, we're out of sync somehow. */
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
PUBLIC void
|
|
GH_DeleteRecord( GHHANDLE pContext, uint32 uRow, Bool bGroup )
|
|
{
|
|
DBT key, data;
|
|
int status;
|
|
|
|
gh_HistContext *hGHContext = (gh_HistContext *)pContext;
|
|
|
|
if( !hGHContext || !hGHContext->uNumRecords )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( uRow > (hGHContext->uNumRecords-1) )
|
|
{
|
|
/* Row is out of range (cannot be less than 0 since it is unsigned */
|
|
return;
|
|
}
|
|
|
|
key.data = (void *)hGHContext->pHistSort[uRow]->pKeyData;
|
|
key.size = (XP_STRLEN(key.data)+1) * sizeof(char);
|
|
|
|
if( hGHContext->pURContext )
|
|
{
|
|
gh_RecordList * pRecordNode = NULL;
|
|
gh_URList * pURNode = NULL;
|
|
|
|
/* Get the record's data so we can undo the operation if necessary */
|
|
|
|
status = (*gh_database->get)( gh_database, &key, &data, 0 );
|
|
if( status < 0 )
|
|
{
|
|
TRACEMSG(("Database ERROR retreiving global history entry"));
|
|
return;
|
|
}
|
|
else if( status > 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
pRecordNode = GH_CreateRecordNode( &key, &data );
|
|
|
|
if( bGroup )
|
|
{
|
|
GH_PushGroupUndo( hGHContext->pURContext, pRecordNode );
|
|
}
|
|
else
|
|
{
|
|
pURNode = GH_CreateURNode( pRecordNode );
|
|
GH_PushUR( &hGHContext->pURContext->pUndoList, pURNode );
|
|
|
|
/* Must purge the redo list whenever we add a new undo item */
|
|
GH_DeleteURList( hGHContext->pURContext->pRedoList );
|
|
hGHContext->pURContext->pRedoList = NULL;
|
|
}
|
|
}
|
|
|
|
(*gh_database->del)( gh_database, &key, 0 );
|
|
|
|
/*
|
|
// Notify the contexts of the deletion
|
|
*/
|
|
GH_NotifyContexts( GH_NOTIFY_DELETE, (char *)key.data );
|
|
}
|
|
|
|
PUBLIC int32
|
|
GH_GetRecordNum( GHHANDLE pContext, char *pszLocation )
|
|
{
|
|
int32 i = 0;
|
|
|
|
gh_HistContext *hGHContext = (gh_HistContext *)pContext;
|
|
|
|
if( !hGHContext || !hGHContext->uNumRecords || !pszLocation )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
for( i = 0; i < (int32)hGHContext->uNumRecords; i++ )
|
|
{
|
|
if( !XP_STRCMP( pszLocation, hGHContext->pHistSort[i]->pKeyData ) )
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Writes out a URL entry to look like:
|
|
*
|
|
* <DT><A HREF="http://www.netscape.com" \
|
|
* ADD_DATE="777240414" LAST_VISIT="802992591">Welcome To Netscape</A>
|
|
*
|
|
*/
|
|
PRIVATE int
|
|
GH_WriteURL( XP_File fp, gh_HistEntry *item )
|
|
{
|
|
char buffer[16];
|
|
|
|
WRITE( "<DT>", -1, fp );
|
|
|
|
/* write address */
|
|
WRITE( "<A HREF=\"", -1, fp );
|
|
WRITE( item->address, -1, fp );
|
|
WRITE( "\"", -1, fp );
|
|
|
|
/* write the addition date */
|
|
WRITE( " FIRST_VISIT=\"", -1, fp );
|
|
XP_SPRINTF( buffer, "%ld", item->first_accessed );
|
|
WRITE( buffer, -1, fp );
|
|
WRITE( "\"", -1, fp );
|
|
|
|
/* write the last visited date */
|
|
WRITE( " LAST_VISIT=\"", -1, fp );
|
|
XP_SPRINTF( buffer, "%ld\"", item->last_accessed );
|
|
WRITE( buffer, -1, fp );
|
|
|
|
/* write the last modified date */
|
|
WRITE( " VISIT_COUNT=\"", -1, fp );
|
|
XP_SPRINTF( buffer, "%i\"", item->iCount );
|
|
WRITE( buffer, -1, fp );
|
|
|
|
WRITE( ">", -1, fp );
|
|
|
|
/* write the name */
|
|
|
|
if( item->pszName )
|
|
{
|
|
WRITE( item->pszName, -1, fp );
|
|
}
|
|
|
|
WRITE( "</A>", -1, fp );
|
|
WRITE( LINEBREAK, LINEBREAK_LEN, fp );
|
|
|
|
return 0;
|
|
}
|
|
|
|
PRIVATE void
|
|
GH_WriteHTML( MWContext *context, char *filename, GHHANDLE pContext )
|
|
{
|
|
XP_File fp = NULL;
|
|
XP_FileType tmptype;
|
|
char * tmpname = NULL;
|
|
int32 i = 0;
|
|
gh_HistContext * hGHContext = (gh_HistContext *)pContext;
|
|
|
|
if( !hGHContext || !filename || (filename[0] == '\0') )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
tmpname = FE_GetTempFileFor( NULL, filename, xpGlobalHistoryList, &tmptype );
|
|
#else
|
|
tmpname = filename;
|
|
tmptype = xpFileToPost; /* let's us simply use the name the user types */
|
|
#endif
|
|
|
|
fp = XP_FileOpen( tmpname, tmptype, XP_FILE_WRITE );
|
|
|
|
if( !fp )
|
|
{
|
|
goto FAIL;
|
|
}
|
|
|
|
/* write cookie */
|
|
if( gh_write_ok( GLHIST_COOKIE, -1, fp) < 0 ) goto FAIL;
|
|
if( gh_write_ok( LINEBREAK, LINEBREAK_LEN, fp ) < 0 ) goto FAIL;
|
|
if( gh_write_ok( LINEBREAK, LINEBREAK_LEN, fp ) < 0 ) goto FAIL;
|
|
|
|
/* Write out all the history records according to the context/cursor */
|
|
for( i = 0; i < (int32)hGHContext->uNumRecords; i++ )
|
|
{
|
|
gh_HistEntry *pHistEntry = GH_GetRecord( hGHContext, i );
|
|
if( pHistEntry )
|
|
{
|
|
GH_WriteURL( fp, pHistEntry );
|
|
}
|
|
}
|
|
|
|
if( XP_FileClose( fp ) != 0 )
|
|
{
|
|
fp = NULL;
|
|
goto FAIL;
|
|
}
|
|
fp = NULL;
|
|
#if 0
|
|
XP_FileRename( tmpname, tmptype, filename, xpGlobalHistoryList );
|
|
#else
|
|
XP_FREE( tmpname );
|
|
#endif
|
|
tmpname = NULL;
|
|
|
|
return;
|
|
|
|
FAIL:
|
|
if( fp )
|
|
{
|
|
XP_FileClose(fp);
|
|
}
|
|
|
|
if( tmpname )
|
|
{
|
|
XP_FileRemove( tmpname, tmptype );
|
|
XP_FREE( tmpname );
|
|
tmpname = NULL;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
PUBLIC void
|
|
GH_FileSaveAsHTML( GHHANDLE pContext, MWContext *pMWContext )
|
|
{
|
|
if( !pContext )
|
|
{
|
|
return;
|
|
}
|
|
|
|
FE_PromptForFileName( pMWContext, XP_GetString( XP_HISTORY_SAVE ), 0, FALSE, FALSE, GH_WriteHTML, pContext );
|
|
}
|
|
|
|
PUBLIC GHURHANDLE GH_GetURContext( GHHANDLE pContext )
|
|
{
|
|
gh_HistContext *hGHContext = (gh_HistContext *)pContext;
|
|
|
|
if( !pContext )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return hGHContext->pURContext;
|
|
}
|
|
|
|
PUBLIC void GH_SupportUndoRedo( GHHANDLE pContext )
|
|
{
|
|
gh_HistContext *hGHContext = (gh_HistContext *)pContext;
|
|
|
|
if( !pContext )
|
|
{
|
|
return;
|
|
}
|
|
|
|
GH_CreateURContext( hGHContext );
|
|
}
|
|
|
|
PRIVATE gh_RecordList *
|
|
GH_CreateRecordNode( DBT *pKey, DBT *pData )
|
|
{
|
|
gh_RecordList *pRecordNode = NULL;
|
|
|
|
if( !pKey ||
|
|
!pKey->data ||
|
|
!pData ||
|
|
!pData->data )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
pRecordNode = XP_ALLOC( sizeof(gh_RecordList) );
|
|
|
|
pRecordNode->key.data = XP_ALLOC( pKey->size );
|
|
XP_MEMCPY( pRecordNode->key.data, pKey->data, pKey->size );
|
|
pRecordNode->key.size = pKey->size;
|
|
|
|
pRecordNode->data.data = XP_ALLOC( pData->size );
|
|
XP_MEMCPY( pRecordNode->data.data, pData->data, pData->size );
|
|
pRecordNode->data.size = pData->size;
|
|
|
|
pRecordNode->pNext = NULL;
|
|
|
|
return pRecordNode;
|
|
}
|
|
|
|
PRIVATE void
|
|
GH_PushRecord( gh_RecordList **ppRecordList, gh_RecordList *pRecordNode )
|
|
{
|
|
if( !ppRecordList || !pRecordNode )
|
|
{
|
|
return;
|
|
}
|
|
|
|
pRecordNode->pNext = *ppRecordList ? *ppRecordList : NULL;
|
|
*ppRecordList = pRecordNode;
|
|
}
|
|
|
|
PRIVATE void
|
|
GH_PushGroupUndo( gh_URContext *pURContext, gh_RecordList *pRecordNode )
|
|
{
|
|
if( !pURContext || !pURContext->pUndoList || !pRecordNode )
|
|
{
|
|
return;
|
|
}
|
|
|
|
GH_PushRecord( &pURContext->pUndoList->pURItem, pRecordNode );
|
|
}
|
|
|
|
PRIVATE gh_URList *
|
|
GH_CreateURNode( gh_RecordList *pRecordList )
|
|
{
|
|
gh_URList *pURNode = NULL;
|
|
|
|
if( !pRecordList )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
pURNode = XP_ALLOC( sizeof(gh_URList) );
|
|
pURNode->pURItem = pRecordList;
|
|
pURNode->pNext = NULL;
|
|
|
|
return pURNode;
|
|
}
|
|
|
|
PRIVATE void
|
|
GH_PushUR( gh_URList **ppURList, gh_URList *pURNode )
|
|
{
|
|
if( !ppURList || !pURNode )
|
|
{
|
|
return;
|
|
}
|
|
|
|
pURNode->pNext = *ppURList ? *ppURList : NULL;
|
|
*ppURList = pURNode;
|
|
}
|
|
|
|
PRIVATE gh_URList *
|
|
GH_PopUR( gh_URList **ppURList )
|
|
{
|
|
gh_URList *pURNode = NULL;
|
|
|
|
if( !ppURList )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
pURNode = *ppURList;
|
|
*ppURList = (*ppURList)->pNext;
|
|
|
|
return pURNode;
|
|
}
|
|
|
|
PUBLIC void
|
|
GH_Undo( GHHANDLE hContext )
|
|
{
|
|
gh_HistContext *pContext = (gh_HistContext *)hContext;
|
|
gh_URList *pURNode = NULL;
|
|
|
|
int status;
|
|
gh_RecordList *pCsr;
|
|
|
|
if( !pContext ||
|
|
!pContext->pURContext ||
|
|
!pContext->pURContext->pUndoList )
|
|
{
|
|
return;
|
|
}
|
|
|
|
pURNode = GH_PopUR( &pContext->pURContext->pUndoList );
|
|
if( !pURNode )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !gh_database )
|
|
{
|
|
return;
|
|
}
|
|
|
|
gh_open_database();
|
|
|
|
if( !gh_database )
|
|
{
|
|
return;
|
|
}
|
|
|
|
global_history_has_changed = TRUE;
|
|
urlLookupGlobalHistHasChanged = TRUE;
|
|
|
|
|
|
pCsr = pURNode->pURItem;
|
|
while( pCsr )
|
|
{
|
|
/*
|
|
// Update the table
|
|
*/
|
|
status = (*gh_database->put)( gh_database, &pCsr->key, &pCsr->data, 0 );
|
|
|
|
if( status < 0 )
|
|
{
|
|
TRACEMSG(("Global history update failed due to database error"));
|
|
gh_RemoveDatabase();
|
|
}
|
|
|
|
/*
|
|
// Notify the contexts of the update
|
|
*/
|
|
GH_NotifyContexts( GH_NOTIFY_UPDATE, (char *)pCsr->key.data );
|
|
|
|
pCsr = pCsr->pNext;
|
|
}
|
|
|
|
GH_PushUR( &pContext->pURContext->pRedoList, pURNode );
|
|
}
|
|
|
|
PUBLIC void
|
|
GH_Redo( GHHANDLE hContext )
|
|
{
|
|
gh_HistContext *pContext = (gh_HistContext *)hContext;
|
|
gh_URList *pURNode = NULL;
|
|
|
|
int status;
|
|
gh_RecordList *pCsr;
|
|
|
|
if( !pContext ||
|
|
!pContext->pURContext ||
|
|
!pContext->pURContext->pRedoList )
|
|
{
|
|
return;
|
|
}
|
|
|
|
pURNode = GH_PopUR( &pContext->pURContext->pRedoList );
|
|
if( !pURNode )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( !gh_database )
|
|
{
|
|
return;
|
|
}
|
|
|
|
gh_open_database();
|
|
|
|
if( !gh_database )
|
|
{
|
|
return;
|
|
}
|
|
|
|
global_history_has_changed = TRUE;
|
|
urlLookupGlobalHistHasChanged = TRUE;
|
|
|
|
|
|
pCsr = pURNode->pURItem;
|
|
while( pCsr )
|
|
{
|
|
/*
|
|
// Delete the record
|
|
*/
|
|
status = (*gh_database->del)( gh_database, &pCsr->key, 0 );
|
|
|
|
if( status < 0 )
|
|
{
|
|
TRACEMSG(("Global history update failed due to database error"));
|
|
gh_RemoveDatabase();
|
|
}
|
|
else if( status > 0 )
|
|
{
|
|
/* Not found */
|
|
pCsr = pCsr->pNext;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
// Notify the contexts of the deletion
|
|
*/
|
|
GH_NotifyContexts( GH_NOTIFY_DELETE, (char *)pCsr->key.data );
|
|
|
|
pCsr = pCsr->pNext;
|
|
}
|
|
|
|
GH_PushUR( &pContext->pURContext->pUndoList, pURNode );
|
|
}
|
|
|
|
PUBLIC Bool GH_CanUndo( GHHANDLE hContext )
|
|
{
|
|
Bool bRet = FALSE;
|
|
gh_HistContext *pContext = (gh_HistContext *)hContext;
|
|
|
|
if( pContext &&
|
|
pContext->pURContext &&
|
|
pContext->pURContext->pUndoList )
|
|
{
|
|
bRet = TRUE;
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
PUBLIC Bool GH_CanRedo( GHHANDLE hContext )
|
|
{
|
|
Bool bRet = FALSE;
|
|
gh_HistContext *pContext = (gh_HistContext *)hContext;
|
|
|
|
if( pContext &&
|
|
pContext->pURContext &&
|
|
pContext->pURContext->pRedoList )
|
|
{
|
|
bRet = TRUE;
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
PRIVATE void GH_DeleteURList( gh_URList *pURList )
|
|
{
|
|
gh_URList *pTrash = NULL;
|
|
|
|
if( !pURList )
|
|
{
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
pTrash = pURList;
|
|
pURList = pURList->pNext;
|
|
GH_DeleteRecordList( pTrash->pURItem );
|
|
XP_FREE( pTrash );
|
|
|
|
}while( pURList );
|
|
}
|
|
|
|
PUBLIC int GH_GetMRUPage( char *pszURL, int iMaxLen )
|
|
{
|
|
/*
|
|
// Note this function will not return a cell in a frame, instead it returns the mru frame set url.
|
|
*/
|
|
time_t date1, date2 = 0;
|
|
DBT key, data;
|
|
|
|
if( !gh_database || (global_history_timeout_interval < 1) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if( !pszURL || (iMaxLen <= 0) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
*pszURL = 0;
|
|
|
|
data.size = key.size = 0;
|
|
|
|
/*
|
|
// Visit each page in the history list while maintaining the most recently visited page.
|
|
*/
|
|
if( 0 != (*gh_database->seq)( gh_database, &key, &data, R_FIRST ) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
do
|
|
{
|
|
if( data.size > sizeof(int32) )
|
|
{
|
|
/*
|
|
// The entry/record is of the new format...
|
|
//
|
|
|
|
// Ignore history records which are NOT flagged as having been explicitly loaded
|
|
// e.g., don't expose gif images that are only part of a page
|
|
*/
|
|
int32 iFlags;
|
|
COPY_INT32( &iFlags, (int8 *)data.data + 3*sizeof(int32) );
|
|
|
|
if( !(iFlags & GH_FLAGS_SHOW) || (iFlags & GH_FLAGS_FRAMECELL) )
|
|
{
|
|
/* The record is either not flagged for showing or is just a cell or both */
|
|
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
// The entry/record is of the old format...
|
|
//
|
|
|
|
// Try to be somewhat smart and filter the implicitly loaded stuff i.e., non-html docs
|
|
*/
|
|
char *pszExt = strrchr( key.data, '.' );
|
|
|
|
if( ((char *)key.data)[XP_STRLEN(key.data)-1] == '/' )
|
|
{
|
|
}
|
|
else if( XP_STRLEN(key.data) < 5 )
|
|
{
|
|
continue;
|
|
}
|
|
else if( pszExt )
|
|
{
|
|
pszExt++;
|
|
if( strncasecomp( pszExt, "htm", 3 ) )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Note the last_accessed field is at the beginning of the data regardless of new/old format */
|
|
|
|
COPY_INT32( &date1, data.data );
|
|
|
|
if( difftime( date1, date2 ) > 0 )
|
|
{
|
|
XP_STRNCPY_SAFE( pszURL, key.data, iMaxLen );
|
|
COPY_INT32( &date2, (int8 *)data.data );
|
|
}
|
|
|
|
}while( 0 == (gh_database->seq)( gh_database, &key, &data, R_NEXT ) );
|
|
|
|
return XP_STRLEN( pszURL );
|
|
}
|
|
|
|
PRIVATE void GH_DeleteRecordList( gh_RecordList *pRecordList )
|
|
{
|
|
gh_RecordList *pTrash = NULL;
|
|
|
|
if( !pRecordList )
|
|
{
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
pTrash = pRecordList;
|
|
pRecordList = pRecordList->pNext;
|
|
XP_FREE( pTrash->key.data );
|
|
XP_FREE( pTrash->data.data );
|
|
XP_FREE( pTrash );
|
|
|
|
}while( pRecordList );
|
|
}
|
|
|
|
/*
|
|
// Return a pointer somewhere in pszURL where a somewhat meaningful title may be.
|
|
// A pointer to the char following the last or second to last slash is returned
|
|
// if a string exists after the slash, otherwise pszURL is returned.
|
|
*/
|
|
PRIVATE char *GH_GetTitleFromURL( char *pszURL )
|
|
{
|
|
char *pszTitle = NULL;
|
|
char *pszSlash = NULL;
|
|
|
|
if( !pszURL || !*pszURL )
|
|
{
|
|
return pszURL;
|
|
}
|
|
|
|
pszTitle = strrchr( pszURL, '/' );
|
|
|
|
if( pszTitle )
|
|
{
|
|
if( *(pszTitle+1) )
|
|
{
|
|
/*
|
|
// The location does not end with a slash so we'll use the sub-string
|
|
// following the the last slash.
|
|
*/
|
|
|
|
pszTitle++;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
// The location ends with a slash, so we'll start at the second from last slash.
|
|
*/
|
|
|
|
*pszTitle = 0;
|
|
pszSlash = pszTitle; /* Save this position so we can put the slash back */
|
|
pszTitle = strrchr( pszURL, '/' );
|
|
*pszSlash = '/';
|
|
|
|
if( pszTitle )
|
|
{
|
|
if( *(pszTitle+1) )
|
|
{
|
|
pszTitle++;
|
|
}
|
|
else
|
|
{
|
|
pszTitle = pszURL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pszTitle = pszURL;
|
|
}
|
|
|
|
return pszTitle;
|
|
}
|
|
|
|
/* fix Mac warning about missing prototype */
|
|
PUBLIC Bool
|
|
NET_EnableUrlMatch(void);
|
|
|
|
PUBLIC Bool
|
|
NET_EnableUrlMatch(void)
|
|
{
|
|
return enableUrlMatch;
|
|
}
|
|
|
|
/* fix Mac warning about missing prototype */
|
|
PUBLIC void
|
|
NET_SetEnableUrlMatchPref(Bool x);
|
|
|
|
PUBLIC void
|
|
NET_SetEnableUrlMatchPref(Bool x)
|
|
{
|
|
enableUrlMatch=x;
|
|
}
|
|
|
|
/* fix Mac warning about missing prototype */
|
|
MODULE_PRIVATE int PR_CALLBACK
|
|
NET_EnableUrlMatchPrefChanged(const char *pref, void *data);
|
|
|
|
MODULE_PRIVATE int PR_CALLBACK
|
|
NET_EnableUrlMatchPrefChanged(const char *pref, void *data)
|
|
{
|
|
Bool x;
|
|
|
|
PREF_GetBoolPref("network.enableUrlMatch", &x);
|
|
NET_SetEnableUrlMatchPref(x);
|
|
return PREF_NOERROR;
|
|
}
|
|
|
|
PUBLIC void
|
|
NET_RegisterEnableUrlMatchCallback(void)
|
|
{
|
|
Bool x;
|
|
|
|
PREF_GetBoolPref("network.enableUrlMatch", &x);
|
|
NET_SetEnableUrlMatchPref(x);
|
|
PREF_RegisterCallback("network.enableUrlMatch", NET_EnableUrlMatchPrefChanged, NULL);
|
|
}
|
|
|
|
/* Is the string passed in too general. */
|
|
PRIVATE Bool
|
|
net_url_sub_string_too_general(const char *criteria, int32 len)
|
|
{
|
|
if( (!criteria) || (len < 1) )
|
|
return TRUE;
|
|
|
|
/* case insensative compares */
|
|
if( !strncasecomp(criteria, "www.", len) ||
|
|
!strncasecomp(criteria, "http://www.", len) ||
|
|
!strncasecomp(criteria, "ftp.", len) ||
|
|
!strncasecomp(criteria, "ftp://ftp.", len) ||
|
|
!strncasecomp(criteria, "file:", len)
|
|
)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
/* Determines whether we want to deal with this url. I'm doing some interpretation here. If the user has
|
|
www.abc.com/cgi/laksjdlskjds121212121 in their global history, I'm assuming they don't want this to come
|
|
up in the completeion. They may though. You can't satisfy everyone. */
|
|
PRIVATE Bool
|
|
net_url_weed_out(const char *url, int32 len)
|
|
{
|
|
if( (!url) || (len < 0) )
|
|
return TRUE;
|
|
url = url + len - 1;
|
|
if(!(*url == 'l' ||
|
|
*url == 'L' ||
|
|
*url == 'm' ||
|
|
*url == 'M' ||
|
|
*url == 'p' || /* some msoft frontpage-like cgi filename i.e. default.asp */
|
|
*url == 'P' ||
|
|
*url == '/') )
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* Description:
|
|
Tries to find a match in the global history database given the criteria string. If found
|
|
the match is returned via result (result is passed in as an unallocated address of a char *,
|
|
and returned as an allocated char * if the return val of the fctn is foundDone; you're
|
|
responsible for freeing it).
|
|
|
|
Parameters:
|
|
criteria - a string for me to search for.
|
|
result - the address of a char * where I will put data if I find it.
|
|
freshStart - Is this the first time calling me? Yes == TRUE.
|
|
scroll - Am I being called because user wants to scroll through matches?
|
|
|
|
Return Type:
|
|
enum autoCompStatus (declared in glhist.h)
|
|
Return Values:
|
|
foundDone - the unallocated address of a char * you passed in now has data in it. The data
|
|
consists of the best match I could find.
|
|
notFoundDone - I searched all my resources and couldn't find anything (ie don't call me again
|
|
with the same criteria), or I found something but it was the same thing I last returned to
|
|
you, or what you passed in is what I returned to you last time you called.
|
|
stillSearching - I don't search through all my resources at once, call me again
|
|
dontCallOnIdle - Don't call me again with the same criteria, the criteria is too general and
|
|
I don't want to waste cycles.
|
|
|
|
This function uses a totally inefficient means of searching (sequential). Function is optimized
|
|
for speed, not flexibility or readibility.
|
|
|
|
Created by: Judson Valeski, 1997
|
|
*/
|
|
PUBLIC enum autoCompStatus
|
|
urlMatch(const char *criteria, char **result, Bool freshStart, Bool scroll)
|
|
{
|
|
static char *lastURLCompletion=NULL;
|
|
int32 eLen, cLen, cPathLen=0, count=0;
|
|
DBT key, data;
|
|
char *t=NULL, *p=NULL, *host=NULL, *ePath=NULL, *cPath=NULL;
|
|
char *eProtocolColon=NULL, *cProtocolColon=NULL;
|
|
|
|
if(!NET_EnableUrlMatch())
|
|
return notFoundDone;
|
|
|
|
if(!criteria || (*criteria == '/') )
|
|
return notFoundDone;
|
|
|
|
/* Is it ok to use the database. */
|
|
if(!gh_database || global_history_timeout_interval < 1)
|
|
return notFoundDone;
|
|
|
|
cLen = XP_STRLEN(criteria);
|
|
|
|
/* Is the criteria too general? ie. www or ftp, etc */
|
|
if( net_url_sub_string_too_general(criteria, cLen) )
|
|
return dontCallOnIdle;
|
|
|
|
/* Did the user include a protocol? If so, we want to search with protocol included. */
|
|
cProtocolColon=XP_STRSTR(criteria, "://");
|
|
|
|
/* Check to see if user has path info on url */
|
|
if(cProtocolColon)
|
|
cPath=XP_STRCHR(cProtocolColon+3, '/');
|
|
else
|
|
cPath=XP_STRCHR(criteria, '/');
|
|
|
|
if(cPath)
|
|
cPathLen=XP_STRLEN(cPath);
|
|
|
|
if(freshStart || urlLookupGlobalHistHasChanged) {
|
|
if(0 != (*gh_database->seq)(gh_database, &key, &data, R_FIRST))
|
|
return notFoundDone;
|
|
}
|
|
else {
|
|
if(0 != (gh_database->seq)(gh_database, &key, &data, R_NEXT) )
|
|
return notFoundDone;
|
|
}
|
|
|
|
urlLookupGlobalHistHasChanged = FALSE;
|
|
|
|
/* Main search loop */
|
|
do {
|
|
if(count > entriesToSearch) /* entries to search is a static defined above */
|
|
return stillSearching;
|
|
|
|
/* If there's no url or there's no slash in it, move on */
|
|
if( !((char *)key.data) || !(host=XP_STRCHR((char *)key.data, '/')) ) {
|
|
count++;
|
|
continue;
|
|
}
|
|
|
|
/* Get the url out of the db entry and determine whether or not we want
|
|
to include the protocol in our search. After this if stmt, t will point to
|
|
allocated memory that must be free'd. */
|
|
if(cProtocolColon) {
|
|
t = XP_STRDUP((char *)key.data);
|
|
if(!t) {
|
|
count++;
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
/* host is assigned in the previous if stmt and is guaranteed to be valid. */
|
|
if( !(host[0] && host[1] && host[2]) ) {
|
|
/* the db entry isn't in the format that we were expecting */
|
|
count++;
|
|
continue;
|
|
}
|
|
t = XP_STRDUP(host+2);
|
|
if(!t) {
|
|
count++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if( (eProtocolColon=XP_STRSTR(t, "://")) != NULL)
|
|
ePath=XP_STRCHR(eProtocolColon+3, '/');
|
|
else
|
|
ePath=XP_STRCHR(t, '/');
|
|
|
|
/* If there's no path in the entity url then the db entry was bad. Move on. */
|
|
if(!ePath) {
|
|
XP_FREE(t);
|
|
count++;
|
|
continue;
|
|
}
|
|
|
|
eLen = XP_STRLEN(t);
|
|
|
|
if(cPath)
|
|
/* Do we want to weed out the url, ie. it's full of cgi stuff. */
|
|
if( net_url_weed_out(t, eLen) ) {
|
|
XP_FREE(t);
|
|
count++;
|
|
continue;
|
|
}
|
|
|
|
/* See if domains are the same. Case-insensative. */
|
|
*ePath='\0';
|
|
if(cPath)
|
|
*cPath='\0';
|
|
/* If the domains aren't the same. Move on. */
|
|
if(strncasecomp(t, criteria, cLen)) {
|
|
if(cPath)
|
|
*cPath='/';
|
|
XP_FREE(t);
|
|
count++;
|
|
continue;
|
|
}
|
|
*ePath='/';
|
|
if(cPath)
|
|
*cPath='/';
|
|
|
|
/* See if the paths are the same. Case-sensative.
|
|
If there's no cPath and we've gotten this far then the user hasn't specified anything
|
|
more than the domain and the check above determined that the domain matched so continue.
|
|
Otherwise check the remaining chars. */
|
|
if( !cPath || !XP_STRNCMP(ePath, cPath, cPathLen)) {
|
|
/* if user didn't specify path info ,
|
|
set the char just after the end to null byte */
|
|
if(!cPath) {
|
|
if(cProtocolColon) {
|
|
if( (p=XP_STRCHR(t, '/')) != NULL && p[1] && p[2] ) {
|
|
if( (p=XP_STRCHR(p+2, '/')) != NULL ) {
|
|
p[1] = '\0';
|
|
}
|
|
else {
|
|
XP_FREE(t);
|
|
count++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if( (p=XP_STRCHR(t, '/')) != NULL) {
|
|
p[1]= '\0';
|
|
}
|
|
else {
|
|
XP_FREE(t);
|
|
count++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* if we're scrolling && lastURLCompletion is not empty && what we're currently
|
|
matching against isn't what we last returned, or, what we're matching against
|
|
is identical to what the caller passed in. */
|
|
if( (( scroll && lastURLCompletion && (!strcasecomp(t, lastURLCompletion))) )
|
|
||
|
|
(!strcasecomp(t, criteria)) ) {
|
|
|
|
if(!scroll) {
|
|
XP_FREEIF(lastURLCompletion);
|
|
lastURLCompletion=NULL;
|
|
XP_FREE(t);
|
|
return notFoundDone;
|
|
}
|
|
|
|
XP_FREE(t);
|
|
count++;
|
|
continue;
|
|
}
|
|
XP_FREEIF(lastURLCompletion);
|
|
lastURLCompletion = XP_STRDUP(t);
|
|
*result = XP_STRDUP(t);
|
|
XP_FREE(t);
|
|
return foundDone;
|
|
}
|
|
|
|
XP_FREE(t);
|
|
count++;
|
|
}
|
|
while( 0 == (gh_database->seq)(gh_database, &key, &data, R_NEXT) );
|
|
if(!scroll) {
|
|
XP_FREEIF(lastURLCompletion);
|
|
lastURLCompletion=NULL;
|
|
}
|
|
return notFoundDone;
|
|
}
|