home *** CD-ROM | disk | FTP | other *** search
/ Tools / WinSN5.0Ver.iso / NETSCAP.50 / WIN1998.ZIP / ns / lib / libmisc / glhist.c < prev    next >
Encoding:
C/C++ Source or Header  |  1998-04-08  |  93.2 KB  |  3,308 lines

  1. /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  *
  3.  * The contents of this file are subject to the Netscape Public License
  4.  * Version 1.0 (the "NPL"); you may not use this file except in
  5.  * compliance with the NPL.  You may obtain a copy of the NPL at
  6.  * http://www.mozilla.org/NPL/
  7.  *
  8.  * Software distributed under the NPL is distributed on an "AS IS" basis,
  9.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
  10.  * for the specific language governing rights and limitations under the
  11.  * NPL.
  12.  *
  13.  * The Initial Developer of this code under the NPL is Netscape
  14.  * Communications Corporation.  Portions created by Netscape are
  15.  * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
  16.  * Reserved.
  17.  */
  18.  
  19. /*
  20.  */
  21. #include "glhist.h"
  22. #include "xp_hash.h"
  23. #include "net.h"
  24. #include "xp.h"
  25. #include "mcom_db.h"
  26. #include <time.h>
  27. #include "merrors.h"
  28. #include "xpgetstr.h"
  29. #if !defined(XP_MAC)    /* macOS doesn't need this, but other platforms may still */
  30.     #include "bkmks.h"
  31. #endif
  32. #include "prefapi.h"
  33. #include "xplocale.h"
  34. #include "libi18n.h"
  35. #include "xp_qsort.h"
  36.  
  37. #if defined(XP_MAC)
  38.     #include "extcache.h"
  39. #endif
  40.  
  41. extern int XP_GLHIST_INFO_HTML;
  42. extern int XP_GLHIST_DATABASE_CLOSED;
  43. extern int XP_GLHIST_UNKNOWN;
  44. extern int XP_GLHIST_DATABASE_EMPTY;
  45. extern int XP_GLHIST_HTML_DATE    ;
  46. extern int XP_GLHIST_HTML_TOTAL_ENTRIES;
  47. extern int XP_HISTORY_SAVE;
  48.  
  49. /*PRIVATE XP_HashList * global_history_list = 0;*/
  50. PRIVATE Bool          global_history_has_changed = FALSE;
  51. PRIVATE time_t gh_cur_date = 0;
  52. PRIVATE int32 global_history_timeout_interval = -1;
  53.  
  54. /* Autocomplete stuff */
  55. PRIVATE Bool urlLookupGlobalHistHasChanged = FALSE;
  56. PRIVATE int32 entriesToSearch = 100;
  57. PRIVATE Bool enableUrlMatch=TRUE;
  58.  
  59.  
  60. PRIVATE DB * gh_database = 0;
  61. PRIVATE HASHINFO gh_hashinfo;
  62.  
  63. #ifdef XP_MAC
  64.  /* The implementation for ppc/mac std lib does b - a for difftime.. weird  */
  65.  #define difftime(a,b) ((double)((double)(a)-(double)(b)))
  66. #endif
  67.  
  68. #define SYNC_RATE 30  /* number of stores before sync */
  69.  
  70. /*
  71. *  Flags for individual records
  72. */
  73. #define GH_FLAGS_SHOW         0x00000001
  74. #define GH_FLAGS_FRAMECELL    0x00000002
  75.  
  76. static const char *pref_link_expiration = "browser.link_expiration";
  77.  
  78. /*------------------------------------------------------------------------------
  79. //
  80. // Global History context/cursor structure local types
  81. //
  82. ------------------------------------------------------------------------------*/
  83.  
  84. /* Structure defining an array element for the Sort
  85. */
  86. typedef struct _gh_HistList
  87. {
  88.     void *         pKeyData;
  89.     void *         pData;
  90. }gh_HistList;
  91.  
  92. /* Structure defining a list of elements
  93. */
  94. typedef struct _gh_RecordList
  95. {
  96.     struct _gh_RecordList *  pNext;
  97.     
  98.     DBT  key;
  99.     DBT  data;
  100.     
  101. } gh_RecordList;
  102.  
  103. /* Structure defining a node in an undo/redo list.
  104. */
  105. typedef struct _gh_URList
  106. {
  107.     struct _gh_URList *  pNext;
  108.     
  109.     gh_RecordList *pURItem;
  110.     
  111. } gh_URList;
  112.  
  113. /* Structure defining the Global History undo/redo context.
  114. */
  115. typedef struct _gh_URContext
  116. {
  117.     gh_URList *   pUndoList;
  118.     gh_URList *   pRedoList;
  119.     
  120. } gh_URContext;
  121.  
  122. /* Structure defining the Global History context/cursor.
  123. // This is the handle passed back and forth for all the Global History
  124. // Context functions.
  125. */
  126. typedef struct _gh_HistContext
  127. {
  128.     struct _gh_HistContext *   pNext;
  129.     struct _gh_HistContext *   pPrev;    
  130.     
  131.     uint32                     uNumRecords;
  132.     gh_Filter *                pFilter;
  133.     gh_SortColumn              enGHSort;
  134.     gh_HistList XP_HUGE **     pHistSort;
  135.     
  136.     GHISTORY_NOTIFYPROC        pfNotifyProc;
  137.     
  138.     gh_URContext *             pURContext;
  139.     
  140.     void *                     pUserData;
  141.     
  142. } gh_HistContext;
  143.  
  144. /* The list of all contexts in use. */
  145. static gh_HistContext *pHistContextList = NULL;
  146.  
  147. #define GLHIST_COOKIE            "<!DOCTYPE NETSCAPE-history-file-1>"
  148.  
  149. #ifndef BYTE_ORDER
  150. Error!  byte order must be defined
  151. #endif
  152.  
  153. #if !defined(XP_MAC)
  154.     #define COPY_INT32(_a,_b)  XP_MEMCPY(_a, _b, sizeof(int32));
  155. #endif
  156.  
  157. PUBLIC void GH_CollectGarbage(void);
  158.  
  159. PRIVATE void            GH_CreateURContext( gh_HistContext *hGHContext );
  160. PRIVATE void            GH_NotifyContexts( int32 iNotifyMsg, char *pszKey );
  161. PRIVATE gh_RecordList * GH_CreateRecordNode( DBT *pKey, DBT *pData );
  162. PRIVATE void            GH_PushRecord( gh_RecordList **ppRecordList, gh_RecordList *pRecordNode );
  163. PRIVATE void            GH_DeleteRecordList( gh_RecordList *pRecordList );
  164. PRIVATE char *          GH_GetTitleFromURL( char *pszURL );
  165. PRIVATE void            GH_PushGroupUndo( gh_URContext *pURContext, gh_RecordList *pRecordNode );
  166. PRIVATE gh_URList *     GH_CreateURNode( gh_RecordList *pRecordList );
  167. PRIVATE void            GH_PushUR( gh_URList **ppURList, gh_URList *pURNode );
  168. PRIVATE gh_URList *     GH_PopUR( gh_URList **ppURList );
  169. PRIVATE void            GH_DeleteURList( gh_URList *pURList );
  170.  
  171. static int
  172. gh_write_ok(const char* str, int length, XP_File fp)
  173. {
  174.   if (length < 0) length = XP_STRLEN(str);
  175.   if ((int)XP_FileWrite(str, length, fp) < length) return -1;
  176.   return 0;
  177. }
  178. #define WRITE(str, length, fp) \
  179.    if (gh_write_ok((str), (length), (fp)) < 0) return -1
  180.  
  181. #if defined(XP_MAC) || defined(XP_UNIX)
  182. /* set the maximum time for an object in the Global history in
  183.  * number of seconds
  184.  */
  185. PUBLIC void
  186. GH_SetGlobalHistoryTimeout(int32 timeout_interval)
  187. {
  188.     global_history_timeout_interval = timeout_interval;
  189. }
  190. #endif
  191.  
  192. PRIVATE void
  193. gh_set_hash_options(void)
  194. {
  195.     gh_hashinfo.bsize = 4*1024;
  196.     gh_hashinfo.nelem = 0;
  197.     gh_hashinfo.hash = NULL;
  198.     gh_hashinfo.ffactor = 0;
  199.     gh_hashinfo.cachesize = 64 * 1024U;
  200.     gh_hashinfo.lorder = 0;
  201. }
  202.  
  203.  
  204. PRIVATE void
  205. gh_open_database(void)
  206. {
  207. #ifndef NO_DBM
  208.     static Bool have_tried_open=FALSE;
  209.  
  210.     if(gh_database)
  211.       {
  212.         return;
  213.       }
  214.     else
  215.       {
  216.         char* filename;
  217.         gh_set_hash_options();
  218.         filename = WH_FileName("", xpGlobalHistory);
  219.         gh_database = dbopen(filename,
  220.                              O_RDWR | O_CREAT,
  221.                              0600,
  222.                              DB_HASH,
  223.                              &gh_hashinfo);
  224.         if (filename) XP_FREE(filename);
  225.  
  226.         if(!have_tried_open && !gh_database)
  227.           {
  228.             XP_StatStruct stat_entry;
  229.  
  230.             have_tried_open = TRUE; /* only try this once */
  231.  
  232.             TRACEMSG(("Could not open gh database -- errno: %d", errno));
  233.  
  234.  
  235.             /* if the file is zero length remove it
  236.              */
  237.             if(XP_Stat("", &stat_entry, xpGlobalHistory) != -1)
  238.               {
  239.                   if(stat_entry.st_size <= 0)
  240.                     {
  241.                       char* filename = WH_FileName("", xpGlobalHistory);
  242.                       if (!filename) return;
  243.                       XP_FileRemove(filename, xpGlobalHistory);
  244.                       XP_FREE(filename);
  245.                     }
  246.                   else
  247.                     {
  248.                         XP_File fp;
  249. #define BUFF_SIZE 1024
  250.                         char buffer[BUFF_SIZE];
  251.  
  252.                         /* open the file and look for
  253.                          * the old magic cookie.  If it's
  254.                          * there delete the file
  255.                          */
  256.                         fp = XP_FileOpen("", xpGlobalHistory, XP_FILE_READ);
  257.  
  258.                         if(fp)
  259.                           {
  260.                             XP_FileReadLine(buffer, BUFF_SIZE, fp);
  261.         
  262.                             XP_FileClose(fp);
  263.  
  264.                             if(XP_STRSTR(buffer, "Global-history-file")) {
  265.                                 char* filename = WH_FileName("", xpGlobalHistory);
  266.                                 if (!filename) return;
  267.                                   XP_FileRemove(filename, xpGlobalHistory);
  268.                                 XP_FREE(filename);
  269.                             }
  270.                           }
  271.                     }
  272.               }
  273.  
  274.             /* try it again */
  275.             filename = WH_FileName("", xpGlobalHistory);
  276.             gh_database = dbopen(filename,
  277.                              O_RDWR | O_CREAT,
  278.                              0600,
  279.                              DB_HASH,
  280.                              &gh_hashinfo);
  281.             if (filename) XP_FREE(filename);
  282.  
  283.             return;
  284.  
  285.           }
  286.  
  287.         if(gh_database && -1 == (*gh_database->sync)(gh_database, 0))
  288.           {
  289.             TRACEMSG(("Error syncing gh database"));
  290.             (*gh_database->close)(gh_database);
  291.             gh_database = 0;
  292.           }
  293.       }
  294. #endif /* NO_DBM */
  295.  
  296. }
  297.  
  298. /* if the url was found in the global history then a number between
  299.  * 0 and 99 is returned representing the percentage of time that
  300.  * has elapsed in the expiration cycle.
  301.  *  0  means most recently accessed
  302.  * 99  means least recently accessed (about to be expired)
  303.  *
  304.  * If the url was not found -1 is returned
  305.  *
  306.  * define USE_PERCENTS if you want to get percent of time
  307.  * through expires cycle.
  308.  */
  309.  
  310. PUBLIC void
  311. GH_DeleteHistoryItem (char * url) {
  312.     DBT key;
  313.     if(url && gh_database) {
  314.       key.data = (void *) url;
  315.       key.size = (XP_STRLEN(url)+1) * sizeof(char);
  316.       (*gh_database->del)(gh_database, &key, 0);
  317.       (*gh_database->sync)(gh_database, 0);
  318.     }
  319. }
  320.  
  321.  
  322. PUBLIC int
  323. GH_CheckGlobalHistory(char * url)
  324. {
  325.     DBT key;
  326.     DBT data;
  327.     int status;
  328.     time_t entry_date;
  329.  
  330.     if(!url)
  331.         return(-1);
  332.  
  333.     if(!gh_database)
  334.         return(-1);
  335.  
  336.     key.data = (void *) url;
  337.     key.size = (XP_STRLEN(url)+1) * sizeof(char);
  338.  
  339.     status = (*gh_database->get)(gh_database, &key, &data, 0);
  340.  
  341.     if(status < 0)
  342.       {
  343.         TRACEMSG(("Database ERROR retreiving global history entry"));
  344.         return(-1);
  345.       }
  346.     else if(status > 0)
  347.       {
  348.         return(-1);
  349.       }
  350.  
  351.     /* otherwise */
  352.  
  353.     /* object was found.
  354.      * check the time to make sure it hasn't expired
  355.      */
  356.     COPY_INT32( &entry_date, data.data );
  357.     if(global_history_timeout_interval > 0
  358.         && entry_date+global_history_timeout_interval < gh_cur_date)
  359.       {
  360.         /* remove the object
  361.           */
  362.         (*gh_database->del)(gh_database, &key, 0);
  363.  
  364.         /*
  365.         // Notify the contexts of the update
  366.         */
  367.         GH_NotifyContexts( GH_NOTIFY_DELETE, (char *)key.data );
  368.         
  369.         /* return not found
  370.          */
  371.         return(-1);
  372.       }
  373.  
  374.     return(1);
  375. }
  376.  
  377. /* callback routine invoked by prefapi when the pref value changes */
  378. /* fix Mac warning about missing prototype */
  379. int PR_CALLBACK gh_link_expiration_changed(const char * newpref, void * data);
  380.  
  381. int PR_CALLBACK gh_link_expiration_changed(const char * newpref, void * data)
  382. {
  383.     int32 iExp;
  384.         
  385.     /* Get the number of days for link expiration */
  386.     PREF_GetIntPref(pref_link_expiration, &iExp);
  387.  
  388.     /* Convert to seconds */
  389.     global_history_timeout_interval = iExp * 60 * 60 * 24;
  390.  
  391.     if (iExp == 0)
  392.         GH_ClearGlobalHistory();
  393.     
  394.     return PREF_NOERROR;
  395. }
  396.  
  397. /* start global history tracking
  398.  */
  399. PUBLIC void
  400. GH_InitGlobalHistory(void)
  401. {
  402. #if defined(XP_WIN) || defined(XP_OS2)
  403.     int32 iExp;
  404.     
  405.     /* Get the number of days for link expiration */
  406.     PREF_GetIntPref(pref_link_expiration, &iExp);
  407.  
  408.     /* Convert to seconds */
  409.     global_history_timeout_interval = iExp * 60 * 60 * 24;
  410.  
  411.     /* Observe the preference */
  412.     PREF_RegisterCallback(pref_link_expiration, gh_link_expiration_changed, NULL);
  413. #endif
  414.     
  415.     gh_open_database();
  416. }
  417.  
  418. PRIVATE void
  419. gh_RemoveDatabase(void)
  420. {
  421.     char* filename;
  422.     if(gh_database)
  423.       {
  424.         (*gh_database->close)(gh_database);
  425.         gh_database = 0;
  426.       }
  427.     filename = WH_FileName("", xpGlobalHistory);
  428.     if (!filename) return;
  429.     XP_FileRemove(filename, xpGlobalHistory);
  430.     XP_FREE(filename);
  431. }
  432.  
  433. /* Notify all the contexts of something e.g., updates, deletions
  434. */
  435. PRIVATE void
  436. GH_NotifyContexts( int32 iNotifyMsg, char *pszKey )
  437. {
  438.    gh_HistContext *  pCsr  = pHistContextList;
  439.    gh_HistContext *  pCopy = NULL;
  440.    
  441.    int32 iCount = 0;
  442.    int32 i = 0;
  443.  
  444.    gh_NotifyMsg stNM;
  445.    XP_MEMSET( &stNM, 0, sizeof(gh_NotifyMsg) );
  446.    stNM.iNotifyMsg = iNotifyMsg;
  447.    stNM.pszKey     = pszKey;
  448.    
  449.    if( !pCsr )
  450.    {
  451.       return;
  452.    }
  453.  
  454.    /* Note we must first make a copy of the context information before we notify.
  455.       The reason: If the notifyee decides to release his context during the notification,
  456.       the list is compromised i.e., GH_ReleaseContext() changes the list.
  457.    */
  458.    
  459.    /* Count the contexts first */
  460.    do
  461.    {
  462.       iCount++;
  463.       pCsr = pCsr->pNext;
  464.       
  465.    }while( pCsr != pHistContextList );
  466.  
  467.    /* Allocate the mem for the copy */   
  468.    pCopy = (gh_HistContext *)XP_ALLOC( iCount * sizeof(gh_HistContext) );
  469.    XP_MEMSET( pCopy, 0, iCount * sizeof(gh_HistContext) );
  470.  
  471.    /* Fill in the context copy */
  472.    pCsr = pHistContextList;
  473.    do
  474.    {
  475.       pCopy[i].pUserData = pCsr->pUserData;
  476.       pCopy[i].pfNotifyProc = pCsr->pfNotifyProc;
  477.       i++;
  478.       
  479.       pCsr = pCsr->pNext;
  480.       
  481.    }while( pCsr != pHistContextList );
  482.  
  483.    /* Now notify the contexts */
  484.    for( i = 0; i < iCount; i++ )
  485.    {
  486.       if( !pCopy[i].pfNotifyProc )
  487.       {
  488.          continue;
  489.       }
  490.       
  491.       stNM.pUserData = pCopy[i].pUserData;
  492.       
  493.       pCopy[i].pfNotifyProc( &stNM );
  494.    }
  495.    
  496.    XP_FREE( pCopy );
  497. }
  498.  
  499. PRBool blockedHistItem (char* url) ;
  500. /* add or update the url in the global history
  501.  */
  502. PUBLIC void
  503. GH_UpdateGlobalHistory(URL_Struct * URL_s)
  504. {
  505.     char *url=NULL, *atSign=NULL, *passwordColon=NULL, *afterProtocol=NULL;
  506.     DBT key, data, dataComp;
  507.     int status;
  508.     static int32 count=0;
  509.     int8 *pData;
  510.     
  511.     int32 iNameLen = 0;
  512.  
  513.     int32 iDefault = 1;
  514.     int32 iCount;
  515.     
  516.     /* check for NULL's
  517.      * and also don't allow ones with post-data in here
  518.       */
  519.     if(!URL_s || !URL_s->address || URL_s->post_data)
  520.         return;
  521.  
  522.     /* Never save these in the history database */
  523.     if (!strncasecomp(URL_s->address, "about:", 6) ||
  524.         !strncasecomp(URL_s->address, "javascript:", 11) ||
  525.         !strncasecomp(URL_s->address, "livescript:", 11) ||
  526.         !strncasecomp(URL_s->address, "mailbox:", 8) ||
  527.         !strncasecomp(URL_s->address, "mailto:", 7) ||
  528.         !strncasecomp(URL_s->address, "mocha:", 6) ||
  529.         !strncasecomp(URL_s->address, "news:", 5) ||
  530.         !strncasecomp(URL_s->address, "pop3:", 5) ||
  531.         !strncasecomp(URL_s->address, "snews:", 6) ||
  532.         !strncasecomp(URL_s->address, "view-source:", 12))
  533.       return;
  534.  
  535.     gh_cur_date = time(NULL);
  536.  
  537.     /*    BM_UpdateBookmarksTime(URL_s, gh_cur_date); */
  538.  
  539.     if(global_history_timeout_interval == 0)
  540.         return;  /* don't add ever */
  541.  
  542.     gh_open_database();
  543.  
  544.     if(!gh_database)
  545.         return;
  546.  
  547.     global_history_has_changed = TRUE;
  548.     urlLookupGlobalHistHasChanged = TRUE;
  549.  
  550.     count++;  /* increment change count */
  551.  
  552.     /* Don't allow passwords through. If there's an at sign, check for a password. */
  553.     if( (atSign = XP_STRCHR(URL_s->address, '@')) != NULL )
  554.     {
  555.         *atSign = '\0';
  556.  
  557.         /* get a position beyond the protocol */
  558.         afterProtocol = XP_STRCHR(URL_s->address, ':');
  559.         if(afterProtocol
  560.             && (afterProtocol[1] == '/')
  561.             && (afterProtocol[2] == '/')) {
  562.             afterProtocol += 3;
  563.         }
  564.         else {
  565.             *atSign = '@';
  566.             return; /* url is in bad format */
  567.         }
  568.         
  569.         if( (passwordColon = XP_STRCHR(afterProtocol, ':')) != NULL)
  570.         {
  571.             /* Copy everything up to the password colon */
  572.             *passwordColon = '\0';
  573.             StrAllocCopy(url, URL_s->address);
  574.  
  575.             /* Put the stripped chars back */
  576.             *passwordColon = ':';
  577.             *atSign = '@';
  578.  
  579.             if(!url)
  580.                 return;
  581.             /* Concatenate everyting from the at sign on, skipping the password */
  582.             StrAllocCat(url, atSign);
  583.             if(!url)
  584.                 return;
  585.             key.data = (void *) url;
  586.             key.size = (XP_STRLEN(url)+1) * sizeof(char);
  587.         }
  588.         /* There was no password, just a username perhaps */
  589.         else {
  590.             *atSign = '@';
  591.             key.data = (void *) URL_s->address;
  592.             key.size = (XP_STRLEN(URL_s->address)+1) * sizeof(char);
  593.         }
  594.     }
  595.     /* No at sign, no chance of a password. Business as usual */
  596.     else {
  597.         key.data = (void *) URL_s->address;
  598.         key.size = (XP_STRLEN(URL_s->address)+1) * sizeof(char);
  599.     }
  600.  
  601. #if 0 /* Old Format */
  602.     COPY_INT32(&date, &gh_cur_date);
  603.     data.data = (void *)&date;
  604.     data.size = sizeof(int32);
  605. #else
  606.     iNameLen = (URL_s->content_name && *URL_s->content_name) ? XP_STRLEN( URL_s->content_name )+1 : 1;
  607.     data.size = sizeof(int32) + sizeof(int32) + sizeof(int32) + sizeof(int32) + iNameLen*sizeof(char);
  608.     
  609.     pData = XP_ALLOC( data.size );
  610.  
  611.     data.data = (void *)pData;
  612.     
  613.     /*
  614.     // last_accessed...
  615.     */
  616.     COPY_INT32( pData, &gh_cur_date );
  617.     
  618.     /*
  619.     // iFlags
  620.     */
  621.     XP_MEMSET( pData+3*sizeof(int32), 0, sizeof(int32) );
  622.         
  623.     /*
  624.     // pszName...
  625.     //
  626.     // Note the content_name member is rarely if ever used, so the title is always blank
  627.     */
  628.     if( iNameLen > 1  )
  629.     {
  630.        XP_STRCPY( (char *)pData+4*sizeof(int32), URL_s->content_name );
  631.     }
  632.     else
  633.     {
  634.         *(char *)(pData+4*sizeof(int32)) = 0;
  635.     }
  636.    
  637.     if( 0 == (*gh_database->get)( gh_database, &key, &dataComp, 0 ) )
  638.     {
  639.         if( dataComp.size > sizeof(int32) )
  640.         {
  641.             /* New format...
  642.             
  643.             //
  644.             // first_accessed
  645.             */
  646.             
  647.             COPY_INT32( pData+sizeof(int32), (int8 *)dataComp.data + sizeof(int32) );
  648.             
  649.             /*
  650.             // iCount
  651.             */
  652.             
  653.             COPY_INT32(&iCount, (int8 *)dataComp.data + 2*sizeof(int32));
  654.             iCount++;
  655.             COPY_INT32( pData + 2*sizeof(int32), &iCount );
  656.         }
  657.         else
  658.         {
  659.             /* Old format...
  660.             
  661.             //
  662.             // first_accessed
  663.             */
  664.             
  665.             COPY_INT32( pData+sizeof(int32), dataComp.data );
  666.  
  667.             /*
  668.             // iCount
  669.             */
  670.             
  671.             COPY_INT32( pData+2*sizeof(int32), &iDefault );
  672.         }
  673.     }
  674.     else
  675.     {
  676.         /* New record...
  677.         
  678.         //
  679.         // first_accessed
  680.         */
  681.     
  682.         COPY_INT32( pData+sizeof(int32), &gh_cur_date );
  683.  
  684.         /*
  685.         // iCount
  686.         */
  687.         COPY_INT32( pData+2*sizeof(int32), &iDefault );        
  688.     }
  689.  
  690. #endif   
  691.     status = (*gh_database->put)( gh_database, &key, &data, 0 );
  692.  
  693.  
  694.     XP_FREE( pData );
  695.     XP_FREEIF(url);
  696.     
  697.     if(status < 0)
  698.     {
  699.         TRACEMSG(("Global history update failed due to database error"));
  700.         gh_RemoveDatabase();
  701.     }
  702.     else if(count >= SYNC_RATE)
  703.     {
  704.         count = 0;
  705.         if( -1 == (*gh_database->sync)( gh_database, 0 ) )
  706.         {
  707.             TRACEMSG(("Error syncing gh database"));
  708.         }
  709.     }
  710. #if 0 /* Not a good idea right now - with a large hash this could cause some lag.
  711.          Instead, do it during GH_UpdateURLTitle() since these are what we're interested in anyway */
  712.     /*
  713.     // Notify the contexts of the update
  714.     */
  715.     GH_NotifyContexts( GH_NOTIFY_UPDATE, (char *)key.data );
  716. #endif
  717. }
  718.  
  719.  
  720. #define MAX_HIST_DBT_SIZE 1024
  721.  
  722. PRIVATE DBT *
  723. gh_HistDBTDup(DBT *obj)
  724. {
  725.     DBT * rv = XP_NEW(DBT);
  726.  
  727.     if(!rv || obj->size > MAX_HIST_DBT_SIZE)
  728.         return NULL;
  729.  
  730.     rv->size = obj->size;
  731.     rv->data = XP_ALLOC(rv->size);
  732.     if(!rv->data)
  733.       {
  734.         XP_FREE(rv);
  735.         return NULL;
  736.       }
  737.  
  738.     XP_MEMCPY(rv->data, obj->data, rv->size);
  739.  
  740.     return(rv);
  741.  
  742. }
  743.  
  744. PRIVATE void
  745. gh_FreeHistDBTdata(DBT *stuff)
  746. {
  747.     XP_FREE(stuff->data);
  748.     XP_FREE(stuff);
  749. }
  750.  
  751.  
  752. /* runs through a portion of the global history
  753.  * database and removes all objects that have expired
  754.  *
  755.  */
  756. PUBLIC void
  757. GH_CollectGarbage(void)
  758. {
  759. #define OLD_ENTRY_ARRAY_SIZE 100
  760.     DBT *old_entry_array[OLD_ENTRY_ARRAY_SIZE];
  761.  
  762.     DBT key, data;    
  763.     DBT *newkey;
  764.     time_t entry_date;
  765.     int i, old_entry_count=0;
  766.     
  767.     if(!gh_database || global_history_timeout_interval < 1)
  768.         return;
  769.  
  770.     gh_cur_date = time(NULL);
  771.  
  772.     if(0 != (*gh_database->seq)(gh_database, &key, &data, R_FIRST))
  773.         return;
  774.     
  775.     global_history_has_changed = TRUE;
  776.     urlLookupGlobalHistHasChanged = TRUE;
  777.  
  778.     do
  779.       {
  780.         COPY_INT32(&entry_date, data.data);
  781.         if(global_history_timeout_interval > 0
  782.            && entry_date+global_history_timeout_interval < gh_cur_date)
  783.             {
  784.             /* put the object on the delete list since it is expired
  785.               */
  786.             if(old_entry_count < OLD_ENTRY_ARRAY_SIZE)
  787.                 old_entry_array[old_entry_count++] = gh_HistDBTDup(&key);
  788.             else
  789.                 break;
  790.             }
  791.       }
  792.     while(0 == (gh_database->seq)(gh_database, &key, &data, R_NEXT));
  793.  
  794.     for(i=0; i < old_entry_count; i++)
  795.       {
  796.         newkey = old_entry_array[i];
  797.         if(newkey)
  798.           {
  799.                (*gh_database->del)(gh_database, newkey, 0);
  800.             
  801.             /*
  802.             // Notify the contexts of the update
  803.             */
  804.             GH_NotifyContexts( GH_NOTIFY_DELETE, (char *)newkey->data );
  805.             
  806.             gh_FreeHistDBTdata(newkey);
  807.           }
  808.       }
  809. }
  810.  
  811. /* save the global history to a file while leaving the object in memory
  812.  */
  813. PUBLIC void
  814. GH_SaveGlobalHistory(void)
  815. {
  816.  
  817.     if(!gh_database)
  818.         return;
  819.  
  820.     GH_CollectGarbage();
  821.  
  822.     if(global_history_has_changed)
  823.       {
  824.         if(-1 == (*gh_database->sync)(gh_database, 0))
  825.           {
  826.             TRACEMSG(("Error syncing gh database"));
  827.             (*gh_database->close)(gh_database);
  828.             gh_database = 0;
  829.           }
  830.         global_history_has_changed = FALSE;
  831.         urlLookupGlobalHistHasChanged = TRUE;
  832.     }
  833. }
  834.  
  835. /* free the global history list
  836.  */
  837. PUBLIC void
  838. GH_FreeGlobalHistory(void)
  839. {
  840.     if(!gh_database)
  841.         return;
  842.  
  843.     if(-1 == (*gh_database->close)(gh_database))
  844.       {
  845.          TRACEMSG(("Error closing gh database"));
  846.       }
  847.  
  848.     gh_database = 0;
  849.     
  850. }
  851.  
  852. /* clear the global history list
  853.  */
  854. PUBLIC void
  855. GH_ClearGlobalHistory(void)
  856. {
  857.     char* filename;
  858.     if(!gh_database)
  859.         return;
  860.  
  861.  
  862.     GH_FreeGlobalHistory();
  863.  
  864.     gh_set_hash_options();
  865.  
  866. #ifndef NO_DBM
  867.     filename = WH_FileName("", xpGlobalHistory);
  868.     gh_database = dbopen(filename,
  869.                          O_RDWR | O_TRUNC,
  870.                          0600,
  871.                          DB_HASH,
  872.                          &gh_hashinfo);
  873.     if (filename) XP_FREE(filename);
  874. #endif /* NO_DBM */
  875.     if(gh_database && -1 == (*gh_database->sync)(gh_database, 0))
  876.       {
  877.         TRACEMSG(("Error syncing gh database"));
  878.         (*gh_database->close)(gh_database);
  879.         gh_database = 0;
  880.       }
  881.  
  882.     global_history_has_changed = FALSE;
  883.     urlLookupGlobalHistHasChanged = TRUE;
  884. }
  885.  
  886.  
  887. /* create an HTML stream and push a bunch of HTML about
  888.  * the global history
  889.  */
  890. MODULE_PRIVATE int
  891. NET_DisplayGlobalHistoryInfoAsHTML(MWContext *context,
  892.                                    URL_Struct *URL_s,
  893.                                    int format_out)
  894. {
  895.     char *buffer = (char*)XP_ALLOC(256);
  896.        NET_StreamClass * stream;
  897.     DBT key, data;
  898.     Bool long_form = FALSE;
  899.     time_t entry_date;
  900.     int status = MK_NO_DATA;
  901.     int32 count=0;
  902.     static char LINK_START[] = "<A href=\"";
  903.     static char LINK_END[] = "\">";
  904.     static char END_LINK[] = "</A>";
  905.  
  906.     if(!buffer)
  907.       {
  908.         return(MK_UNABLE_TO_CONVERT);
  909.       }
  910.  
  911.     if(strcasestr(URL_s->address, "?long"))
  912.         long_form = TRUE;
  913.  
  914.     StrAllocCopy(URL_s->content_type, TEXT_HTML);
  915.  
  916.     format_out = CLEAR_CACHE_BIT(format_out);
  917.     stream = NET_StreamBuilder(format_out,
  918.                                URL_s,
  919.                                context);
  920.  
  921.     if(!stream)
  922.       {
  923.         return(MK_UNABLE_TO_CONVERT);
  924.       }
  925.  
  926.  
  927.     /* define a macro to push a string up the stream
  928.      * and handle errors
  929.      */
  930. #define PUT_PART(part)                                                    \
  931. status = (*stream->put_block)(stream,            \
  932.                                         part ? part : XP_GetString(XP_GLHIST_UNKNOWN),     \
  933.                                         part ? XP_STRLEN(part) : 7);    \
  934. if(status < 0)                                                \
  935.   goto END;
  936.  
  937.     XP_SPRINTF(buffer, XP_GetString(XP_GLHIST_INFO_HTML));
  938.  
  939.     PUT_PART(buffer);
  940.  
  941.     if(!gh_database)
  942.       {
  943.         XP_STRCPY(buffer, XP_GetString(XP_GLHIST_DATABASE_CLOSED));
  944.         PUT_PART(buffer);
  945.         goto END;
  946.       }
  947.  
  948.     if(0 != (*gh_database->seq)(gh_database, &key, &data, R_FIRST))
  949.       {
  950.         XP_STRCPY(buffer, XP_GetString(XP_GLHIST_DATABASE_EMPTY));
  951.         PUT_PART(buffer);
  952.         goto END;
  953.       }
  954.  
  955.     /* define some macros to help us output HTML tables
  956.      */
  957. #define TABLE_TOP(arg1)                \
  958.     XP_SPRINTF(buffer,                 \
  959. "<TR><TD ALIGN=RIGHT><b>%s</TD>\n"    \
  960. "<TD>", arg1);                        \
  961. PUT_PART(buffer);
  962.  
  963. #define TABLE_BOTTOM                \
  964.     XP_SPRINTF(buffer,                 \
  965. "</TD></TR>");                        \
  966. PUT_PART(buffer);
  967.  
  968.     do
  969.       {
  970.         count++;
  971.  
  972.         COPY_INT32(&entry_date, data.data);
  973.  
  974.         /* print url */
  975.         XP_STRCPY(buffer, "<TT> URL:</TT> ");
  976.         PUT_PART(buffer);
  977.  
  978.         /* make the URL a link */
  979.         PUT_PART(LINK_START);
  980.         if(status < 0)
  981.               goto END;
  982.  
  983.         /* push the key special since we know the size */
  984.         status = (*stream->put_block)(stream,
  985.                                       (char*)key.data, key.size);
  986.         if(status < 0)
  987.               goto END;
  988.  
  989.         PUT_PART(LINK_END);
  990.         if(status < 0)
  991.               goto END;
  992.  
  993.         /* push the key special since we know the size */
  994.         status = (*stream->put_block)(stream,
  995.                                       (char*)key.data, key.size);
  996.         if(status < 0)
  997.               goto END;
  998.  
  999.         PUT_PART(END_LINK);
  1000.         if(status < 0)
  1001.               goto END;
  1002.  
  1003.         XP_SPRINTF(buffer, XP_GetString(XP_GLHIST_HTML_DATE), ctime(&entry_date));
  1004.         PUT_PART(buffer);
  1005.  
  1006.       }
  1007.     while(0 == (*gh_database->seq)(gh_database, &key, &data, R_NEXT));
  1008.  
  1009.     
  1010.     XP_SPRINTF(buffer, XP_GetString(XP_GLHIST_HTML_TOTAL_ENTRIES), count);
  1011.     PUT_PART(buffer);
  1012.  
  1013. END:
  1014.     XP_FREE(buffer);
  1015.     
  1016.     if(status < 0)
  1017.         (*stream->abort)(stream, status);
  1018.     else
  1019.         (*stream->complete)(stream);
  1020.     XP_FREE(stream);
  1021.  
  1022.     return(status);
  1023. }
  1024.  
  1025. /*------------------------------------------------------------------------------
  1026. //
  1027. */
  1028. PRIVATE void
  1029. GH_CreateURContext( gh_HistContext *hGHContext )
  1030. {
  1031.     if( !hGHContext || hGHContext->pURContext )
  1032.     {
  1033.         return;
  1034.     }
  1035.     
  1036.     hGHContext->pURContext = XP_ALLOC( sizeof(gh_URContext) );
  1037.     
  1038.     hGHContext->pURContext->pUndoList = hGHContext->pURContext->pRedoList = NULL;
  1039. }
  1040.  
  1041. /*------------------------------------------------------------------------------
  1042. //
  1043. */
  1044. PRIVATE void
  1045. GH_ReleaseURContext( gh_URContext *pURContext )
  1046. {
  1047.     if( !pURContext )
  1048.     {
  1049.         return;
  1050.     }
  1051.     
  1052.     if( pURContext->pUndoList )
  1053.     {
  1054.        GH_DeleteURList( pURContext->pUndoList );
  1055.     }
  1056.  
  1057.     if( pURContext->pRedoList )
  1058.     {
  1059.        GH_DeleteURList( pURContext->pRedoList );
  1060.     }
  1061.     
  1062.     XP_FREE( pURContext );
  1063. }
  1064.  
  1065. /*------------------------------------------------------------------------------
  1066. // 
  1067. // QSort compare callbacks
  1068. */
  1069. static int QSortCompStr( const void *elem1, const void *elem2 )
  1070. {
  1071.     gh_HistList ** p1 = (gh_HistList **)elem1;
  1072.     gh_HistList ** p2 = (gh_HistList **)elem2;
  1073.     return XP_StrColl( (char *)(*p1)->pData, (char *)(*p2)->pData );
  1074. }
  1075. static int QSortCompStr2( const void *elem1, const void *elem2 )
  1076. {
  1077.     gh_HistList ** p1 = (gh_HistList **)elem1;
  1078.     gh_HistList ** p2 = (gh_HistList **)elem2;
  1079.     return XP_StrColl( (char *)(*p1)->pKeyData, (char *)(*p2)->pKeyData );
  1080. }
  1081.  
  1082. #ifdef SUNOS4
  1083. /* difftime() doesn't seem to exist on SunOS anywhere. -mcafee */
  1084. static double difftime(time_t time1, time_t time0)
  1085. {
  1086.   return (double) (time1 - time0);
  1087. }
  1088. #endif
  1089.  
  1090. static int QSortCompDate( const void *elem1, const void *elem2 )
  1091. {
  1092.     gh_HistList ** p1 = (gh_HistList **)elem1;
  1093.     gh_HistList ** p2 = (gh_HistList **)elem2;
  1094.     return difftime( *(time_t *)(*p2)->pData, *(time_t *)(*p1)->pData ) >= 0 ? 1 : -1;
  1095. }
  1096. static int QSortCompInt32( const void *elem1, const void *elem2 )
  1097. {
  1098.     gh_HistList ** p1 = (gh_HistList **)elem1;
  1099.     gh_HistList ** p2 = (gh_HistList **)elem2;
  1100.     return (*(int32 *)(*p1)->pData >= *(int32 *)(*p2)->pData) ? 1 : -1;
  1101. }
  1102.  
  1103. /*------------------------------------------------------------------------------
  1104. // 
  1105. // Supplies a "context", or handle, to the hash table.  The context serves as a 
  1106. // quasi-cursor to the table, offering the ability to navigate and enumerate the 
  1107. // records sorting on a specified column/field.
  1108. */
  1109. #define SORT_ARRAY_GROW_SIZE 1024
  1110. PUBLIC GHHANDLE
  1111. GH_GetContext( enum                 gh_SortColumn enGHSort, 
  1112.                gh_Filter *          pFilter, 
  1113.                GHISTORY_NOTIFYPROC  pfNotifyProc,
  1114.                GHURHANDLE           hUR,
  1115.                void *               pUserData )
  1116. {
  1117.     DBT key, data;
  1118.     void XP_HUGE *pBase = NULL;
  1119. #ifdef XP_WIN16
  1120.     void XP_HUGE *pBuf = NULL;    
  1121. #endif
  1122.     int16 csid = INTL_DefaultWinCharSetID( 0 );
  1123.     
  1124.     gh_HistList *pNode;
  1125.     gh_HistContext *hGHContext = XP_ALLOC( sizeof(gh_HistContext) );
  1126.     XP_MEMSET( hGHContext, 0, sizeof(gh_HistContext) );
  1127.     
  1128.     if( !gh_database || global_history_timeout_interval < 1 )
  1129.     {
  1130.         return NULL;
  1131.     }
  1132.  
  1133.     hGHContext->uNumRecords    = 0;
  1134.     hGHContext->enGHSort       = enGHSort;
  1135.     hGHContext->pFilter        = pFilter;
  1136.     hGHContext->pfNotifyProc   = pfNotifyProc;
  1137.     hGHContext->pUserData      = pUserData;
  1138.     hGHContext->pURContext     = (gh_URContext *)hUR;
  1139.     
  1140.     /* Append the context to the list */
  1141.     if( !pHistContextList )
  1142.     {
  1143.        pHistContextList = hGHContext;
  1144.        pHistContextList->pNext = pHistContextList->pPrev = pHistContextList;
  1145.     }
  1146.     else
  1147.     {
  1148.        hGHContext->pPrev = pHistContextList->pPrev;
  1149.        hGHContext->pNext = pHistContextList;
  1150.        pHistContextList->pPrev->pNext = hGHContext;
  1151.        pHistContextList->pPrev = hGHContext;
  1152.     }
  1153.     
  1154.     data.size = key.size = 0;
  1155.     
  1156.     /*
  1157.     // Build the array.  We don't know the number of entries in the hash, so we
  1158.     // have to grow the array as we read the entries.  Gross.
  1159.     */
  1160.     if( 0 != (*gh_database->seq)( gh_database, &key, &data, R_FIRST ) )
  1161.     {
  1162.         return hGHContext;
  1163.     }
  1164.     
  1165.     pBase = (void XP_HUGE *)XP_HUGE_ALLOC( SORT_ARRAY_GROW_SIZE*sizeof(gh_HistList *) );
  1166.     do
  1167.     {
  1168.         if( data.size > sizeof(int32) )
  1169.         {
  1170.             /*
  1171.             // The entry/record is of the new format...        
  1172.             //
  1173.             
  1174.             // Ignore history records which are NOT flagged as having been explicitly loaded
  1175.             // e.g., don't expose gif images that are only part of a page
  1176.             */
  1177.             /*int32 iFlags = *(int32 *)((int8 *)data.data + 3*sizeof(int32));*/
  1178.             int32 iFlags;
  1179.             COPY_INT32( &iFlags, (int8 *)data.data + 3*sizeof(int32) );
  1180.  
  1181.             if( !(iFlags & GH_FLAGS_SHOW) )
  1182.             {
  1183.                continue;
  1184.             }
  1185.             
  1186.             /*
  1187.             // Filter records according to the supplied filter struct.
  1188.             */
  1189.             
  1190.             if( pFilter )
  1191.             {
  1192.                 Bool bKeep = FALSE;
  1193.                 int i;
  1194.                 
  1195.                 for( i = 0; i < pFilter->iNumConditions; i++ )
  1196.                 {
  1197.                    if( i > 0 )
  1198.                    {
  1199.                       if( pFilter->enOps[i-1] == eGH_FLOAnd )
  1200.                       {
  1201.                          if( !bKeep )
  1202.                          {
  1203.                             /* No need to evaluate logical-and expression if already false */
  1204.                             continue;
  1205.                          }
  1206.                       }
  1207.                       else /* eGH_FLOOr */
  1208.                       {
  1209.                          if( bKeep )
  1210.                          {
  1211.                             /* No need to evaluate logical-or expression if already true */
  1212.                             continue;
  1213.                          }
  1214.                       }
  1215.                    }
  1216.                    
  1217.                    /* Guilty until proven innocent */
  1218.                    bKeep = FALSE;
  1219.                    
  1220.                    switch( pFilter->pConditions[i].enCol )
  1221.                    {
  1222.                       case eGH_LocationSort:
  1223.                       {
  1224.                          int iRes = XP_StrColl( pFilter->pConditions[i].tests.pszTest, key.data );
  1225.                       
  1226.                          switch( pFilter->pConditions[i].enOp )
  1227.                          {
  1228.                              case eGH_FOEquals:
  1229.                              {
  1230.                                 if( !iRes )
  1231.                                 {
  1232.                                    bKeep = TRUE;
  1233.                                 }
  1234.                                 break;                                
  1235.                              }
  1236.                              case eGH_FOEqualsNot:
  1237.                              {
  1238.                                 if( iRes )
  1239.                                 {
  1240.                                    bKeep = TRUE;
  1241.                                 }
  1242.                                 break;                                
  1243.                              }
  1244.                              case eGH_FOGreater:
  1245.                              {
  1246.                                 if( iRes > 0 )
  1247.                                 {
  1248.                                    bKeep = TRUE;
  1249.                                 }
  1250.                                 break;                                
  1251.                              }
  1252.                              case eGH_FOGreaterEqual:
  1253.                              {
  1254.                                 if( iRes >= 0 )
  1255.                                 {
  1256.                                    bKeep = TRUE;
  1257.                                 }
  1258.                                 break;                                
  1259.                              }
  1260.                              case eGH_FOLess:
  1261.                              {
  1262.                                 if( iRes < 0 )
  1263.                                 {
  1264.                                    bKeep = TRUE;
  1265.                                 }
  1266.                                 break;                                
  1267.                              }
  1268.                              case eGH_FOLessEqual:
  1269.                              {
  1270.                                 if( iRes <= 0 )
  1271.                                 {
  1272.                                    bKeep = TRUE;
  1273.                                 }
  1274.                                 break;                                
  1275.                              }
  1276.                              case eGH_FOHas:
  1277.                              {
  1278.                                 bKeep = INTL_Strcasestr( csid, key.data, pFilter->pConditions[i].tests.pszTest ) != NULL;
  1279.                                 break;
  1280.                              }
  1281.                              case eGH_FOHasNot:
  1282.                              {
  1283.                                 bKeep = INTL_Strcasestr( csid, key.data, pFilter->pConditions[i].tests.pszTest ) == NULL;
  1284.                                 break;
  1285.                              }
  1286.                              default:
  1287.                                 bKeep = FALSE;
  1288.                          }
  1289.                          break;
  1290.                       }
  1291.                       
  1292.                       case eGH_NameSort:
  1293.                       {
  1294.                          char *pText = (char *)((int8 *)data.data + 4*sizeof(int32));
  1295.                          
  1296.                          int iRes = XP_StrColl( pFilter->pConditions[i].tests.pszTest, pText );
  1297.                       
  1298.                          switch( pFilter->pConditions[i].enOp )
  1299.                          {
  1300.                              case eGH_FOEquals:
  1301.                              {
  1302.                                 if( !iRes )
  1303.                                 {
  1304.                                    bKeep = TRUE;
  1305.                                 }
  1306.                                 break;                                
  1307.                              }
  1308.                              case eGH_FOEqualsNot:
  1309.                              {
  1310.                                 if( iRes )
  1311.                                 {
  1312.                                    bKeep = TRUE;
  1313.                                 }
  1314.                                 break;                                
  1315.                              }
  1316.                              case eGH_FOGreater:
  1317.                              {
  1318.                                 if( iRes > 0 )
  1319.                                 {
  1320.                                    bKeep = TRUE;
  1321.                                 }
  1322.                                 break;                                
  1323.                              }
  1324.                              case eGH_FOGreaterEqual:
  1325.                              {
  1326.                                 if( iRes >= 0 )
  1327.                                 {
  1328.                                    bKeep = TRUE;
  1329.                                 }
  1330.                                 break;                                
  1331.                              }
  1332.                              case eGH_FOLess:
  1333.                              {
  1334.                                 if( iRes < 0 )
  1335.                                 {
  1336.                                    bKeep = TRUE;
  1337.                                 }
  1338.                                 break;                                
  1339.                              }
  1340.                              case eGH_FOLessEqual:
  1341.                              {
  1342.                                 if( iRes <= 0 )
  1343.                                 {
  1344.                                    bKeep = TRUE;
  1345.                                 }
  1346.                                 break;                                
  1347.                              }
  1348.                              case eGH_FOHas:
  1349.                              {
  1350.                                 bKeep = INTL_Strcasestr( csid, pText, pFilter->pConditions[i].tests.pszTest ) != NULL;
  1351.                                 break;
  1352.                              }
  1353.                              case eGH_FOHasNot:
  1354.                              {
  1355.                                 bKeep = INTL_Strcasestr( csid, pText, pFilter->pConditions[i].tests.pszTest ) == NULL;
  1356.                                 break;
  1357.                              }
  1358.                              default:
  1359.                                 bKeep = FALSE;
  1360.                          }
  1361.                          break;
  1362.                       }
  1363.  
  1364.                       case eGH_VisitCountSort:           
  1365.                       {
  1366.                          int32 iCount; 
  1367.                          COPY_INT32( &iCount, (int8 *)data.data + 2*sizeof(int32) );                         
  1368.                       
  1369.                          switch( pFilter->pConditions[i].enOp )
  1370.                          {
  1371.                              case eGH_FOEquals:
  1372.                              {
  1373.                                 if( iCount == pFilter->pConditions[i].tests.iTest )
  1374.                                 {
  1375.                                    bKeep = TRUE;
  1376.                                 }
  1377.                                 break;                                
  1378.                              }
  1379.                              case eGH_FOEqualsNot:
  1380.                              {
  1381.                                 if( iCount != pFilter->pConditions[i].tests.iTest )
  1382.                                 {
  1383.                                    bKeep = TRUE;
  1384.                                 }
  1385.                                 break;                                
  1386.                              }
  1387.                              case eGH_FOGreater:
  1388.                              {
  1389.                                 if( iCount > pFilter->pConditions[i].tests.iTest )
  1390.                                 {
  1391.                                    bKeep = TRUE;
  1392.                                 }
  1393.                                 break;                                
  1394.                              }
  1395.                              case eGH_FOGreaterEqual:
  1396.                              {
  1397.                                 if( iCount >= pFilter->pConditions[i].tests.iTest )
  1398.                                 {
  1399.                                    bKeep = TRUE;
  1400.                                 }
  1401.                                 break;                                
  1402.                              }
  1403.                              case eGH_FOLess:
  1404.                              {
  1405.                                 if( iCount < pFilter->pConditions[i].tests.iTest )
  1406.                                 {
  1407.                                    bKeep = TRUE;
  1408.                                 }
  1409.                                 break;                                
  1410.                              }
  1411.                              case eGH_FOLessEqual:
  1412.                              {
  1413.                                 if( iCount <= pFilter->pConditions[i].tests.iTest )
  1414.                                 {
  1415.                                    bKeep = TRUE;
  1416.                                 }
  1417.                                 break;                                
  1418.                              }
  1419.                              default:
  1420.                                 bKeep = FALSE;
  1421.                          }
  1422.                          break;
  1423.                       }
  1424.  
  1425.                       case eGH_FirstDateSort:           
  1426.                       case eGH_LastDateSort:                                 
  1427.                       {
  1428.                          /*
  1429.                          // Assuming we are comparing ONLY the date, set the time to 00:00:00 for both time values
  1430.                          */
  1431.                          
  1432.                          time_t time0, time1 = pFilter->pConditions[i].tests.iTest;
  1433.                          struct tm *pTM;
  1434.                          struct tm tm0;
  1435.                          struct tm tm1;                         
  1436.                                                
  1437.                          if( pFilter->pConditions[i].enCol == eGH_FirstDateSort )
  1438.                          {
  1439.                             COPY_INT32( &time0, (int8 *)data.data + sizeof(int32) );                         
  1440.                          }
  1441.                          else
  1442.                          {
  1443.                             COPY_INT32( &time0, (int8 *)data.data );                         
  1444.                          }
  1445.                          
  1446.                          pTM = localtime( &time0 );
  1447.                          XP_MEMSET( &tm0, 0, sizeof(struct tm) );
  1448.                          tm0.tm_mday = pTM->tm_mday;
  1449.                          tm0.tm_mon  = pTM->tm_mon;
  1450.                          tm0.tm_year = pTM->tm_year;
  1451.                          pTM = localtime( &time1 );
  1452.                          XP_MEMSET( &tm1, 0, sizeof(struct tm) );
  1453.                          tm1.tm_mday = pTM->tm_mday;
  1454.                          tm1.tm_mon  = pTM->tm_mon;
  1455.                          tm1.tm_year = pTM->tm_year;
  1456.                          
  1457.                          time0 = mktime( &tm0 );
  1458.                          time1 = mktime( &tm1 );
  1459.                          
  1460.                          switch( pFilter->pConditions[i].enOp )
  1461.                          {
  1462.                              case eGH_FOEquals:
  1463.                              {
  1464.                                 if( time0 == time1 )
  1465.                                 {
  1466.                                    bKeep = TRUE;
  1467.                                 }
  1468.                                 break;                                
  1469.                              }
  1470.                              case eGH_FOEqualsNot:
  1471.                              {
  1472.                                 if( time0 != time1 )
  1473.                                 {
  1474.                                    bKeep = TRUE;
  1475.                                 }
  1476.                                 break;                                
  1477.                              }
  1478.                              case eGH_FOGreater:
  1479.                              {
  1480.                                 if( time0 > time1 )
  1481.                                 {
  1482.                                    bKeep = TRUE;
  1483.                                 }
  1484.                                 break;                                
  1485.                              }
  1486.                              case eGH_FOGreaterEqual:
  1487.                              {
  1488.                                 if( time0 >= time1 )
  1489.                                 {
  1490.                                    bKeep = TRUE;
  1491.                                 }
  1492.                                 break;                                
  1493.                              }
  1494.                              case eGH_FOLess:
  1495.                              {
  1496.                                 if( time0 < time1 )
  1497.                                 {
  1498.                                    bKeep = TRUE;
  1499.                                 }
  1500.                                 break;                                
  1501.                              }
  1502.                              case eGH_FOLessEqual:
  1503.                              {
  1504.                                 if( time0 <= time1 )
  1505.                                 {
  1506.                                    bKeep = TRUE;
  1507.                                 }
  1508.                                 break;                                
  1509.                              }
  1510.                              default:
  1511.                                 bKeep = FALSE;
  1512.                          }
  1513.                          break;
  1514.                       }
  1515.                      default:
  1516.                        break;
  1517.                    }
  1518.                 }
  1519.                 
  1520.                 if( !bKeep )
  1521.                 {
  1522.                    /*
  1523.                    // Did NOT pass filter.  Test next record.
  1524.                    */
  1525.                    continue;
  1526.                 }
  1527.             }
  1528.         }
  1529.         else
  1530.         {   
  1531.             /*
  1532.             // The entry/record is of the old format...
  1533.             //
  1534.             
  1535.             // Try to be somewhat smart and filter the implicitly loaded stuff i.e., non-html docs
  1536.             */
  1537.             char *pszExt = strrchr( key.data, '.' );
  1538.             
  1539.             if( ((char *)key.data)[XP_STRLEN(key.data)-1] == '/' )
  1540.             {
  1541.             }
  1542.             else if( XP_STRLEN(key.data) < 5 )
  1543.             {
  1544.                continue;
  1545.             }
  1546.             else if( pszExt )
  1547.             {
  1548.                pszExt++;
  1549.                if( strncasecomp( pszExt, "htm", 3 ) )
  1550.                {
  1551.                   continue;
  1552.                }
  1553.             }
  1554.             else
  1555.             {
  1556.                continue;
  1557.             }
  1558.  
  1559.             /*
  1560.             // Filter records according to the supplied filter struct
  1561.             */
  1562.  
  1563.             if( pFilter )
  1564.             {
  1565.                 Bool bKeep = FALSE;
  1566.                 int i;
  1567.                 
  1568.                 for( i = 0; i < pFilter->iNumConditions; i++ )
  1569.                 {
  1570.                 
  1571.                    if( i > 0 )
  1572.                    {
  1573.                       if( pFilter->enOps[i-1] == eGH_FLOAnd )
  1574.                       {
  1575.                          if( !bKeep )
  1576.                          {
  1577.                             /* No need to evaluate logical-and expression if already false */
  1578.                             continue;
  1579.                          }
  1580.                       }
  1581.                       else /* eGH_FLOOr */
  1582.                       {
  1583.                          if( bKeep )
  1584.                          {
  1585.                             /* No need to evaluate logical-or expression if already true */
  1586.                             continue;
  1587.                          }
  1588.                       }
  1589.                    }
  1590.                    
  1591.                    /* Guilty until proven innocent */
  1592.                    bKeep = FALSE;
  1593.                 
  1594.                    switch( pFilter->pConditions[i].enCol )
  1595.                    {
  1596.                       case eGH_LocationSort:
  1597.                       {
  1598.                          int iRes = XP_StrColl( pFilter->pConditions[i].tests.pszTest, key.data );
  1599.                       
  1600.                          switch( pFilter->pConditions[i].enOp )
  1601.                          {
  1602.                              case eGH_FOEquals:
  1603.                              {
  1604.                                 if( !iRes )
  1605.                                 {
  1606.                                    bKeep = TRUE;
  1607.                                 }
  1608.                                 break;                                
  1609.                              }
  1610.                              case eGH_FOEqualsNot:
  1611.                              {
  1612.                                 if( iRes )
  1613.                                 {
  1614.                                    bKeep = TRUE;
  1615.                                 }
  1616.                                 break;                                
  1617.                              }
  1618.                              case eGH_FOGreater:
  1619.                              {
  1620.                                 if( iRes > 0 )
  1621.                                 {
  1622.                                    bKeep = TRUE;
  1623.                                 }
  1624.                                 break;                                
  1625.                              }
  1626.                              case eGH_FOGreaterEqual:
  1627.                              {
  1628.                                 if( iRes >= 0 )
  1629.                                 {
  1630.                                    bKeep = TRUE;
  1631.                                 }
  1632.                                 break;                                
  1633.                              }
  1634.                              case eGH_FOLess:
  1635.                              {
  1636.                                 if( iRes < 0 )
  1637.                                 {
  1638.                                    bKeep = TRUE;
  1639.                                 }
  1640.                                 break;                                
  1641.                              }
  1642.                              case eGH_FOLessEqual:
  1643.                              {
  1644.                                 if( iRes <= 0 )
  1645.                                 {
  1646.                                    bKeep = TRUE;
  1647.                                 }
  1648.                                 break;                                
  1649.                              }
  1650.                              case eGH_FOHas:
  1651.                              {
  1652.                                 bKeep = INTL_Strcasestr( csid, key.data, pFilter->pConditions[i].tests.pszTest ) != NULL;
  1653.                                 break;
  1654.                              }
  1655.                              case eGH_FOHasNot:
  1656.                              {
  1657.                                 bKeep = INTL_Strcasestr( csid, key.data, pFilter->pConditions[i].tests.pszTest ) == NULL;
  1658.                                 break;
  1659.                              }
  1660.                              default:
  1661.                                 bKeep = FALSE;
  1662.                          }
  1663.                          break;
  1664.                       }
  1665.                       
  1666.                       case eGH_NameSort:
  1667.                       {
  1668.                          /*
  1669.                          // Since there is no title available from the old format, let's try and pull
  1670.                          // a meaningful title from the URL.
  1671.                          */
  1672.                       
  1673.                          char *pszTitle = GH_GetTitleFromURL( key.data );
  1674.                          int iRes = XP_StrColl( pFilter->pConditions[i].tests.pszTest, pszTitle );
  1675.                       
  1676.                          switch( pFilter->pConditions[i].enOp )
  1677.                          {
  1678.                              case eGH_FOEquals:
  1679.                              {
  1680.                                 if( !iRes )
  1681.                                 {
  1682.                                    bKeep = TRUE;
  1683.                                 }
  1684.                                 break;                                
  1685.                              }
  1686.                              case eGH_FOEqualsNot:
  1687.                              {
  1688.                                 if( iRes )
  1689.                                 {
  1690.                                    bKeep = TRUE;
  1691.                                 }
  1692.                                 break;                                
  1693.                              }
  1694.                              case eGH_FOGreater:
  1695.                              {
  1696.                                 if( iRes > 0 )
  1697.                                 {
  1698.                                    bKeep = TRUE;
  1699.                                 }
  1700.                                 break;                                
  1701.                              }
  1702.                              case eGH_FOGreaterEqual:
  1703.                              {
  1704.                                 if( iRes >= 0 )
  1705.                                 {
  1706.                                    bKeep = TRUE;
  1707.                                 }
  1708.                                 break;                                
  1709.                              }
  1710.                              case eGH_FOLess:
  1711.                              {
  1712.                                 if( iRes < 0 )
  1713.                                 {
  1714.                                    bKeep = TRUE;
  1715.                                 }
  1716.                                 break;                                
  1717.                              }
  1718.                              case eGH_FOLessEqual:
  1719.                              {
  1720.                                 if( iRes <= 0 )
  1721.                                 {
  1722.                                    bKeep = TRUE;
  1723.                                 }
  1724.                                 break;                                
  1725.                              }
  1726.                              case eGH_FOHas:
  1727.                              {
  1728.                                 bKeep = INTL_Strcasestr( csid, pszTitle, pFilter->pConditions[i].tests.pszTest ) != NULL;
  1729.                                 break;
  1730.                              }
  1731.                              case eGH_FOHasNot:
  1732.                              {
  1733.                                 bKeep = INTL_Strcasestr( csid, pszTitle, pFilter->pConditions[i].tests.pszTest ) == NULL;
  1734.                                 break;
  1735.                              }
  1736.                              default:
  1737.                                 bKeep = FALSE;
  1738.                          }
  1739.                          
  1740.                          break;
  1741.                       }
  1742.                       
  1743.                       case eGH_FirstDateSort:           
  1744.                       case eGH_LastDateSort:                                 
  1745.                       {
  1746.                          time_t time;
  1747.                                                
  1748.                          COPY_INT32( &time, (int8 *)data.data );                         
  1749.                          
  1750.                          switch( pFilter->pConditions[i].enOp )
  1751.                          {
  1752.                              case eGH_FOEquals:
  1753.                              {
  1754.                                 if( time == pFilter->pConditions[i].tests.iTest )
  1755.                                 {
  1756.                                    bKeep = TRUE;
  1757.                                 }
  1758.                                 break;                                
  1759.                              }
  1760.                              case eGH_FOEqualsNot:
  1761.                              {
  1762.                                 if( time != pFilter->pConditions[i].tests.iTest )
  1763.                                 {
  1764.                                    bKeep = TRUE;
  1765.                                 }
  1766.                                 break;                                
  1767.                              }
  1768.                              case eGH_FOGreater:
  1769.                              {
  1770.                                 if( time > pFilter->pConditions[i].tests.iTest )
  1771.                                 {
  1772.                                    bKeep = TRUE;
  1773.                                 }
  1774.                                 break;                                
  1775.                              }
  1776.                              case eGH_FOGreaterEqual:
  1777.                              {
  1778.                                 if( time >= pFilter->pConditions[i].tests.iTest )
  1779.                                 {
  1780.                                    bKeep = TRUE;
  1781.                                 }
  1782.                                 break;                                
  1783.                              }
  1784.                              case eGH_FOLess:
  1785.                              {
  1786.                                 if( time < pFilter->pConditions[i].tests.iTest )
  1787.                                 {
  1788.                                    bKeep = TRUE;
  1789.                                 }
  1790.                                 break;                                
  1791.                              }
  1792.                              case eGH_FOLessEqual:
  1793.                              {
  1794.                                 if( time <= pFilter->pConditions[i].tests.iTest )
  1795.                                 {
  1796.                                    bKeep = TRUE;
  1797.                                 }
  1798.                                 break;                                
  1799.                              }
  1800.                              default:
  1801.                                 bKeep = FALSE;
  1802.                          }
  1803.                          break;
  1804.                       }
  1805.                      default:
  1806.                        break;
  1807.                    }
  1808.                 }
  1809.                 
  1810.                 if( !bKeep )
  1811.                 {
  1812.                    /* Did NOT pass filter.  Test next record. */
  1813.                    continue;
  1814.                 }
  1815.             }                        
  1816.         }
  1817.         
  1818.         pNode = XP_ALLOC( sizeof(gh_HistList) );
  1819.         pNode->pKeyData = XP_ALLOC( (XP_STRLEN(key.data)+1) * sizeof(char) );
  1820.         XP_STRCPY( pNode->pKeyData, key.data );
  1821.         
  1822.         switch( enGHSort )
  1823.         {
  1824.            case eGH_LocationSort:
  1825.            {
  1826.               pNode->pData = pNode->pKeyData;
  1827.               break;
  1828.            }
  1829.            
  1830.            case eGH_NameSort:
  1831.            {
  1832.               if( data.size > sizeof(int32) )
  1833.               {
  1834.                  /* The entry/record is of the new format i.e., record has title data */
  1835.  
  1836.                  pNode->pData = XP_ALLOC( (XP_STRLEN((char *)data.data + 4*sizeof(int32))+1) * sizeof(char) );
  1837.                  XP_STRCPY( pNode->pData, (char *)data.data + 4*sizeof(int32) );
  1838.               }
  1839.               else
  1840.               {
  1841.                 #if 0
  1842.                  /*
  1843.                  // The entry/record is of the old format i.e., no title data available
  1844.                  // Note this is only for internal sorting to keep unititled records at bottom of A->Z sort
  1845.                  */
  1846.                  pNode->pData = XP_ALLOC( (XP_STRLEN("~~")+1) * sizeof(char) );
  1847.                  XP_STRCPY( pNode->pData, "~~" );                 
  1848.                 #else
  1849.                  /*
  1850.                  // Since there is no title available from the old format, let's try and pull
  1851.                  // a meaningful title from the URL.
  1852.                  */
  1853.                  char *pszTitle = GH_GetTitleFromURL( key.data );
  1854.                  pNode->pData = XP_ALLOC( (XP_STRLEN( pszTitle )+1) * sizeof(char) );
  1855.                  XP_STRCPY( pNode->pData, pszTitle );
  1856.                  pszTitle = strrchr( pNode->pData, '/' );
  1857.                  if( pszTitle )
  1858.                  {
  1859.                    /* Remove the trailing slash from the title. */
  1860.                    *pszTitle = 0;
  1861.                  }
  1862.                 #endif
  1863.               }
  1864.               break;
  1865.            }
  1866.  
  1867.            case eGH_VisitCountSort:           
  1868.            {
  1869.               if( data.size > sizeof(int32) )
  1870.               {
  1871.                  /* The entry/record is of the new format i.e., record has a visit count */
  1872.  
  1873.                  pNode->pData = XP_ALLOC( sizeof(int32) );
  1874.                  COPY_INT32( pNode->pData, (int8 *)data.data + 2*sizeof(int32) );
  1875.               }
  1876.               else
  1877.               {
  1878.                  /* The entry/record is of the old format i.e., no visit count available */
  1879.                  
  1880.                  pNode->pData = XP_ALLOC( sizeof(int32) );
  1881.                  *(int32 *)pNode->pData = 1;
  1882.               }
  1883.               break;
  1884.            }
  1885.  
  1886.            case eGH_FirstDateSort:           
  1887.            {
  1888.               if( data.size > sizeof(int32) )
  1889.               {
  1890.                  /* The entry/record is of the new format */
  1891.  
  1892.                  time_t date;
  1893.                  COPY_INT32( &date, data.data );
  1894.                  
  1895.                  pNode->pData = XP_ALLOC( sizeof(int32) );
  1896.                  
  1897.                  COPY_INT32( pNode->pData, (int8 *)data.data + sizeof(int32) );
  1898.                  
  1899.                  break;
  1900.               }
  1901.               
  1902.               /* Fall through if old format (i.e., sort by last_accessed) */
  1903.            }
  1904.            
  1905.            case eGH_LastDateSort:           
  1906.            default:           
  1907.            {
  1908.               /* Note the last_accessed field is at the beginning of the data regardless of new/old format */
  1909.               
  1910.               time_t date;
  1911.               COPY_INT32( &date, data.data );
  1912.               
  1913.               pNode->pData = XP_ALLOC( sizeof(int32) );
  1914.               
  1915.               COPY_INT32( pNode->pData, (int8 *)data.data );
  1916.               
  1917.               break;
  1918.            }
  1919.         }
  1920.         
  1921.         ((gh_HistList XP_HUGE **)pBase)[hGHContext->uNumRecords] = pNode;
  1922.         
  1923.         hGHContext->uNumRecords++;
  1924.         
  1925.         if( !(hGHContext->uNumRecords % SORT_ARRAY_GROW_SIZE) )
  1926.         {
  1927.            #ifndef XP_WIN16
  1928.             pBase = (void *)XP_REALLOC( pBase, (hGHContext->uNumRecords + SORT_ARRAY_GROW_SIZE)*sizeof(gh_HistList *) );
  1929.            #else
  1930.             pBuf = (void *)XP_HUGE_ALLOC( (hGHContext->uNumRecords + SORT_ARRAY_GROW_SIZE)*sizeof(gh_HistList *) );
  1931.             XP_HUGE_MEMCPY( pBuf, pBase, hGHContext->uNumRecords * sizeof(gh_HistList *) );
  1932.             XP_HUGE_FREE( pBase );
  1933.             pBase = pBuf;
  1934.            #endif /* XP_WIN16 */
  1935.         }
  1936.     }while( 0 == (gh_database->seq)(gh_database, &key, &data, R_NEXT) );
  1937.  
  1938.     /*
  1939.     // Perform a quick sort on the array, sorting by the pData member.
  1940.     */
  1941.     
  1942.     switch( enGHSort )
  1943.     {
  1944.        case eGH_NameSort:
  1945.        case eGH_LocationSort:           
  1946.        {
  1947.           int32 i;
  1948.           gh_HistList XP_HUGE **pList;
  1949.           
  1950.           XP_QSORT( pBase, hGHContext->uNumRecords, sizeof(void *), QSortCompStr );
  1951.           
  1952.           if( enGHSort == eGH_LocationSort )
  1953.           {
  1954.              break;
  1955.           }
  1956.  
  1957.           /*
  1958.           *  Now perform a secondary sort on Location
  1959.           */
  1960.           pList = (gh_HistList XP_HUGE **)pBase;
  1961.           
  1962.           for( i = 1; i < (int32)hGHContext->uNumRecords; i++ )
  1963.           {
  1964.              int32 iStartPos = i-1;
  1965.              int32 iBlockLen;             
  1966.              while( (i < (int32)hGHContext->uNumRecords) &&
  1967.                     !XP_StrColl( pList[i]->pData, pList[i-1]->pData ) )
  1968.              {
  1969.                 i++;
  1970.              }
  1971.              iBlockLen = i - iStartPos;
  1972.              if( iBlockLen > 1 )
  1973.              {
  1974.                 XP_QSORT( pList+iStartPos, iBlockLen, sizeof(void *), QSortCompStr2 );
  1975.              }
  1976.           }
  1977.           
  1978.           break;
  1979.        }
  1980.           
  1981.        case eGH_FirstDateSort:
  1982.        case eGH_LastDateSort:
  1983.        {
  1984.           int32 i;       
  1985.           gh_HistList XP_HUGE **pList;
  1986.                  
  1987.           XP_QSORT( pBase, hGHContext->uNumRecords, sizeof(void *), QSortCompDate );    
  1988.  
  1989.           /*
  1990.           *  Now perform a secondary sort on Location
  1991.           */
  1992.           pList = (gh_HistList XP_HUGE **)pBase;
  1993.           
  1994.           for( i = 1; i < (int32)hGHContext->uNumRecords; i++ )
  1995.           {
  1996.              int32 iStartPos = i-1;
  1997.              int32 iBlockLen;             
  1998.              while( (i < (int32)hGHContext->uNumRecords) &&
  1999.                     *(time_t *)pList[i]->pData == *(time_t *)pList[i-1]->pData )
  2000.              {
  2001.                 i++;
  2002.              }
  2003.              iBlockLen = i - iStartPos;
  2004.              if( iBlockLen > 1 )
  2005.              {
  2006.                 XP_QSORT( pList+iStartPos, iBlockLen, sizeof(void *), QSortCompStr2 );
  2007.              }
  2008.           }
  2009.           
  2010.           break;
  2011.        }
  2012.        
  2013.        case eGH_VisitCountSort:
  2014.        {
  2015.           int32 i;       
  2016.           gh_HistList XP_HUGE **pList;
  2017.                  
  2018.           XP_QSORT( pBase, hGHContext->uNumRecords, sizeof(void *), QSortCompInt32 );    
  2019.  
  2020.           /*
  2021.           *  Now perform a secondary sort on Location
  2022.           */
  2023.           pList = (gh_HistList XP_HUGE **)pBase;
  2024.           
  2025.           for( i = 1; i < (int32)hGHContext->uNumRecords; i++ )
  2026.           {
  2027.              int32 iStartPos = i-1;
  2028.              int32 iBlockLen;             
  2029.              
  2030.              while( (i < (int32)hGHContext->uNumRecords) &&
  2031.                     *(int32 *)pList[i]->pData == *(int32 *)pList[i-1]->pData )
  2032.              {
  2033.                 i++;
  2034.              }
  2035.              iBlockLen = i - iStartPos;
  2036.              if( iBlockLen > 1 )
  2037.              {
  2038.                 XP_QSORT( pList+iStartPos, iBlockLen, sizeof(void *), QSortCompStr2 );
  2039.              }
  2040.           }
  2041.           
  2042.           break;
  2043.        }
  2044.       default:
  2045.         break;
  2046.     }
  2047.         
  2048.     hGHContext->pHistSort = (gh_HistList XP_HUGE **)pBase;
  2049.     
  2050.     return hGHContext;
  2051. }
  2052.  
  2053. PUBLIC void
  2054. GH_ReleaseContext( GHHANDLE pContext, Bool bReleaseUR )
  2055. {
  2056.    gh_HistContext *hGHContext = (gh_HistContext *)pContext;
  2057.    uint32 uRow;
  2058.    
  2059.    if( !pContext )
  2060.    {
  2061.       return;
  2062.    }
  2063.    
  2064.    XP_ASSERT( pHistContextList );
  2065.    
  2066.    if( pHistContextList == hGHContext )
  2067.    {
  2068.       if( hGHContext->pNext == hGHContext )
  2069.       {
  2070.          pHistContextList = NULL;
  2071.       }
  2072.       else
  2073.       {
  2074.          pHistContextList = hGHContext->pNext;
  2075.       }
  2076.    }
  2077.    hGHContext->pPrev->pNext = hGHContext->pNext;
  2078.    hGHContext->pNext->pPrev = hGHContext->pPrev;
  2079.    
  2080.    /*
  2081.    // Release all the memory associated with the History Context
  2082.    */
  2083.    
  2084.    for( uRow = 0; uRow < hGHContext->uNumRecords; uRow++ )
  2085.    {
  2086.       XP_FREE( hGHContext->pHistSort[uRow]->pKeyData );
  2087.       
  2088.       if( hGHContext->enGHSort != eGH_LocationSort )
  2089.       {
  2090.          /* The pData member points to the same storage as pKeyData for Location sort */
  2091.          
  2092.          XP_FREE( hGHContext->pHistSort[uRow]->pData );      
  2093.       }
  2094.       
  2095.       XP_FREE( hGHContext->pHistSort[uRow] );
  2096.    }
  2097.    
  2098.    XP_HUGE_FREE( hGHContext->pHistSort );
  2099.    
  2100.    if( bReleaseUR )
  2101.    {
  2102.       GH_ReleaseURContext( hGHContext->pURContext );
  2103.    }
  2104. }
  2105.  
  2106. PUBLIC uint32 
  2107. GH_GetNumRecords( GHHANDLE pContext )
  2108. {
  2109.    gh_HistContext *hGHContext = (gh_HistContext *)pContext;
  2110.    
  2111.    return hGHContext ? hGHContext->uNumRecords : 0;
  2112. }
  2113.  
  2114. PUBLIC gh_SortColumn
  2115. GH_GetSortField( GHHANDLE pContext )
  2116. {
  2117.    gh_HistContext *hGHContext = (gh_HistContext *)pContext;
  2118.    
  2119.    return hGHContext ? hGHContext->enGHSort : eGH_NoSort;
  2120. }
  2121.  
  2122.  
  2123. PR_PUBLIC_API(void) updateNewHistItem (DBT *key, DBT *data);
  2124.  
  2125.  
  2126. PUBLIC int
  2127. GH_UpdateURLTitle( URL_Struct *pUrl, char *pszTitle, Bool bFrameCell )
  2128. {
  2129.     DBT key, data, dataNew;
  2130.     int status;
  2131.     int iNameLen;
  2132.     int8 *pData;
  2133.     static int32 count=0;
  2134.     int32 iFlags = bFrameCell ? (GH_FLAGS_SHOW | GH_FLAGS_FRAMECELL) : GH_FLAGS_SHOW;
  2135.         
  2136.     if( !pUrl || !pUrl->address || !pszTitle )
  2137.     {
  2138.         return -1;
  2139.     }
  2140.     
  2141.     if( !gh_database )
  2142.     {
  2143.         return -1;
  2144.     }
  2145.  
  2146.     gh_open_database();
  2147.  
  2148.     if( !gh_database )
  2149.     {
  2150.         return -1;
  2151.     }
  2152.     
  2153.     global_history_has_changed = TRUE;
  2154.     urlLookupGlobalHistHasChanged = TRUE;
  2155.  
  2156.     count++;  /* Increment change count */
  2157.     
  2158.     key.data = (void *)pUrl->address;
  2159.     key.size = (XP_STRLEN(pUrl->address)+1) * sizeof(char);
  2160.  
  2161.     status = (*gh_database->get)(gh_database, &key, &data, 0);
  2162.  
  2163.     if( status < 0 )
  2164.     {
  2165.        TRACEMSG(("Database ERROR retreiving global history entry"));
  2166.        return -1;
  2167.     }
  2168.     else if( status > 0 )
  2169.     {
  2170.        return -1;
  2171.     }
  2172.  
  2173.     /* Object was found */
  2174.     
  2175.     iNameLen = XP_STRLEN( pszTitle )+1;    
  2176.     dataNew.size = sizeof(int32) + sizeof(int32) + sizeof(int32) + sizeof(int32) + iNameLen*sizeof(char);
  2177.     
  2178.     pData = XP_ALLOC( dataNew.size );
  2179.  
  2180.     dataNew.data = (void *)pData;
  2181.     
  2182.     /*
  2183.     // Copy the record's data into the new buffer
  2184.     */
  2185.     XP_MEMCPY( pData, data.data, (data.size < dataNew.size) ? data.size : dataNew.size );
  2186.     
  2187.     /*
  2188.     // Now overwrite the old title with the new
  2189.     */
  2190.     if( iNameLen > 1  )
  2191.     {
  2192.         XP_STRCPY( (char *)pData+4*sizeof(int32), pszTitle );
  2193.     }
  2194.     else
  2195.     {
  2196.         *(char *)(pData+4*sizeof(int32)) = 0;
  2197.     }
  2198.  
  2199.     /*
  2200.     // Mark this record for global history viewing
  2201.     */
  2202.     COPY_INT32( pData+3*sizeof(int32), &iFlags );
  2203.     
  2204.     /*
  2205.     // Update the table
  2206.     */
  2207.     status = (*gh_database->put)( gh_database, &key, &dataNew, 0 );
  2208.  
  2209.  
  2210.     /* update the  history display in nav center if its open */
  2211.     if (iFlags == 1) updateNewHistItem(&key, &dataNew);
  2212.  
  2213.  
  2214.     XP_FREE( pData );
  2215.     
  2216.     if( status < 0 )
  2217.     {
  2218.         TRACEMSG(("Global history update failed due to database error"));
  2219.         gh_RemoveDatabase();
  2220.     }
  2221.     else if( count >= SYNC_RATE )
  2222.     {
  2223.         count = 0;
  2224.         if( -1 == (*gh_database->sync)( gh_database, 0 ) )
  2225.         {
  2226.             TRACEMSG(("Error syncing gh database"));
  2227.         }
  2228.     }
  2229.     
  2230.     /*
  2231.     // Notify the contexts of the update
  2232.     */
  2233.     GH_NotifyContexts( GH_NOTIFY_UPDATE, (char *)key.data );
  2234.     return 0;
  2235. }
  2236.  
  2237. PUBLIC gh_HistEntry *
  2238. GH_GetRecord( GHHANDLE pContext, uint32 uRow )
  2239. {
  2240.    DBT key, data;
  2241.    static gh_HistEntry ghEntry;
  2242.    static char szTitle[1024];
  2243.    int32 iDefault = 1;
  2244.    
  2245.    gh_HistContext *hGHContext = (gh_HistContext *)pContext;
  2246.    
  2247.    if( !hGHContext || !hGHContext->uNumRecords )
  2248.    {
  2249.       return NULL;
  2250.    }
  2251.    
  2252.    if( uRow > (hGHContext->uNumRecords-1) )
  2253.    {
  2254.       /* Row is out of range (cannot be less than 0 since it is unsigned */
  2255.       return NULL;
  2256.    }
  2257.    
  2258.    key.data = (void *)hGHContext->pHistSort[uRow]->pKeyData;
  2259.    key.size = (XP_STRLEN(key.data)+1) * sizeof(char);
  2260.    
  2261.    ghEntry.address = key.data;
  2262.    
  2263.    if( 0 == (*gh_database->get)( gh_database, &key, &data, 0 ) )
  2264.    {
  2265.       if( data.size > sizeof(int32) )
  2266.       {
  2267.          /* The entry/record is of the new format */
  2268.  
  2269.          COPY_INT32( &ghEntry.first_accessed, (int8 *)data.data + sizeof(int32) );
  2270.          
  2271.          COPY_INT32( &ghEntry.last_accessed, data.data );
  2272.          
  2273.          COPY_INT32( &ghEntry.iCount, (int8 *)data.data + 2*sizeof(int32) );         
  2274.          
  2275.          XP_STRNCPY_SAFE( szTitle, (char *)data.data + 4*sizeof(int32), sizeof(szTitle)-1 );
  2276.          ghEntry.pszName = szTitle;
  2277.       }
  2278.       else
  2279.       {
  2280.          /* The entry/record is of the old format i.e., only last_accessed date available */
  2281.          
  2282.          char *pszTitle = GH_GetTitleFromURL( ghEntry.address );         
  2283.          XP_STRNCPY_SAFE( szTitle, pszTitle, sizeof(szTitle)-1 );
  2284.          pszTitle = strrchr( szTitle, '/' );
  2285.          if( pszTitle )
  2286.          {
  2287.            /* Remove the trailing slash from the title */
  2288.             *pszTitle = 0;
  2289.          }
  2290.          ghEntry.pszName = szTitle;
  2291.                   
  2292.          COPY_INT32( &ghEntry.first_accessed, data.data );
  2293.          
  2294.          COPY_INT32( &ghEntry.last_accessed, data.data );         
  2295.          
  2296.          COPY_INT32( &ghEntry.iCount, &iDefault );         
  2297.       }
  2298.       return &ghEntry;
  2299.       
  2300.    }
  2301.    else
  2302.    {
  2303.       /* The entry is not there, we're out of sync somehow. */
  2304.       return NULL;
  2305.    }
  2306. }
  2307.  
  2308. PUBLIC void
  2309. GH_DeleteRecord( GHHANDLE pContext, uint32 uRow, Bool bGroup )
  2310. {
  2311.     DBT key, data;
  2312.     int status;
  2313.        
  2314.     gh_HistContext *hGHContext = (gh_HistContext *)pContext;
  2315.     
  2316.     if( !hGHContext || !hGHContext->uNumRecords )
  2317.     {
  2318.        return;
  2319.     }
  2320.     
  2321.     if( uRow > (hGHContext->uNumRecords-1) )
  2322.     {
  2323.        /* Row is out of range (cannot be less than 0 since it is unsigned */
  2324.        return;
  2325.     }
  2326.     
  2327.     key.data = (void *)hGHContext->pHistSort[uRow]->pKeyData;
  2328.     key.size = (XP_STRLEN(key.data)+1) * sizeof(char);
  2329.  
  2330.     if( hGHContext->pURContext )
  2331.     {
  2332.         gh_RecordList *  pRecordNode = NULL;
  2333.         gh_URList *      pURNode = NULL;
  2334.         
  2335.         /* Get the record's data so we can undo the operation if necessary */
  2336.         
  2337.         status = (*gh_database->get)( gh_database, &key, &data, 0 );
  2338.         if( status < 0 )
  2339.         {
  2340.             TRACEMSG(("Database ERROR retreiving global history entry"));
  2341.             return;
  2342.         }
  2343.         else if( status > 0 )
  2344.         {
  2345.             return;
  2346.         }
  2347.         
  2348.         pRecordNode = GH_CreateRecordNode( &key, &data );
  2349.         
  2350.         if( bGroup )
  2351.         {
  2352.             GH_PushGroupUndo( hGHContext->pURContext, pRecordNode );
  2353.         }
  2354.         else
  2355.         {
  2356.             pURNode = GH_CreateURNode( pRecordNode );
  2357.             GH_PushUR( &hGHContext->pURContext->pUndoList, pURNode );
  2358.             
  2359.             /* Must purge the redo list whenever we add a new undo item */
  2360.             GH_DeleteURList( hGHContext->pURContext->pRedoList );
  2361.             hGHContext->pURContext->pRedoList = NULL;
  2362.         }
  2363.     }
  2364.     
  2365.     (*gh_database->del)( gh_database, &key, 0 );    
  2366.     
  2367.     /*
  2368.     // Notify the contexts of the deletion
  2369.     */
  2370.     GH_NotifyContexts( GH_NOTIFY_DELETE, (char *)key.data );
  2371. }
  2372.  
  2373. PUBLIC int32 
  2374. GH_GetRecordNum( GHHANDLE pContext, char *pszLocation )
  2375. {
  2376.    int32 i = 0;
  2377.       
  2378.    gh_HistContext *hGHContext = (gh_HistContext *)pContext;
  2379.    
  2380.    if( !hGHContext || !hGHContext->uNumRecords || !pszLocation )
  2381.    {
  2382.       return -1;
  2383.    }
  2384.  
  2385.    for( i = 0; i < (int32)hGHContext->uNumRecords; i++ )
  2386.    {
  2387.       if( !XP_STRCMP( pszLocation, hGHContext->pHistSort[i]->pKeyData ) )
  2388.       {
  2389.          return i;
  2390.       }
  2391.    }
  2392.    
  2393.    return -1;
  2394. }
  2395.  
  2396. /* Writes out a URL entry to look like:
  2397.  *
  2398.  * <DT><A HREF="http://www.netscape.com" \
  2399.  * ADD_DATE="777240414" LAST_VISIT="802992591">Welcome To Netscape</A>
  2400.  *
  2401.  */
  2402. PRIVATE int
  2403. GH_WriteURL( XP_File fp, gh_HistEntry *item )
  2404. {
  2405.    char buffer[16];
  2406.  
  2407.    WRITE( "<DT>", -1, fp );
  2408.  
  2409.    /* write address */
  2410.    WRITE( "<A HREF=\"", -1, fp );
  2411.    WRITE( item->address, -1, fp );
  2412.    WRITE( "\"", -1, fp );
  2413.  
  2414.    /* write the addition date  */
  2415.    WRITE( " FIRST_VISIT=\"", -1, fp );
  2416.    XP_SPRINTF( buffer, "%ld", item->first_accessed );
  2417.    WRITE( buffer, -1, fp );
  2418.    WRITE( "\"", -1, fp );
  2419.  
  2420.    /* write the last visited date */
  2421.    WRITE( " LAST_VISIT=\"", -1, fp );
  2422.    XP_SPRINTF( buffer, "%ld\"", item->last_accessed );
  2423.    WRITE( buffer, -1, fp );
  2424.  
  2425.    /* write the last modified date */
  2426.    WRITE( " VISIT_COUNT=\"", -1, fp );
  2427.    XP_SPRINTF( buffer, "%i\"", item->iCount );
  2428.    WRITE( buffer, -1, fp );
  2429.  
  2430.    WRITE( ">", -1, fp );
  2431.  
  2432.    /* write the name */
  2433.  
  2434.    if( item->pszName ) 
  2435.    {
  2436.       WRITE( item->pszName, -1, fp );
  2437.    }
  2438.  
  2439.    WRITE( "</A>", -1, fp );
  2440.    WRITE( LINEBREAK, LINEBREAK_LEN, fp );
  2441.  
  2442.    return 0;
  2443. }
  2444.  
  2445. PRIVATE void
  2446. GH_WriteHTML( MWContext *context, char *filename, GHHANDLE pContext )
  2447. {
  2448.    XP_File           fp = NULL;
  2449.    XP_FileType       tmptype;
  2450.    char *            tmpname = NULL;
  2451.    int32             i = 0;  
  2452.    gh_HistContext *  hGHContext = (gh_HistContext *)pContext;
  2453.  
  2454.    if( !hGHContext || !filename || (filename[0] == '\0') )
  2455.    {
  2456.       return;
  2457.    }
  2458.    
  2459.  #if 0
  2460.    tmpname = FE_GetTempFileFor( NULL, filename, xpGlobalHistoryList, &tmptype );
  2461.  #else
  2462.    tmpname = filename;
  2463.    tmptype = xpFileToPost; /* let's us simply use the name the user types */
  2464.  #endif
  2465.  
  2466.    fp = XP_FileOpen( tmpname, tmptype, XP_FILE_WRITE );
  2467.  
  2468.    if( !fp )
  2469.    {
  2470.       goto FAIL;
  2471.    }
  2472.  
  2473.    /* write cookie */
  2474.    if( gh_write_ok( GLHIST_COOKIE, -1, fp) < 0 ) goto FAIL;
  2475.    if( gh_write_ok( LINEBREAK, LINEBREAK_LEN, fp ) < 0 ) goto FAIL;
  2476.    if( gh_write_ok( LINEBREAK, LINEBREAK_LEN, fp ) < 0 ) goto FAIL;
  2477.  
  2478.    /* Write out all the history records according to the context/cursor */
  2479.    for( i = 0; i < (int32)hGHContext->uNumRecords; i++ )
  2480.    {
  2481.       gh_HistEntry *pHistEntry = GH_GetRecord( hGHContext, i );
  2482.       if( pHistEntry )
  2483.       {
  2484.          GH_WriteURL( fp, pHistEntry );
  2485.       }
  2486.    }
  2487.   
  2488.    if( XP_FileClose( fp ) != 0 ) 
  2489.    {
  2490.       fp = NULL;
  2491.       goto FAIL;
  2492.    }
  2493.    fp = NULL;
  2494.  #if 0  
  2495.    XP_FileRename( tmpname, tmptype, filename, xpGlobalHistoryList );
  2496.  #else
  2497.    XP_FREE( tmpname );
  2498.  #endif
  2499.    tmpname = NULL;
  2500.  
  2501.    return;
  2502.  
  2503.  FAIL:
  2504.    if( fp ) 
  2505.    {
  2506.       XP_FileClose(fp);
  2507.    }
  2508.    
  2509.    if( tmpname ) 
  2510.    {
  2511.       XP_FileRemove( tmpname, tmptype );
  2512.       XP_FREE( tmpname );
  2513.       tmpname = NULL;
  2514.    }
  2515.  
  2516.    return;
  2517. }
  2518.  
  2519. PUBLIC void
  2520. GH_FileSaveAsHTML( GHHANDLE pContext, MWContext *pMWContext )
  2521. {
  2522.    if( !pContext )
  2523.    {
  2524.       return;
  2525.    }
  2526.    
  2527.    FE_PromptForFileName( pMWContext, XP_GetString( XP_HISTORY_SAVE ), 0, FALSE, FALSE, GH_WriteHTML, pContext );
  2528. }
  2529.  
  2530. PUBLIC GHURHANDLE GH_GetURContext( GHHANDLE pContext )
  2531. {
  2532.    gh_HistContext *hGHContext = (gh_HistContext *)pContext;
  2533.    
  2534.    if( !pContext )
  2535.    {
  2536.       return NULL;
  2537.    }
  2538.  
  2539.    return hGHContext->pURContext;
  2540. }
  2541.  
  2542. PUBLIC void GH_SupportUndoRedo( GHHANDLE pContext )
  2543. {
  2544.    gh_HistContext *hGHContext = (gh_HistContext *)pContext;
  2545.    
  2546.    if( !pContext )
  2547.    {
  2548.       return;
  2549.    }
  2550.  
  2551.    GH_CreateURContext( hGHContext );
  2552. }
  2553.  
  2554. PRIVATE gh_RecordList *
  2555. GH_CreateRecordNode( DBT *pKey, DBT *pData )
  2556. {
  2557.    gh_RecordList *pRecordNode = NULL;
  2558.    
  2559.    if( !pKey ||
  2560.        !pKey->data ||
  2561.        !pData ||
  2562.        !pData->data )
  2563.    {
  2564.       return NULL;
  2565.    }
  2566.    
  2567.    pRecordNode = XP_ALLOC( sizeof(gh_RecordList) );
  2568.    
  2569.    pRecordNode->key.data  = XP_ALLOC( pKey->size );
  2570.    XP_MEMCPY( pRecordNode->key.data, pKey->data, pKey->size );   
  2571.    pRecordNode->key.size = pKey->size;
  2572.    
  2573.    pRecordNode->data.data = XP_ALLOC( pData->size );
  2574.    XP_MEMCPY( pRecordNode->data.data, pData->data, pData->size );   
  2575.    pRecordNode->data.size = pData->size;
  2576.  
  2577.    pRecordNode->pNext = NULL;
  2578.    
  2579.    return pRecordNode;
  2580. }
  2581.  
  2582. PRIVATE void 
  2583. GH_PushRecord( gh_RecordList **ppRecordList, gh_RecordList *pRecordNode )
  2584. {
  2585.    if( !ppRecordList || !pRecordNode )
  2586.    {
  2587.       return;
  2588.    }
  2589.    
  2590.    pRecordNode->pNext = *ppRecordList ? *ppRecordList : NULL;
  2591.    *ppRecordList = pRecordNode;
  2592. }
  2593.  
  2594. PRIVATE void 
  2595. GH_PushGroupUndo( gh_URContext *pURContext, gh_RecordList *pRecordNode )
  2596. {
  2597.    if( !pURContext || !pURContext->pUndoList || !pRecordNode )
  2598.    {
  2599.        return;
  2600.    }
  2601.    
  2602.    GH_PushRecord( &pURContext->pUndoList->pURItem, pRecordNode );
  2603. }
  2604.  
  2605. PRIVATE gh_URList *
  2606. GH_CreateURNode( gh_RecordList *pRecordList )
  2607. {
  2608.    gh_URList *pURNode = NULL;
  2609.  
  2610.    if( !pRecordList )   
  2611.    {
  2612.       return NULL;
  2613.    }
  2614.    
  2615.    pURNode = XP_ALLOC( sizeof(gh_URList) );
  2616.    pURNode->pURItem = pRecordList;
  2617.    pURNode->pNext   = NULL;
  2618.    
  2619.    return pURNode;
  2620. }
  2621.  
  2622. PRIVATE void 
  2623. GH_PushUR( gh_URList **ppURList, gh_URList *pURNode )
  2624. {
  2625.    if( !ppURList || !pURNode )
  2626.    {
  2627.       return;
  2628.    }
  2629.    
  2630.    pURNode->pNext = *ppURList ? *ppURList : NULL;
  2631.    *ppURList = pURNode;
  2632. }
  2633.  
  2634. PRIVATE gh_URList *
  2635. GH_PopUR( gh_URList **ppURList )
  2636. {
  2637.    gh_URList *pURNode = NULL;
  2638.       
  2639.    if( !ppURList )
  2640.    {
  2641.       return NULL;
  2642.    }
  2643.  
  2644.    pURNode = *ppURList;
  2645.    *ppURList = (*ppURList)->pNext;
  2646.    
  2647.    return pURNode;
  2648. }
  2649.  
  2650. PUBLIC void 
  2651. GH_Undo( GHHANDLE hContext )
  2652. {
  2653.     gh_HistContext *pContext = (gh_HistContext *)hContext;
  2654.     gh_URList *pURNode = NULL;
  2655.  
  2656.     int status;
  2657.     gh_RecordList *pCsr;
  2658.   
  2659.     if( !pContext || 
  2660.         !pContext->pURContext || 
  2661.         !pContext->pURContext->pUndoList )
  2662.     {
  2663.        return;
  2664.     }
  2665.     
  2666.     pURNode = GH_PopUR( &pContext->pURContext->pUndoList );
  2667.     if( !pURNode )
  2668.     {
  2669.        return;
  2670.     }
  2671.  
  2672.     if( !gh_database )
  2673.     {
  2674.         return;
  2675.     }
  2676.  
  2677.     gh_open_database();
  2678.  
  2679.     if( !gh_database )
  2680.     {
  2681.         return;
  2682.     }
  2683.     
  2684.     global_history_has_changed = TRUE;
  2685.     urlLookupGlobalHistHasChanged = TRUE;
  2686.  
  2687.  
  2688.     pCsr = pURNode->pURItem;
  2689.     while( pCsr )
  2690.     {
  2691.         /*
  2692.         // Update the table
  2693.         */
  2694.         status = (*gh_database->put)( gh_database, &pCsr->key, &pCsr->data, 0 );
  2695.  
  2696.         if( status < 0 )
  2697.         {
  2698.             TRACEMSG(("Global history update failed due to database error"));
  2699.             gh_RemoveDatabase();
  2700.         }
  2701.  
  2702.         /*
  2703.         // Notify the contexts of the update
  2704.         */
  2705.         GH_NotifyContexts( GH_NOTIFY_UPDATE, (char *)pCsr->key.data );
  2706.         
  2707.         pCsr = pCsr->pNext;
  2708.     }
  2709.  
  2710.     GH_PushUR( &pContext->pURContext->pRedoList, pURNode );
  2711. }
  2712.  
  2713. PUBLIC void 
  2714. GH_Redo( GHHANDLE hContext )
  2715. {
  2716.     gh_HistContext *pContext = (gh_HistContext *)hContext;
  2717.     gh_URList *pURNode = NULL;
  2718.  
  2719.     int status;
  2720.     gh_RecordList *pCsr;
  2721.   
  2722.     if( !pContext || 
  2723.         !pContext->pURContext || 
  2724.         !pContext->pURContext->pRedoList )
  2725.     {
  2726.        return;
  2727.     }
  2728.     
  2729.     pURNode = GH_PopUR( &pContext->pURContext->pRedoList );
  2730.     if( !pURNode )
  2731.     {
  2732.        return;
  2733.     }
  2734.  
  2735.     if( !gh_database )
  2736.     {
  2737.         return;
  2738.     }
  2739.  
  2740.     gh_open_database();
  2741.  
  2742.     if( !gh_database )
  2743.     {
  2744.         return;
  2745.     }
  2746.     
  2747.     global_history_has_changed = TRUE;
  2748.     urlLookupGlobalHistHasChanged = TRUE;
  2749.  
  2750.  
  2751.     pCsr = pURNode->pURItem;
  2752.     while( pCsr )
  2753.     {
  2754.         /*
  2755.         // Delete the record
  2756.         */
  2757.         status = (*gh_database->del)( gh_database, &pCsr->key, 0 );
  2758.  
  2759.         if( status < 0 )
  2760.         {
  2761.             TRACEMSG(("Global history update failed due to database error"));
  2762.             gh_RemoveDatabase();
  2763.         }
  2764.         else if( status > 0 )
  2765.         {
  2766.             /* Not found */
  2767.             pCsr = pCsr->pNext;        
  2768.             continue;
  2769.         }
  2770.  
  2771.         /*
  2772.         // Notify the contexts of the deletion
  2773.         */
  2774.         GH_NotifyContexts( GH_NOTIFY_DELETE, (char *)pCsr->key.data );
  2775.         
  2776.         pCsr = pCsr->pNext;
  2777.     }
  2778.  
  2779.     GH_PushUR( &pContext->pURContext->pUndoList, pURNode );
  2780. }
  2781.  
  2782. PUBLIC Bool GH_CanUndo( GHHANDLE hContext )
  2783. {
  2784.     Bool bRet = FALSE;
  2785.     gh_HistContext *pContext = (gh_HistContext *)hContext;
  2786.     
  2787.     if( pContext &&
  2788.         pContext->pURContext &&
  2789.         pContext->pURContext->pUndoList )
  2790.     {
  2791.        bRet = TRUE;
  2792.     }
  2793.     
  2794.     return bRet;
  2795. }
  2796.  
  2797. PUBLIC Bool GH_CanRedo( GHHANDLE hContext )
  2798. {
  2799.     Bool bRet = FALSE;
  2800.     gh_HistContext *pContext = (gh_HistContext *)hContext;
  2801.     
  2802.     if( pContext &&
  2803.         pContext->pURContext &&
  2804.         pContext->pURContext->pRedoList )
  2805.     {
  2806.        bRet = TRUE;
  2807.     }
  2808.     
  2809.     return bRet;
  2810. }
  2811.  
  2812. PRIVATE void GH_DeleteURList( gh_URList *pURList )
  2813. {
  2814.    gh_URList *pTrash = NULL;
  2815.    
  2816.    if( !pURList )
  2817.    {
  2818.       return;
  2819.    }
  2820.    
  2821.    do
  2822.    {
  2823.       pTrash = pURList;
  2824.       pURList = pURList->pNext;
  2825.       GH_DeleteRecordList( pTrash->pURItem );
  2826.       XP_FREE( pTrash );
  2827.       
  2828.    }while( pURList );
  2829. }
  2830.  
  2831. PUBLIC int GH_GetMRUPage( char *pszURL, int iMaxLen )
  2832. {
  2833.     /*
  2834.     //  Note this function will not return a cell in a frame, instead it returns the mru frame set url.
  2835.     */
  2836.     time_t           date1, date2 = 0;
  2837.     DBT              key, data;
  2838.     
  2839.     if( !gh_database || (global_history_timeout_interval < 1) )
  2840.     {
  2841.         return 0;
  2842.     }
  2843.  
  2844.     if( !pszURL || (iMaxLen <= 0) )
  2845.     {
  2846.        return 0;
  2847.     }
  2848.     
  2849.     *pszURL = 0;
  2850.     
  2851.     data.size = key.size = 0;
  2852.     
  2853.     /*
  2854.     // Visit each page in the history list while maintaining the most recently visited page.
  2855.     */
  2856.     if( 0 != (*gh_database->seq)( gh_database, &key, &data, R_FIRST ) )
  2857.     {
  2858.         return 0;
  2859.     }
  2860.     
  2861.     do
  2862.     {
  2863.         if( data.size > sizeof(int32) )
  2864.         {
  2865.             /*
  2866.             // The entry/record is of the new format...        
  2867.             //
  2868.             
  2869.             // Ignore history records which are NOT flagged as having been explicitly loaded
  2870.             // e.g., don't expose gif images that are only part of a page
  2871.             */
  2872.             int32 iFlags;
  2873.             COPY_INT32( &iFlags, (int8 *)data.data + 3*sizeof(int32) );
  2874.             
  2875.             if( !(iFlags & GH_FLAGS_SHOW) || (iFlags & GH_FLAGS_FRAMECELL) )
  2876.             {
  2877.                /* The record is either not flagged for showing or is just a cell or both */
  2878.                
  2879.                continue;
  2880.             }
  2881.         }
  2882.         else
  2883.         {   
  2884.             /*
  2885.             // The entry/record is of the old format...
  2886.             //
  2887.             
  2888.             // Try to be somewhat smart and filter the implicitly loaded stuff i.e., non-html docs
  2889.             */
  2890.             char *pszExt = strrchr( key.data, '.' );
  2891.             
  2892.             if( ((char *)key.data)[XP_STRLEN(key.data)-1] == '/' )
  2893.             {
  2894.             }
  2895.             else if( XP_STRLEN(key.data) < 5 )
  2896.             {
  2897.                continue;
  2898.             }
  2899.             else if( pszExt )
  2900.             {
  2901.                pszExt++;
  2902.                if( strncasecomp( pszExt, "htm", 3 ) )
  2903.                {
  2904.                   continue;
  2905.                }
  2906.             }
  2907.             else
  2908.             {
  2909.                continue;
  2910.             }
  2911.         }
  2912.         
  2913.         /* Note the last_accessed field is at the beginning of the data regardless of new/old format */
  2914.         
  2915.         COPY_INT32( &date1, data.data );
  2916.         
  2917.         if( difftime( date1, date2 ) > 0 )
  2918.         {
  2919.             XP_STRNCPY_SAFE( pszURL, key.data, iMaxLen );
  2920.             COPY_INT32( &date2, (int8 *)data.data );
  2921.         }      
  2922.         
  2923.     }while( 0 == (gh_database->seq)( gh_database, &key, &data, R_NEXT ) );
  2924.  
  2925.     return XP_STRLEN( pszURL );
  2926. }
  2927.  
  2928. PRIVATE void GH_DeleteRecordList( gh_RecordList *pRecordList )
  2929. {
  2930.    gh_RecordList *pTrash = NULL;
  2931.    
  2932.    if( !pRecordList )
  2933.    {
  2934.       return;
  2935.    }
  2936.    
  2937.    do
  2938.    {
  2939.       pTrash = pRecordList;
  2940.       pRecordList = pRecordList->pNext;
  2941.       XP_FREE( pTrash->key.data );
  2942.       XP_FREE( pTrash->data.data );
  2943.       XP_FREE( pTrash );
  2944.       
  2945.    }while( pRecordList );
  2946. }
  2947.  
  2948. /*
  2949. // Return a pointer somewhere in pszURL where a somewhat meaningful title may be.
  2950. // A pointer to the char following the last or second to last slash is returned
  2951. // if a string exists after the slash, otherwise pszURL is returned.
  2952. */
  2953. PRIVATE char *GH_GetTitleFromURL( char *pszURL )
  2954. {
  2955.    char *pszTitle = NULL;
  2956.    char *pszSlash = NULL;
  2957.    
  2958.    if( !pszURL || !*pszURL )
  2959.    {
  2960.       return pszURL;
  2961.    }
  2962.  
  2963.    pszTitle = strrchr( pszURL, '/' );
  2964.  
  2965.    if( pszTitle )
  2966.    {
  2967.       if( *(pszTitle+1) )
  2968.       {
  2969.          /*
  2970.          // The location does not end with a slash so we'll use the sub-string
  2971.          // following the the last slash.
  2972.          */
  2973.          
  2974.          pszTitle++;
  2975.       }
  2976.       else
  2977.       {
  2978.          /*
  2979.          // The location ends with a slash, so we'll start at the second from last slash.
  2980.          */
  2981.          
  2982.          *pszTitle = 0;
  2983.          pszSlash = pszTitle; /* Save this position so we can put the slash back */
  2984.          pszTitle = strrchr( pszURL, '/' );
  2985.          *pszSlash = '/';
  2986.          
  2987.          if( pszTitle )
  2988.          {
  2989.             if( *(pszTitle+1) )
  2990.             {
  2991.                pszTitle++;
  2992.             }
  2993.             else
  2994.             {
  2995.                pszTitle = pszURL;
  2996.             }
  2997.          }
  2998.       }
  2999.    }
  3000.    else
  3001.    {
  3002.       pszTitle = pszURL;
  3003.    }
  3004.    
  3005.    return pszTitle;
  3006. }
  3007.  
  3008. /* fix Mac warning about missing prototype */
  3009. PUBLIC Bool
  3010. NET_EnableUrlMatch(void);
  3011.  
  3012. PUBLIC Bool
  3013. NET_EnableUrlMatch(void)
  3014. {
  3015.     return enableUrlMatch;
  3016. }
  3017.  
  3018. /* fix Mac warning about missing prototype */
  3019. PUBLIC void
  3020. NET_SetEnableUrlMatchPref(Bool x);
  3021.  
  3022. PUBLIC void
  3023. NET_SetEnableUrlMatchPref(Bool x)
  3024. {
  3025.     enableUrlMatch=x;
  3026. }
  3027.  
  3028. /* fix Mac warning about missing prototype */
  3029. MODULE_PRIVATE int PR_CALLBACK
  3030. NET_EnableUrlMatchPrefChanged(const char *pref, void *data);
  3031.  
  3032. MODULE_PRIVATE int PR_CALLBACK
  3033. NET_EnableUrlMatchPrefChanged(const char *pref, void *data)
  3034. {
  3035.     Bool x;
  3036.  
  3037.     PREF_GetBoolPref("network.enableUrlMatch", &x);
  3038.     NET_SetEnableUrlMatchPref(x);
  3039.     return PREF_NOERROR;
  3040. }
  3041.  
  3042. PUBLIC void
  3043. NET_RegisterEnableUrlMatchCallback(void)
  3044. {
  3045.     Bool x;
  3046.  
  3047.     PREF_GetBoolPref("network.enableUrlMatch", &x);
  3048.     NET_SetEnableUrlMatchPref(x);
  3049.     PREF_RegisterCallback("network.enableUrlMatch", NET_EnableUrlMatchPrefChanged, NULL);
  3050. }
  3051.  
  3052. /*    Is the string passed in too general. */
  3053. PRIVATE Bool
  3054. net_url_sub_string_too_general(const char *criteria, int32 len)
  3055. {
  3056.     if( (!criteria) || (len < 1) )
  3057.         return TRUE;
  3058.  
  3059.     /* case insensative compares */
  3060.     if( !strncasecomp(criteria, "www.", len) ||
  3061.         !strncasecomp(criteria, "http://www.", len) ||
  3062.         !strncasecomp(criteria, "ftp.", len) ||
  3063.         !strncasecomp(criteria, "ftp://ftp.", len) ||
  3064.         !strncasecomp(criteria, "file:", len)
  3065.         )
  3066.         return TRUE;
  3067.     return FALSE;
  3068. }
  3069.  
  3070. /* Determines whether we want to deal with this url. I'm doing some interpretation here. If the user has
  3071.    www.abc.com/cgi/laksjdlskjds121212121 in their global history, I'm assuming they don't want this to come
  3072.    up in the completeion. They may though. You can't satisfy everyone. */
  3073. PRIVATE Bool
  3074. net_url_weed_out(const char *url, int32 len)
  3075. {
  3076.     if( (!url) || (len < 0) )
  3077.         return TRUE;
  3078.     url = url + len - 1;
  3079.     if(!(*url == 'l' ||
  3080.         *url == 'L' || 
  3081.         *url == 'm' || 
  3082.         *url == 'M' || 
  3083.         *url == 'p' || /* some msoft frontpage-like cgi filename i.e. default.asp */
  3084.         *url == 'P' ||
  3085.         *url == '/') )
  3086.         return TRUE;
  3087.     return FALSE;
  3088. }
  3089.  
  3090.  
  3091. /* Description:
  3092.     Tries to find a match in the global history database given the criteria string. If found
  3093.     the match is returned via result (result is passed in as an unallocated address of a char *,
  3094.     and returned as an allocated char * if the return val of the fctn is foundDone; you're 
  3095.     responsible for freeing it).
  3096.  
  3097.    Parameters:
  3098.     criteria - a string for me to search for.
  3099.     result - the address of a char * where I will put data if I find it.
  3100.     freshStart - Is this the first time calling me? Yes == TRUE.
  3101.     scroll - Am I being called because user wants to scroll through matches?
  3102.  
  3103.    Return Type:
  3104.     enum autoCompStatus (declared in glhist.h)
  3105.    Return Values:
  3106.     foundDone - the unallocated address of a char * you passed in now has data in it. The data
  3107.         consists of the best match I could find.
  3108.     notFoundDone - I searched all my resources and couldn't find anything (ie don't call me again
  3109.         with the same criteria), or I found something but it was the same thing I last returned to 
  3110.         you, or what you passed in is what I returned to you last time you called.
  3111.     stillSearching - I don't search through all my resources at once, call me again
  3112.     dontCallOnIdle - Don't call me again with the same criteria, the criteria is too general and
  3113.         I don't want to waste cycles.
  3114.  
  3115.    This function uses a totally inefficient means of searching (sequential). Function is optimized
  3116.    for speed, not flexibility or readibility.
  3117.  
  3118.   Created by: Judson Valeski, 1997
  3119. */
  3120. PUBLIC enum autoCompStatus
  3121. urlMatch(const char *criteria, char **result, Bool freshStart, Bool scroll)
  3122. {
  3123.     static char *lastURLCompletion=NULL;
  3124.     int32 eLen, cLen, cPathLen=0, count=0;
  3125.     DBT key, data;
  3126.     char *t=NULL, *p=NULL, *host=NULL, *ePath=NULL, *cPath=NULL;
  3127.     char *eProtocolColon=NULL, *cProtocolColon=NULL;
  3128.     
  3129.     if(!NET_EnableUrlMatch())
  3130.         return notFoundDone;
  3131.  
  3132.     if(!criteria || (*criteria == '/') )
  3133.         return notFoundDone;
  3134.  
  3135.     /* Is it ok to use the database. */
  3136.     if(!gh_database || global_history_timeout_interval < 1)
  3137.         return notFoundDone;
  3138.  
  3139.     cLen = XP_STRLEN(criteria);
  3140.  
  3141.     /* Is the criteria too general? ie. www or ftp, etc */
  3142.     if( net_url_sub_string_too_general(criteria, cLen) )
  3143.         return dontCallOnIdle;
  3144.  
  3145.     /* Did the user include a protocol? If so, we want to search with protocol included. */
  3146.     cProtocolColon=XP_STRSTR(criteria, "://");
  3147.  
  3148.     /* Check to see if user has path info on url */
  3149.     if(cProtocolColon)
  3150.         cPath=XP_STRCHR(cProtocolColon+3, '/');
  3151.     else
  3152.         cPath=XP_STRCHR(criteria, '/');
  3153.  
  3154.     if(cPath)
  3155.         cPathLen=XP_STRLEN(cPath);
  3156.  
  3157.     if(freshStart || urlLookupGlobalHistHasChanged)    {
  3158.         if(0 != (*gh_database->seq)(gh_database, &key, &data, R_FIRST))
  3159.             return notFoundDone;
  3160.     }
  3161.     else {
  3162.         if(0 != (gh_database->seq)(gh_database, &key, &data, R_NEXT) )
  3163.             return notFoundDone;
  3164.     }
  3165.  
  3166.     urlLookupGlobalHistHasChanged = FALSE;
  3167.  
  3168.     /* Main search loop */
  3169.     do {
  3170.         if(count > entriesToSearch) /* entries to search is a static defined above */
  3171.             return stillSearching;
  3172.  
  3173.         /* If there's no url or there's no slash in it, move on */
  3174.         if( !((char *)key.data) || !(host=XP_STRCHR((char *)key.data, '/')) ) {
  3175.             count++;
  3176.             continue;
  3177.         }
  3178.  
  3179.         /* Get the url out of the db entry and determine whether or not we want
  3180.            to include the protocol in our search. After this if stmt, t will point to
  3181.            allocated memory that must be free'd. */
  3182.         if(cProtocolColon) {
  3183.             t = XP_STRDUP((char *)key.data);
  3184.             if(!t) {
  3185.                 count++;
  3186.                 continue;
  3187.             }
  3188.         }
  3189.         else {
  3190.             /* host is assigned in the previous if stmt and is guaranteed to be valid. */
  3191.             if( !(host[0] && host[1] && host[2]) ) {
  3192.                 /* the db entry isn't in the format that we were expecting */ 
  3193.                 count++;
  3194.                 continue;
  3195.             }
  3196.             t = XP_STRDUP(host+2);
  3197.             if(!t) {
  3198.                 count++;
  3199.                 continue;
  3200.             }
  3201.         }
  3202.  
  3203.         if( (eProtocolColon=XP_STRSTR(t, "://")) != NULL)
  3204.             ePath=XP_STRCHR(eProtocolColon+3, '/');
  3205.         else
  3206.             ePath=XP_STRCHR(t, '/');        
  3207.  
  3208.         /* If there's no path in the entity url then the db entry was bad. Move on. */
  3209.         if(!ePath) {
  3210.             XP_FREE(t);
  3211.             count++;
  3212.             continue;
  3213.         }
  3214.  
  3215.         eLen = XP_STRLEN(t);
  3216.  
  3217.         if(cPath)
  3218.             /* Do we want to weed out the url, ie. it's full of cgi stuff. */
  3219.             if( net_url_weed_out(t, eLen) )    {
  3220.                 XP_FREE(t);
  3221.                 count++;
  3222.                 continue;
  3223.             }
  3224.  
  3225.         /* See if domains are the same. Case-insensative. */
  3226.         *ePath='\0';
  3227.         if(cPath)
  3228.             *cPath='\0';
  3229.         /* If the domains aren't the same. Move on. */
  3230.         if(strncasecomp(t, criteria, cLen)) {
  3231.             if(cPath)
  3232.                 *cPath='/';
  3233.             XP_FREE(t);
  3234.             count++;
  3235.             continue;
  3236.         }
  3237.         *ePath='/';
  3238.         if(cPath)
  3239.             *cPath='/';
  3240.  
  3241.         /* See if the paths are the same. Case-sensative. 
  3242.            If there's no cPath and we've gotten this far then the user hasn't specified anything
  3243.            more than the domain and the check above determined that the domain matched so continue.
  3244.            Otherwise check the remaining chars. */
  3245.         if( !cPath || !XP_STRNCMP(ePath, cPath, cPathLen)) {
  3246.             /* if user didn't specify path info ,
  3247.                set the char just after the end to null byte */
  3248.             if(!cPath) {
  3249.                 if(cProtocolColon) {
  3250.                     if( (p=XP_STRCHR(t, '/')) != NULL && p[1] && p[2] ) {
  3251.                         if( (p=XP_STRCHR(p+2, '/')) != NULL ) {
  3252.                             p[1] = '\0';
  3253.                         }
  3254.                         else {
  3255.                             XP_FREE(t);
  3256.                             count++;
  3257.                             continue;
  3258.                         }
  3259.                     }
  3260.                 }
  3261.                 else {
  3262.                     if( (p=XP_STRCHR(t, '/')) != NULL) {
  3263.                         p[1]= '\0';
  3264.                     }
  3265.                     else {
  3266.                         XP_FREE(t);
  3267.                         count++;
  3268.                         continue;
  3269.                     }
  3270.                 }
  3271.             }
  3272.  
  3273.             /* if we're scrolling && lastURLCompletion is not empty && what we're currently
  3274.                matching against isn't what we last returned, or, what we're matching against
  3275.                is identical to what the caller passed in. */
  3276.             if( (( scroll && lastURLCompletion && (!strcasecomp(t, lastURLCompletion))) ) 
  3277.                 ||
  3278.                 (!strcasecomp(t, criteria)) ) {
  3279.  
  3280.                 if(!scroll) {
  3281.                     XP_FREEIF(lastURLCompletion);
  3282.                     lastURLCompletion=NULL;
  3283.                     XP_FREE(t);
  3284.                     return notFoundDone;
  3285.                 }
  3286.  
  3287.                 XP_FREE(t);
  3288.                 count++;
  3289.                 continue;
  3290.             }
  3291.             XP_FREEIF(lastURLCompletion);
  3292.             lastURLCompletion = XP_STRDUP(t);
  3293.             *result = XP_STRDUP(t);
  3294.             XP_FREE(t);
  3295.             return foundDone;
  3296.         }
  3297.  
  3298.         XP_FREE(t);
  3299.         count++;
  3300.     }
  3301.     while( 0 == (gh_database->seq)(gh_database, &key, &data, R_NEXT) );
  3302.     if(!scroll) {
  3303.         XP_FREEIF(lastURLCompletion);
  3304.         lastURLCompletion=NULL;
  3305.     }
  3306.     return notFoundDone;
  3307. }
  3308.