Logo Search packages:      
Sourcecode: galeon version File versions

global-history.c

/*
 *  Copyright (C) 2000, 2001, 2002 Marco Pesenti Gritti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "global-history.h"
#include "eel-gconf-extensions.h"
#include "gul-string.h"
#include "gul-general.h"
#include "galeon-config.h"
#include "galeon-autocompletion-source.h"
#include "galeon-auto-bookmarks-source.h"
#include "galeon-debug.h"

#include <libgnomevfs/gnome-vfs-uri.h>
#include <libgnome/gnome-util.h>
#include <libxml/parser.h>
#include <libxml/xmlwriter.h>
#include <glib/gstrfuncs.h>
#include <glib/gi18n.h>

#include <time.h>
#include <math.h>
#include <unistd.h>

/* when parsing using SAX, elements 0 and 1 of the attribute array are
 * the key and value respectively */
enum
{
      SAX_KEY   = 0,
      SAX_VALUE = 1
};

#define GLOBAL_HISTORY_GET_PRIVATE(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), \
                               TYPE_GLOBAL_HISTORY, GlobalHistoryPrivate))


struct GlobalHistoryPrivate
{
      GHashTable *history_sites;
      GHashTable *history_hosts;
      HistoryItem *last_visited_site;
      gboolean history_dirty;
      gint expire_days;
      guint timeout;
};

struct HistoryItemPrivate
{
      HistoryItem *host;
      GHashTable *properties;
      GSList *children;
      gboolean is_host;
      gint zoom;
};

enum
{
        ADD,
      UPDATE,
      REMOVE,
        LAST_SIGNAL
};

static void
global_history_autocompletion_source_init (GaleonAutocompletionSourceIface *iface);
static void
global_history_auto_bookmarks_source_init (GaleonAutoBookmarksSourceIface *iface);
static void
global_history_finalize (GObject *object);
static void
global_history_autocompletion_source_foreach (GaleonAutocompletionSource *source,
                                    const gchar *basic_key,
                                    GaleonAutocompletionSourceForeachFunc func,
                                    gpointer data);
static void
global_history_autocompletion_source_set_basic_key (GaleonAutocompletionSource *source,
                                        const gchar *basic_key);
static void
global_history_auto_bookmarks_source_get_autobookmarks      (GaleonAutoBookmarksSource *source, 
                                           GaleonAutoBookmarksSourceIteratorFunc iterator,
                                           gpointer iterator_data,
                                           GaleonAutoBookmarksSourceFilterFunc filter,
                                           gpointer filter_data,
                                           GaleonAutoBookmarksScoringMethod scoring,
                                           gboolean group_by_host);
static void
global_history_emit_changed_data_changed (GlobalHistory *gh);


static HistoryItem *
history_add_host (GlobalHistory *gh, 
              const char *name);
static HistoryItem *
history_add_item (GlobalHistory *gh,
              char *url, 
              char *title, 
              GTime first, 
              GTime last, 
              gint visits);
static HistoryItem *
history_get_host (GlobalHistory *gh, 
              const char *url);
static void
history_item_free (HistoryItem *hi);

/* how often to save the history, in milliseconds */
#define HISTORY_SAVE_INTERVAL (60 * 5 * 1000)

static guint global_history_signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE_WITH_CODE (GlobalHistory, global_history, G_TYPE_OBJECT,
                   G_IMPLEMENT_INTERFACE (GALEON_TYPE_AUTOCOMPLETION_SOURCE,
                                    global_history_autocompletion_source_init)
                   G_IMPLEMENT_INTERFACE (GALEON_TYPE_AUTO_BOOKMARKS_SOURCE,
                                    global_history_auto_bookmarks_source_init));

static void
global_history_autocompletion_source_init (GaleonAutocompletionSourceIface *iface)
{
      iface->foreach = global_history_autocompletion_source_foreach;
      iface->set_basic_key = global_history_autocompletion_source_set_basic_key;
}

static void
global_history_auto_bookmarks_source_init (GaleonAutoBookmarksSourceIface *iface)
{
      iface->get_autobookmarks = global_history_auto_bookmarks_source_get_autobookmarks;
}

static void
global_history_class_init (GlobalHistoryClass *klass)
{
        GObjectClass *object_class = G_OBJECT_CLASS (klass);

        object_class->finalize = global_history_finalize;

      global_history_signals[ADD] =
                g_signal_new ("add",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GlobalHistoryClass, add),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__POINTER,
                              G_TYPE_NONE,
                              1,
                        G_TYPE_POINTER);
      global_history_signals[UPDATE] =
                g_signal_new ("update",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GlobalHistoryClass, update),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__POINTER,
                              G_TYPE_NONE,
                              1,
                        G_TYPE_POINTER);
      global_history_signals[REMOVE] =
                g_signal_new ("remove",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (GlobalHistoryClass, remove),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__POINTER,
                              G_TYPE_NONE,
                              1,
                        G_TYPE_POINTER);

      g_type_class_add_private (klass, sizeof (GlobalHistoryPrivate));
}

/** 
 * Returns the number of days since the history item was visited for the last time
 */
static gint
calculate_age (GTime last)
{
        GTime now = time (NULL);
        gint days = (now - last) / (24 * 60 * 60);
        return days;
}

/**
 * history_parse_host_element: parse <host> elements
 */
static void
history_parse_host_element (GlobalHistory *gh, const xmlChar **attrs)
{
      HistoryItem *host;
      const xmlChar **attr;
      gchar *name = NULL;
      gint css = -1;
      gint zoom = -1;

      /* parse each attribute */
      for (attr = attrs; *attr != NULL; attr += 2)
      {
            if (xmlStrcmp (attr[SAX_KEY], "name") == 0)
            {
                  name = g_strdup (attr[SAX_VALUE]);
            }
            else if (xmlStrcmp (attr[SAX_KEY], "authorcss") == 0)
            {
                  css = atoi (attr[SAX_VALUE]);
            }
            else if (xmlStrcmp (attr[SAX_KEY], "zoom") == 0)
            {
                  zoom = atoi (attr[SAX_VALUE]);
            }
            else
            {
                  g_warning ("Unsupported attribute for "
                           "history.xml::%s: %s", 
                           attr[SAX_KEY], attr[SAX_VALUE]);
            }
      }

      /* create a host structure and store */
      host = history_add_host (gh, name);
      host->priv->zoom = zoom;
      g_free (name);
}

/**
 * history_parse_item_element: parse <item> elements
 */
static void
history_parse_item_element (GlobalHistory *gh, const xmlChar **attrs)
{
      const xmlChar **attr;
      HistoryItem *item;
      gchar *title = NULL;
      gchar *url = NULL;
      GTime first = 0;
      GTime last = 0;
      gint visits = 0;

      /* parse each attribute */
      for (attr = attrs; *attr != NULL; attr += 2)
      {
            if (xmlStrcmp(attr[SAX_KEY], "title") == 0)
            {
                  title = g_strdup (attr[SAX_VALUE]);
            }
            else if (xmlStrcmp(attr[SAX_KEY], "url") == 0)
            {
                  url = g_strdup (attr[SAX_VALUE]);
            }
            else if (xmlStrcmp(attr[SAX_KEY], "first_time") == 0)
            {
                  first = strtol (attr[SAX_VALUE], NULL, 10);
            }
            else if (xmlStrcmp(attr[SAX_KEY], "last_time") == 0)
            {
                  last = strtol (attr[SAX_VALUE], NULL, 10);
            }
            else if (xmlStrcmp(attr[SAX_KEY], "visits") == 0)
            {
                  visits = atoi (attr[SAX_VALUE]);
            }
            else
            {
                  g_warning("Unsupported attribute for "
                          "history.xml::%s: %s", 
                          attr[SAX_KEY], attr[SAX_VALUE]);
            }
      }

      /* check age */
      if (calculate_age (last) <= gh->priv->expire_days)
      {
            /* add to the history */
            item = history_add_item (gh, url, title,
                               first, last, visits);

            g_return_if_fail (item);

            for (attr = attrs; *attr != NULL; attr += 2)
            {
                  if (xmlStrcmp(attr[SAX_KEY], "prop:") == 0)
                  {
                        global_history_set_page_property (gh,
                                                  item,
                                                  &attr[SAX_KEY][5],
                                                  attr[SAX_VALUE]);
                  }
            }
      }
      else
      {
            g_free (url);
            g_free (title);               
      }

}

/**
 * history_start_element: callback called to parse a single element of the
 * history XML file.
 */
static void 
history_start_element (void *ctx, const xmlChar *fullname, 
                   const xmlChar **attrs)
{
      xmlParserCtxtPtr  ctxt = ctx;
      GlobalHistory    *gh   = ctxt->_private;

      /* parse element */
      if (xmlStrcmp (fullname, "host") == 0 && attrs != NULL)
      {
            /* <host> element */
            history_parse_host_element (gh, attrs);
      }
      else if (xmlStrcmp (fullname, "item") == 0 && attrs != NULL)
      {
            /* <item> element */
            history_parse_item_element (gh, attrs);
      }
      else if (xmlStrcmp (fullname, "history") == 0)
      {
            /* we recurse into this automatically */
      }
      else
      {
            g_warning ("unknown history element '%s'\n", fullname);
      }
}

/**
 * history_load: Loads the history from the history file (if it
 * exists). Creates the history dialog. Does not load expired entries
 */
static void
history_load (GlobalHistory *gh)
{
        /* definition of SAX parser for reading documents */
      static xmlSAXHandler parser =
      {
            NULL, /* internalSubset        */
            NULL, /* isStandalone          */
            NULL, /* hasInternalSubset     */
            NULL, /* hasExternalSubset     */
            NULL, /* resolveEntity         */
            NULL, /* getEntity             */
            NULL, /* entityDecl            */
            NULL, /* notationDecl          */
            NULL, /* attributeDecl         */
            NULL, /* elementDecl           */
            NULL, /* unparsedEntityDecl    */
            NULL, /* setDocumentLocator    */
            NULL, /* startDocument         */
            NULL, /* endDocument           */
            (startElementSAXFunc) history_start_element, /* startElement */
            NULL, /* endElement            */
            NULL, /* reference             */
            NULL, /* characters            */
            NULL, /* ignorableWhitespace   */
            NULL, /* processingInstruction */
            NULL, /* comment               */
            NULL, /* warning    (FIXME)    */
            NULL, /* error      (FIXME)    */
            NULL, /* fatalError (FIXME)    */
      };
        gchar *histfile;

      /* build the filename */
      histfile = g_build_filename (g_get_home_dir (), GALEON_DIR, 
                             "history.xml", NULL);
      
      /* build the initial history hash table */
        gh->priv->history_sites = g_hash_table_new_full (g_str_hash, g_str_equal,
                                           NULL, (GDestroyNotify)history_item_free);

      /* build the initial history hosts hashtable */
      gh->priv->history_hosts = g_hash_table_new_full (g_str_hash, g_str_equal,
                                           NULL, (GDestroyNotify)history_item_free);

      /* find out how many days to keep before expiring elements */
      gh->priv->expire_days = eel_gconf_get_integer (CONF_HISTORY_EXPIRE);

      LOG ("Loading history.");
      START_PROFILER ("Loading History");

      /* load the file if we can access it */
      if (access (histfile, F_OK) != -1)
      {
            xmlSubstituteEntitiesDefault (1);
            xmlSAXParseFileWithData (&parser, histfile, TRUE, gh);
      }

      STOP_PROFILER ("Loading History");
      
      /* free allocated strings */
      g_free (histfile);

      LOG ("History loaded: %d sites, %d hosts.", 
           g_hash_table_size (gh->priv->history_sites),
           g_hash_table_size (gh->priv->history_hosts));
}


static void
history_save_item_property (gpointer  key,
                      gpointer  value,
                      gpointer  user_data)
{
      xmlTextWriterPtr writer = (xmlTextWriterPtr)user_data;

      g_return_if_fail (key);
      g_return_if_fail (value);

      /* I'm not convinced that the person that wrote this code
       * originally meant to put the "prop:" bit before the value
       * - crispin 30/03/2004 */
      xmlTextWriterWriteFormatAttribute (writer, (gchar*)key, "prop:%s", (gchar*)value);
}

/**
 * Saves one host of the history
 */
static void 
history_save_host (gpointer key, gpointer value, gpointer user_data)
{
        HistoryItem *hh = (HistoryItem *) value;
      xmlTextWriterPtr writer = (xmlTextWriterPtr)user_data;

      g_return_if_fail (hh->url);

      if (!hh->priv->children) return;

      xmlTextWriterStartElement (writer, "host");
      xmlTextWriterWriteAttribute (writer, "name", hh->url);

      if (hh->priv->zoom > 0)
      {
            xmlTextWriterWriteFormatAttribute (writer, "zoom", "%d", hh->priv->zoom);
      }

      g_hash_table_foreach (hh->priv->properties,
                        (GHFunc)history_save_item_property,
                        writer);
      
      xmlTextWriterEndElement (writer);
}

/**
 * Saves one item of the history
 */
static void 
history_save_item (gpointer key, gpointer value, gpointer user_data)
{
        HistoryItem *hi = (HistoryItem *) value;
      xmlTextWriterPtr writer = (xmlTextWriterPtr)user_data;

      g_return_if_fail (hi->title);
      g_return_if_fail (hi->url);

      xmlTextWriterStartElement (writer, "item");

      xmlTextWriterWriteAttribute (writer, "title", hi->title);
      xmlTextWriterWriteAttribute (writer, "url", hi->url);

      xmlTextWriterWriteFormatAttribute (writer, "first_time", "%d", hi->first);
      xmlTextWriterWriteFormatAttribute (writer, "last_time", "%d", hi->last);
      xmlTextWriterWriteFormatAttribute (writer, "visits", "%d", hi->visits);

      g_hash_table_foreach (hi->priv->properties,
                        (GHFunc)history_save_item_property,
                        writer);
      
      xmlTextWriterEndElement (writer);
}


/**
 * history_save_to_file: Actually do the file save
 */
static int
history_save_to_file (GlobalHistoryPrivate *ghpriv, xmlTextWriterPtr writer)
{
      int ret;

      ret = xmlTextWriterStartDocument (writer, "1.0", NULL, NULL);
      if (ret < 0) return ret;
      ret = xmlTextWriterStartElement (writer, "history");
      if (ret < 0) return ret;

        g_hash_table_foreach (ghpriv->history_hosts, history_save_host, writer);
        g_hash_table_foreach (ghpriv->history_sites, history_save_item, writer);

      ret = xmlTextWriterEndElement (writer); /* root */
      if (ret < 0) return ret;

      ret = xmlTextWriterEndDocument (writer);
      if (ret < 0) return ret;
      
      return ret;
}

/** 
 * history_save: saves the history out to the default XML file
 */
static void 
history_save (GlobalHistoryPrivate *ghpriv)
{
      gchar *histfile, *tmpfile;
      xmlTextWriterPtr writer;
      int ret;

      LOG ("saving history");

      if (!ghpriv->history_dirty)
      {
            return;
      }

      histfile = g_build_filename (g_get_home_dir (),
                             GALEON_DIR,
                             "history.xml",
                             NULL);
      tmpfile = g_strconcat (histfile, ".tmp", NULL);

      writer = xmlNewTextWriterFilename (tmpfile, 0);
      xmlTextWriterSetIndent (writer, 1);
      xmlTextWriterSetIndentString (writer, "  ");

      START_PROFILER ("Saving History");

      ret = history_save_to_file (ghpriv, writer);

      STOP_PROFILER ("Saving History");

      if (ret < 0)
      {
            g_warning( "Failed writing to history.xml" );
      }
      else
      {
            gul_general_switch_temp_file (histfile, tmpfile);
      }

      ghpriv->history_dirty = FALSE;

      xmlFreeTextWriter (writer);
      g_free (histfile);
      g_free (tmpfile);
}

void 
global_history_save_if_needed (GlobalHistory *gh)
{
      g_return_if_fail (IS_GLOBAL_HISTORY (gh));

      history_save (gh->priv);
}

/**
 * history_periodic_save_cb: save the history (if dirty) every once in a while
 */
static gboolean
history_periodic_save_cb (GlobalHistoryPrivate *ghpriv)
{
        /* save it */
        history_save (ghpriv);

        /* call again */
        return TRUE;
}

/**
 * history_item_free: free one history item
 */
static void
history_item_free (HistoryItem *hi)
{
      if (!hi->priv->is_host)
      {
            HistoryItem *host = hi->priv->host;
            if (host) /* I think host can't be NULL, but I'm not sure */
            {
                  host->priv->children = g_slist_remove (host->priv->children, hi);
            }
      }
      
      g_free (hi->url); 
      g_free (hi->title);

      g_hash_table_destroy (hi->priv->properties);

      g_free (hi->priv);
      g_free (hi);
}

static void
global_history_finalize (GObject *object)
{
        GlobalHistory *gh;

        g_return_if_fail (object != NULL);
        g_return_if_fail (IS_GLOBAL_HISTORY (object));

      LOG ("Finalizing GlobalHistory");

      gh = GLOBAL_HISTORY (object);

      /* save the history */
      history_save (gh->priv);

      g_source_remove (gh->priv->timeout);

      /* destroy hosts hash table */
      g_hash_table_destroy (gh->priv->history_sites);
      gh->priv->history_sites = NULL;

      /* destroy sites hash table */
      g_hash_table_destroy (gh->priv->history_hosts);
      gh->priv->history_hosts = NULL;
      gh->priv->last_visited_site = NULL;

        g_return_if_fail (gh->priv != NULL);


        G_OBJECT_CLASS (global_history_parent_class)->finalize (object);
}

static void
global_history_init (GlobalHistory *gh)
{
        gh->priv = GLOBAL_HISTORY_GET_PRIVATE (gh);
      gh->priv->history_sites = NULL;
      gh->priv->history_hosts = NULL;
      gh->priv->history_dirty = FALSE;
      gh->priv->expire_days = 0;

      /* load the history */
      history_load (gh);

      /* setup the periodic history saving callback */
      gh->priv->timeout = g_timeout_add (HISTORY_SAVE_INTERVAL, 
                                 (GSourceFunc)history_periodic_save_cb, 
                                 gh->priv);
}

GlobalHistory *
global_history_new (void)
{
      return GLOBAL_HISTORY (g_object_new 
                         (TYPE_GLOBAL_HISTORY, NULL));
}

static void
history_update_last_visited_site (GlobalHistory *gh, HistoryItem *hi)
{
      if ((gh->priv->last_visited_site == NULL) 
          || (hi->last > gh->priv->last_visited_site->last))
            gh->priv->last_visited_site = hi;
}

/**
 * history_add_host: add an host to the list
 */
static HistoryItem *
history_add_host (GlobalHistory *gh, 
              const char *name)
{
      HistoryItem *host;
      
      g_return_val_if_fail (name, NULL);

      /* allocate */
      host = g_new0 (HistoryItem, 1);

      /* fill in structure */
      host->title = g_strdup (name);
      host->url = g_strdup (name);
      host->first = 0;
      host->last = 0;
      host->visits = 0;
      
      host->priv = g_new0 (HistoryItemPrivate, 1);
      host->priv->children = NULL;
      host->priv->properties = g_hash_table_new_full (g_str_hash, g_str_equal,
                                          g_free, g_free);
      
      /* add to the hosts hashtable */
      g_hash_table_insert (gh->priv->history_hosts, host->url, host);

      /* return completed structure */
      return host;
}

/*
 * history_get_host: get the host folder of the url
 */
static HistoryItem *
history_get_host (GlobalHistory *gh, 
              const char *url)
{
      GnomeVFSURI *vfs_uri = NULL;
      const char *name = NULL;
      HistoryItem *host;

      /* check args */
      g_return_val_if_fail (url, NULL);

      /* check if it's a local file */
      if (!g_ascii_strncasecmp (url, "file://", 7))
      {
            /* if so, put it in a folder named "Local files" */
            name = _("Local files");
      }
      else
      {
            /* parse the url as a GnomeVFS uri */
            vfs_uri = gnome_vfs_uri_new (url);
            if (vfs_uri != NULL)
            {
                  name = gnome_vfs_uri_get_host_name (vfs_uri);
            }

            /* handle failure gracefully */
            if (name == NULL)
            {
                  /* FIXME gnome vfs doesnt handle https correctly */
                  name = _("Other");
            }
      }

      /* lookup in table */
      host = g_hash_table_lookup (gh->priv->history_hosts, name);
      if (host == NULL)
      {
            /* new host */
            host = history_add_host (gh, name);
      }

      /* free uri -- this must be done here since the hostname returned
       * is a constant string and part of the vfs_uri structure */
      if (vfs_uri != NULL)
      {
            gnome_vfs_uri_unref (vfs_uri);
      }

      /* return the appropriate folder */
      return host;
}

/**
 * Adds a new history item to the hashtable
 */
static HistoryItem *
history_add_item (GlobalHistory *gh,
              char *url, 
              char *title, 
              GTime first, 
              GTime last, 
              gint visits)
{
      HistoryItem *hi;
      HistoryItem *host;

      g_return_val_if_fail (IS_GLOBAL_HISTORY (gh), NULL);
      g_return_val_if_fail (url, NULL);
      g_return_val_if_fail (title, NULL);

      /* allocate */
      hi = g_new0 (HistoryItem, 1);

      /* find the parent host */
      host = history_get_host (gh, url);
      g_return_val_if_fail (host, NULL);

      /* fill in the fields */
      hi->url = url;
      hi->title = title;
      hi->first = first;
      hi->last = last;
      hi->visits = visits;
      hi->priv = g_new0 (HistoryItemPrivate, 1);
      hi->priv->children = NULL;
      hi->priv->host = host;
      hi->priv->properties = g_hash_table_new_full (g_str_hash, g_str_equal,
                                          g_free, g_free);
      
      /* update the host */
      host->visits += visits;

      /* first item of host */
      if (!host->first || first < host->first)
      {
            host->first = first;
      }
      
      if (last > host->last)
      {
            host->last = last;
      }
      host->priv->children = g_slist_prepend (host->priv->children, hi);

      /* add to the table */
        g_hash_table_insert (gh->priv->history_sites, 
                       hi->url, hi);

      /* update the last visited item */
      history_update_last_visited_site (gh, hi);

      global_history_emit_changed_data_changed (gh);

      /* return the finished item */
      return hi;
}

void     
global_history_visited (GlobalHistory *gh,
                  const char *url)
{
        HistoryItem *hi;
        GTime now;

      /* check arguments */
      g_return_if_fail (url != NULL);

      /* get current time */
        now = time (NULL);

      /* lookup in history */
        hi = g_hash_table_lookup (gh->priv->history_sites, url);
        if (hi != NULL)
      {
            /* update other info */
                hi->last = now;
                hi->visits++;

            g_return_if_fail (hi->priv->host != NULL);
            
            /* update host data as well */
            hi->priv->host->last = now;
            hi->priv->host->visits++;
            g_signal_emit (G_OBJECT (gh), global_history_signals[UPDATE], 0, hi);
        }
      else
      {
            /* create a new item to add to the view */
            hi = history_add_item (gh,
                               g_strdup (url), 
                               g_strdup (_("Untitled")),
                               now, now, 1);

            if (hi)
            {
                  g_signal_emit (G_OBJECT (gh), global_history_signals[ADD], 0, hi);
            }
        }

      /* we have changed the history, so save at next checkpoint */
      gh->priv->history_dirty = TRUE;
}

gboolean 
global_history_is_visited (GlobalHistory *gh,
                     const char *url)
{
        return (g_hash_table_lookup (gh->priv->history_sites, url) != NULL);
}

/**
 * Helper function to locate the newer HistoryItem
 */
static void 
history_look_for_newer (gpointer key, gpointer value, GlobalHistory *gh)
{
        HistoryItem *hi = (HistoryItem *) value;
      history_update_last_visited_site (gh, hi);
}

const char *
global_history_get_last_page (GlobalHistory *gh)
{
      if (gh->priv->last_visited_site == NULL)
      {
            g_hash_table_foreach (gh->priv->history_sites, 
                              (GHFunc)history_look_for_newer, 
                              gh->priv);
      }

        if (gh->priv->last_visited_site != NULL)
      {
                return gh->priv->last_visited_site->url;
        } 
      else
      {
                return NULL;
        }
}

gboolean 
global_history_set_page_title (GlobalHistory *gh,
                         const char *url, 
                         const char *title)
{
      HistoryItem *hi;

      /* lookup in history */
        hi = g_hash_table_lookup (gh->priv->history_sites, url);

      /* check its there */
      if (hi == NULL)
      {
            /* FIXME: this certainly happens, should we add the
             * URL to the history at this point? I don't know -MattA */
            return FALSE;
      }

        /* free old locale title */
      if (hi->title != NULL)
      {
            g_free (hi->title);
      }

      hi->title = g_strdup (title);

      /* we have changed the history, so save at next checkpoint */
      gh->priv->history_dirty = TRUE;

      g_signal_emit (G_OBJECT (gh), global_history_signals[UPDATE], 0, hi);
      global_history_emit_changed_data_changed (gh);
      
      return TRUE;
}

const char *
global_history_get_page_title (GlobalHistory *gh,
                         const char *url)
{
      HistoryItem *hi;

      hi = g_hash_table_lookup (gh->priv->history_sites, url);

      if (hi) return hi->title;
      else return NULL;
}


gboolean 
global_history_set_host_zoom (GlobalHistory *gh,
                        const char *url, 
                        gint zoom)
{
      HistoryItem *hi;
      HistoryItem *hh;

        hi = g_hash_table_lookup (gh->priv->history_sites, url);

      if (hi == NULL)
      {
            return FALSE;
      }

      hh = hi->priv->host;

      hh->priv->zoom = zoom;

      /* we have changed the history, so save at next checkpoint */
      gh->priv->history_dirty = TRUE;

      g_signal_emit (G_OBJECT (gh), global_history_signals[UPDATE], 0, hh);
      global_history_emit_changed_data_changed (gh);
      
      return TRUE;
}

/*
 * history_get_host_zoom: gets the zoom setting for the host refernced in
 * the url.
 *
 * Returns -1 if there is no zoom set for the host.
 */
gint
global_history_get_host_zoom (GlobalHistory *gh,
                        const char *url)
{
      HistoryItem *hi;
      HistoryItem *hh;

      hi = g_hash_table_lookup (gh->priv->history_sites, url);

      if (!hi || !hi->priv->host) return -1;

      hh = hi->priv->host;

      return hh->priv->zoom;
}


gboolean 
global_history_remove_url (GlobalHistory *gh,
                     const char *url)
{
        HistoryItem *hi;

      /* check arguments */
      g_return_val_if_fail (url != NULL, FALSE);
      
       /* lookup in history */
        hi = g_hash_table_lookup (gh->priv->history_sites, url);

      if (hi)
      {     
            /* if this was the last visited item, make it NULL.
             * It will be recalculated when needed */
            if (hi == gh->priv->last_visited_site)
            {
                  gh->priv->last_visited_site = NULL;
            }

            g_signal_emit (G_OBJECT (gh), global_history_signals[REMOVE], 0, hi);
            g_hash_table_remove (gh->priv->history_sites, url);

            global_history_emit_changed_data_changed (gh);
            gh->priv->history_dirty = TRUE;
            return FALSE;
      }
      
      return FALSE;
}

void
global_history_clear (GlobalHistory *gh)
{
      GlobalHistoryPrivate *p = gh->priv;
      GHashTable *old_history_sites = p->history_sites;
      GHashTable *old_history_hosts = p->history_hosts;

      /* no last_visited_site */
      p->last_visited_site = NULL;

      p->history_hosts = g_hash_table_new_full (g_str_hash, g_str_equal,
                                      NULL, (GDestroyNotify)history_item_free);
      p->history_sites = g_hash_table_new_full (g_str_hash, g_str_equal,
                                      NULL, (GDestroyNotify)history_item_free);

      global_history_emit_changed_data_changed (gh);

      /* clear the file */
      gh->priv->history_dirty = TRUE;
      history_save (gh->priv);

      /* Destroy old history hash table */
      g_hash_table_destroy (old_history_sites);

      /* destroy old hosts hash table */
      g_hash_table_destroy (old_history_hosts);
}

/**
 * history_add_host_to_list: callback to add server structure to a list
 */
static void 
history_add_host_to_list (gpointer key, HistoryItem *host, GList **list)
{
      /* add it to the list */
      *list = g_list_prepend (*list, host);
}

/**
 * global_history_get_host_list: get a fresh linked list of all the hosts
 */
GList *
global_history_get_host_list (GlobalHistory *gh)
{
      GList *list = NULL;

      g_hash_table_foreach (gh->priv->history_hosts, 
                        (GHFunc)history_add_host_to_list, &list);

      return list;
}

static void 
history_add_item_to_slist (gpointer key, HistoryItem *item, GSList **list)
{
      *list = g_slist_prepend (*list, item);
}

static GSList *
global_history_get_sites_slist (GlobalHistory *gh)
{
      GSList *list = NULL;

      g_hash_table_foreach (gh->priv->history_sites, 
                        (GHFunc) history_add_item_to_slist, &list);

      return list;
}


void 
global_history_set_page_property  (GlobalHistory *gh,
                           HistoryItem *hi,
                           const char *key,
                           const char *value)
{
      g_hash_table_replace (hi->priv->properties,
                        (gpointer)g_strdup(key),
                        (gpointer)g_strdup(value));
}

char * 
global_history_get_page_property  (GlobalHistory *gh,
                           HistoryItem *hi,
                           const gchar *key)
{
      return g_hash_table_lookup (hi->priv->properties,
                            (gpointer)key);
}

static gboolean
filter_by_word (const char *url, 
            const char *word)
{
      return (g_strrstr (url, word) != NULL);
}

static gboolean
filter_by_date (int filter_type,
            GTime atime)
{
        GDate date, current_date;
        gboolean result;

        g_date_clear (&current_date, 1);
        g_date_set_time (&current_date, time (NULL));

        g_date_clear (&date, 1);
        g_date_set_time (&date, atime);

        switch (filter_type)
        {
                /* Always */
        case 0:
                return TRUE;
                /* Today */
        case 1:
                break;
                /* Last two days */
        case 2:
                g_date_subtract_days (&current_date, 1);
                break;
                /* Last three days */
        case 3:
                g_date_subtract_days (&current_date, 2);
                break;
                /* Week */
        case 4:
                g_date_subtract_days (&current_date, 7);
                break;
                /* Month */
        case 5:
                g_date_subtract_months (&current_date, 1);
                break;
        default:
                break;
        }

        result = (g_date_compare (&date, &current_date) >= 0);

        return result;
}

static void 
history_add_url_to_list (HistoryItem *item,
                   HistoryFilter *filter,
                   GList **list)
{
      gboolean add = TRUE;
      
      if (filter != NULL)
      {
            add = ((filter_by_date (filter->type, item->last)) &&
                   (filter_by_word (item->url, filter->word)));
      }
      
      /* add it to the list */
      if (add)
      {
            *list = g_list_prepend (*list, item);
      }
}

GList *
global_history_get_urls_list (GlobalHistory *gh,
                              HistoryItem *item,
                        HistoryFilter *filter)
{
      GList *list = NULL;
      GSList *l;
      
      g_return_val_if_fail (item != NULL, NULL);
      g_return_val_if_fail (item->priv != NULL, NULL);

      for (l = item->priv->children; l != NULL; l = l->next)
      {
            HistoryItem *item = l->data;
            history_add_url_to_list (item, filter, &list);
      }
      
      return list;
}

HistoryItem *
global_history_get_host_from_site (GlobalHistory *gh,
                           HistoryItem *site)
{
      return site->priv->host;
}

static void 
global_history_autocompletion_source_foreach_aux (gpointer key, gpointer value, gpointer user_data)
{
        HistoryItem *hi = (HistoryItem *) value;
      gpointer *little_hack = user_data;
      ((GaleonAutocompletionSourceForeachFunc) little_hack[1]) 
            (little_hack[0], hi->url, hi->title, 
             /* very simple scoring function. Most recent entries get higher scores and each
                visit makes the item like 1.5 days younger aprox. */
                hi->last + (guint) (hi->visits << 17),
                little_hack[2]);
}

static void
global_history_autocompletion_source_set_basic_key (GaleonAutocompletionSource *source,
                                        const gchar *basic_key)
{
      /* nothing to do here */
}

static void
global_history_autocompletion_source_foreach (GaleonAutocompletionSource *source,
                                    const gchar *current_text,
                                    GaleonAutocompletionSourceForeachFunc func,
                                    gpointer data)
{
      gpointer little_hack[3] = { source, func, data };
      GlobalHistoryPrivate  *p = GLOBAL_HISTORY (source)->priv;
      g_hash_table_foreach (p->history_sites, global_history_autocompletion_source_foreach_aux, 
                        little_hack);
}

static void
global_history_emit_changed_data_changed (GlobalHistory *gh)
{
      g_signal_emit_by_name (gh, "data-changed");
}

/* Autobookmarks */

static int 
autobm_score_both (const HistoryItem *item)
{
      return item->last + (item->visits << 17); 
}

static gint 
autobm_compare_both (const HistoryItem *a, const HistoryItem *b)
{
      int score_a = autobm_score_both (a);
      int score_b = autobm_score_both (b);

      return score_b > score_a ? 1 : -1;
}

static gint 
autobm_compare_recently_visited (const HistoryItem *a, const HistoryItem *b)
{
      int score_a = a->last;
      int score_b = b->last;

      return score_b > score_a ? 1 : -1;
}

static gint 
autobm_compare_frequently_visited (const HistoryItem *a, const HistoryItem *b)
{
      int score_a = a->visits;
      int score_b = b->visits;

      return score_b > score_a ? 1 : -1;
}

static HistoryItem *
global_history_get_host_dominant_item (const HistoryItem *host, 
                               GaleonAutoBookmarksSourceFilterFunc filter, gpointer filter_data, 
                               GCompareFunc compare_func)
{
      const GSList *it;
      host->priv->children = g_slist_sort (host->priv->children, compare_func);

      for (it = host->priv->children; it; it = it->next)
      {
            HistoryItem *ret = it->data;
            if (filter (ret->title, ret->url, filter_data))
            {
                  return ret;
            }
      }
      return NULL;
}

static void
global_history_auto_bookmarks_source_get_autobookmarks      (GaleonAutoBookmarksSource *source, 
                                           GaleonAutoBookmarksSourceIteratorFunc iterator,
                                           gpointer iterator_data,
                                           GaleonAutoBookmarksSourceFilterFunc filter,
                                           gpointer filter_data,
                                           GaleonAutoBookmarksScoringMethod scoring,
                                           gboolean group_by_host)
{
      GlobalHistory *gh = GLOBAL_HISTORY (source);
      GList *host_list;
      const GList *it;
      GSList *candidates = NULL;
      const GSList *sit;
      gboolean cont;
      GCompareFunc compare_func = 
            scoring == GALEON_AUTO_BOOKMARKS_SCORING_RECENTLY_VISITED ? (GCompareFunc) autobm_compare_recently_visited
            : scoring == GALEON_AUTO_BOOKMARKS_SCORING_FRECUENTLY_VISITED ? (GCompareFunc) autobm_compare_frequently_visited
            : (GCompareFunc) autobm_compare_both;

      START_PROFILER ("history_generate_autobookmarks");

      if (group_by_host)
      {
           START_PROFILER ("history_generate_autobookmarks updating dominant_item");
           host_list = global_history_get_host_list (gh);
           for (it = host_list; it; it = it->next)
           {
              HistoryItem *host = it->data;
              HistoryItem *di = global_history_get_host_dominant_item (host, filter, filter_data, compare_func);
              if (di)
              {
                    candidates = g_slist_prepend (candidates, di);
              }
           }
           g_list_free (host_list);
           STOP_PROFILER ("history_generate_autobookmarks updating dominant_item");
      }
      else
      {
           START_PROFILER ("history_generate_autobookmarks getting list");
           candidates = global_history_get_sites_slist (gh);
           STOP_PROFILER ("history_generate_autobookmarks getting list");
      }

      START_PROFILER ("history_generate_autobookmarks sorting");
      candidates = g_slist_sort (candidates, compare_func);
      STOP_PROFILER ("history_generate_autobookmarks sorting");

      cont = TRUE;
      for (sit = candidates; sit && cont; sit = sit->next)
      {
            HistoryItem *hi = sit->data;

            if (group_by_host /* already filtered */
                || filter (hi->title, hi->url, filter_data))
            {
                  /* g_print ("AB: %s - %s\n", hi->title, hi->url); */
                  cont = iterator (hi->title, hi->url, iterator_data);
            }
      }

      g_slist_free (candidates);

      STOP_PROFILER ("history_generate_autobookmarks");
}


Generated by  Doxygen 1.6.0   Back to index