aboutsummaryrefslogblamecommitdiffstats
path: root/embed/ephy-history.c
blob: 90b0fb6f0c1890487fea2c6f9a13b3173eca7b32 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  
                                                 













                                                                              

        

   
                   
 

                              
                       
                         
                             

                                 


                   
                       

                                      
                                               
                                      
 
                                                    
                                             
 
                                                                               
                                     
 

                                                                                                                       


                         
                       



                               
                               
                             
                                
                       







                                          



    
                
                


                   
                                          
 


                                                                  
 
                                         



                            
                              
 
                                   













                                                                 


                                                             

         








                                                    

                                         













                                                                 
                                                     














                                                                                        
                                                     






                                                                            


           






                                                            











                                                                                   








                                                                          
 









                                                                          
                                                                            

 




























                                                            
                                                      






                                                          
                                              



                 


                                   
                
 





                                            
                                 
 




                                                  

                                    

                                    
 
                    



                                         






                                

                               
                                                  
                                                                                                          
                                    




                                  
                                  

                                  

                               
                                                  
                                                                                                  


           

                                  
                                    





                                  



                                

                               


                                                                                                          




                                  
                                  

                                  

                               

                                                                                                  

 


                                  
                                    




                                  








                                   





                                                        


















                                                               












                                                                             
               
                              
 

                            
                            

                                                            




                                                      
 

                                           







                                                                    
                 
         


                                                             

                                        









                                           
                                             
         

                                                        



           

















                                                       









                                                                                      



                                                                                      

 
           









                                                                                  

                                   
                              
                       
 
                                                 
                                        
                                 
 
                                                     

                          



                                                                  

                                                               

                                                              
 
                   
                                                                    









                                                             
                                                         



                                                             







                                                                             



                                                                             

                   
                                                                    







                                                                             



                                                                             
 

                                                               


                                                                      
 



                                                                      


                                




                                                        




                                                                          




                                       
                                                
 




                                                              




                                          

                                      
                                                    
                                                    
 

                                                     

                                                                              

                                    

                                         





                                                         
                                                                     

 
           






                                           

                            

















                                                                     
                 
                                                       
 










                                                        
                                    
                              
                                     
                                         
                              

                                  
 
                                                 
 




                                       
                          








                                                                  
                                





                                                                          
                                              

                                             

                                                                       


            

                               
 
                                               

                                                                          
 







                                                                                 

                                                                           
                             
                                                                          

         

                                                            





                                                                 


                  
                                                    



                                                                                              



                                                                                              
 






                                                                        
                                                                          












                                                                              
                                                  





                                              

                                                             










                                                      
                    


                          
                                
 
























                                                                        


                                                                                 



                                                                             

         

                                   
                                                                






                                              
                       

                                               





                                                           










                                       




                                       






                                                
                                          








                                                                 
                                              


















                                                                
                                                               
















                                                         


                              
 

                                                      
                                               
                                 
 




                                                          

 



















                                                                                 







                                        
 










                                                                 




                                    
                       
 



                                                         
                                                                             
         

                                       
                               



                                                                      





















                                                                    







                                                                
/*
 *  Copyright (C) 2002, 2003 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.
 *
 *  $Id$
 */

#include "config.h"

#include "ephy-history.h"
#include "ephy-file-helpers.h"
#include "ephy-debug.h"
#include "ephy-node-db.h"
#include "ephy-node-common.h"
#include "eel-gconf-extensions.h"
#include "ephy-prefs.h"

#include <time.h>
#include <string.h>
#include <glib/gi18n.h>
#include <libgnomevfs/gnome-vfs-uri.h>

#define EPHY_HISTORY_XML_ROOT    "ephy_history"
#define EPHY_HISTORY_XML_VERSION "1.0"

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

/* if you change this remember to change also the user interface description */
#define HISTORY_PAGE_OBSOLETE_DAYS 10

#define EPHY_HISTORY_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_HISTORY, EphyHistoryPrivate))

struct EphyHistoryPrivate
{
    char *xml_file;
    EphyNodeDb *db;
    EphyNode *hosts;
    EphyNode *pages;
    EphyNode *last_page;
    GHashTable *hosts_hash;
    GHashTable *pages_hash;
    int autosave_timeout;
    guint update_hosts_idle;
    gboolean dirty;
    gboolean enabled;
    guint disable_history_notifier_id;
};

enum
{
    PROP_0,
    PROP_ENABLED
};

enum
{
    VISITED,
    CLEARED,
        LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

static void ephy_history_class_init (EphyHistoryClass *klass);
static void ephy_history_init       (EphyHistory *history);
static void ephy_history_finalize   (GObject *object);

static GObjectClass *parent_class = NULL;

GType
ephy_history_get_type (void)
{
        static GType type = 0;

        if (G_UNLIKELY (type == 0))
        {
                static const GTypeInfo our_info =
                {
                        sizeof (EphyHistoryClass),
                        NULL, /* base_init */
                        NULL, /* base_finalize */
                        (GClassInitFunc) ephy_history_class_init,
                        NULL,
                        NULL, /* class_data */
                        sizeof (EphyHistory),
                        0, /* n_preallocs */
                        (GInstanceInitFunc) ephy_history_init
                };

                type = g_type_register_static (G_TYPE_OBJECT,
                           "EphyHistory",
                           &our_info, 0);
        }

        return type;
}

static void
ephy_history_set_enabled (EphyHistory *history,
              gboolean enabled)
{
    LOG ("ephy_history_set_enabled %d", enabled)

    history->priv->enabled = enabled;

    ephy_node_db_set_immutable (history->priv->db, !enabled);

    if (enabled == FALSE)
    {
        ephy_history_clear (history);
    }
}

static void
ephy_history_set_property (GObject *object,
               guint prop_id,
               const GValue *value,
               GParamSpec *pspec)
{
    EphyHistory *history = EPHY_HISTORY (object);

    switch (prop_id)
    {
        case PROP_ENABLED:
            ephy_history_set_enabled (history, g_value_get_boolean (value));
            break;
    }
}

static void
ephy_history_get_property (GObject *object,
               guint prop_id,
               GValue *value,
               GParamSpec *pspec)
{
    EphyHistory *history = EPHY_HISTORY (object);

    switch (prop_id)
    {
        case PROP_ENABLED:
            g_value_set_boolean (value, history->priv->enabled);
            break;
        }
}

static void
ephy_history_class_init (EphyHistoryClass *klass)
{
        GObjectClass *object_class = G_OBJECT_CLASS (klass);

        parent_class = g_type_class_peek_parent (klass);

        object_class->finalize = ephy_history_finalize;
    object_class->get_property = ephy_history_get_property;
    object_class->set_property = ephy_history_set_property;

    g_object_class_install_property (object_class,
                     PROP_ENABLED,
                     g_param_spec_boolean ("enabled",
                                   "Enabled",
                                   "Enabled",
                                   TRUE,
                                   G_PARAM_READWRITE));

    signals[VISITED] =
                g_signal_new ("visited",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (EphyHistoryClass, visited),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__STRING,
                              G_TYPE_NONE,
                              1,
                  G_TYPE_STRING);

    signals[CLEARED] =
                g_signal_new ("cleared",
                              G_OBJECT_CLASS_TYPE (object_class),
                              G_SIGNAL_RUN_FIRST,
                              G_STRUCT_OFFSET (EphyHistoryClass, cleared),
                              NULL, NULL,
                              g_cclosure_marshal_VOID__VOID,
                              G_TYPE_NONE,
                              0);

    g_type_class_add_private (object_class, sizeof(EphyHistoryPrivate));
}

static gboolean
page_is_obsolete (EphyNode *node, GDate *now)
{
    int last_visit;
    GDate date;

    last_visit = ephy_node_get_property_int
        (node, EPHY_NODE_PAGE_PROP_LAST_VISIT);

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

    return (g_date_days_between (&date, now) >=
        HISTORY_PAGE_OBSOLETE_DAYS);
}

static void
remove_obsolete_pages (EphyHistory *eb)
{
    GPtrArray *children;
    int i;
    GTime now;
    GDate current_date;

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

    children = ephy_node_get_children (eb->priv->pages);
    for (i = (int) children->len - 1; i >= 0; i--)
    {
        EphyNode *kid;

        kid = g_ptr_array_index (children, i);

        if (page_is_obsolete (kid, &current_date))
        {
            ephy_node_unref (kid);
        }
    }
}

static void
ephy_history_save (EphyHistory *eb)
{
    int ret;

    /* only save if there are changes */
    if (eb->priv->dirty == FALSE)
    {
        return;
    }

    LOG ("Saving history db")

    ret = ephy_node_db_write_to_xml_safe
        (eb->priv->db, eb->priv->xml_file,
         EPHY_HISTORY_XML_ROOT,
         EPHY_HISTORY_XML_VERSION,
         NULL, /* comment */
         eb->priv->hosts,
         1, eb->priv->pages,
         eb->priv->pages, 0,
         NULL);

    if (ret >=0)
    {
        /* save was successful */
        eb->priv->dirty = FALSE;
    }
}

static void
hosts_added_cb (EphyNode *node,
            EphyNode *child,
            EphyHistory *eb)
{
    eb->priv->dirty = TRUE;

    g_hash_table_insert (eb->priv->hosts_hash,
                 (char *) ephy_node_get_property_string (child, EPHY_NODE_PAGE_PROP_LOCATION),
                 child);
}

static void
hosts_removed_cb (EphyNode *node,
          EphyNode *child,
          guint old_index,
          EphyHistory *eb)
{
    eb->priv->dirty = TRUE;

    g_hash_table_remove (eb->priv->hosts_hash,
                 ephy_node_get_property_string (child, EPHY_NODE_PAGE_PROP_LOCATION));
}

static void
hosts_changed_cb (EphyNode *node,
          EphyNode *child,
          guint property_id,
          EphyHistory *eb)
{
    eb->priv->dirty = TRUE;
}

static void
pages_added_cb (EphyNode *node,
            EphyNode *child,
            EphyHistory *eb)
{
    eb->priv->dirty = TRUE;

    g_hash_table_insert (eb->priv->pages_hash,
                 (char *) ephy_node_get_property_string (child, EPHY_NODE_PAGE_PROP_LOCATION),
                 child);
}

static void
pages_removed_cb (EphyNode *node,
          EphyNode *child,
          guint old_index,
          EphyHistory *eb)
{
    eb->priv->dirty = TRUE;

    g_hash_table_remove (eb->priv->pages_hash,
                 ephy_node_get_property_string (child, EPHY_NODE_PAGE_PROP_LOCATION));
}

static void
pages_changed_cb (EphyNode *node,
          EphyNode *child,
          guint property_id,
          EphyHistory *eb)
{
    eb->priv->dirty = TRUE;
}

static gboolean
periodic_save_cb (EphyHistory *eh)
{
    remove_obsolete_pages (eh);
    ephy_history_save (eh);

    return TRUE;
}

static void
update_host_on_child_remove (EphyNode *node)
{
    GPtrArray *children;
    int i, host_last_visit, new_host_last_visit = 0;

    host_last_visit = ephy_node_get_property_int
            (node, EPHY_NODE_PAGE_PROP_LAST_VISIT);

    children = ephy_node_get_children (node);
    for (i = 0; i < children->len; i++)
    {
        EphyNode *kid;
        int last_visit;

        kid = g_ptr_array_index (children, i);

        last_visit = ephy_node_get_property_int
                        (kid, EPHY_NODE_PAGE_PROP_LAST_VISIT);

        if (last_visit > new_host_last_visit)
        {
            new_host_last_visit = last_visit;
        }
    }

    if (host_last_visit != new_host_last_visit)
    {
        GValue value = { 0, };

        g_value_init (&value, G_TYPE_INT);
        g_value_set_int (&value, new_host_last_visit);
        ephy_node_set_property (node, EPHY_NODE_PAGE_PROP_LAST_VISIT,
                        &value);
        g_value_unset (&value);
    }
}

static gboolean
update_hosts (EphyHistory *eh)
{
    GPtrArray *children;
    int i;
    GList *empty = NULL;

    children = ephy_node_get_children (eh->priv->hosts);
    for (i = 0; i < children->len; i++)
    {
        EphyNode *kid;

        kid = g_ptr_array_index (children, i);

        if (kid != eh->priv->pages)
        {
            if (ephy_node_get_n_children (kid) > 0)
            {
                update_host_on_child_remove (kid);
            }
            else
            {
                empty = g_list_prepend (empty, kid);
            }
        }
    }

    g_list_foreach (empty, (GFunc)ephy_node_unref, NULL);
    g_list_free (empty);

    eh->priv->update_hosts_idle = 0;

    return FALSE;
}

static void
page_removed_from_host_cb (EphyNode *node,
                   EphyNode *child,
                   guint old_index,
                   EphyHistory *eb)
{
    if (eb->priv->update_hosts_idle == 0)
    {
        eb->priv->update_hosts_idle = g_idle_add
            ((GSourceFunc)update_hosts, eb);
    }
}

static void
remove_pages_from_host_cb (EphyNode *host,
               EphyHistory *eh)
{
    GPtrArray *children;
    EphyNode *site;
    int i;

    children = ephy_node_get_children (host);

    for (i = (int) children->len - 1; i >= 0; i--)
    {
        site = g_ptr_array_index (children, i);

        ephy_node_unref (site);
    }
}

static void
connect_page_removed_from_host (char *url,
                                EphyNode *node,
                                EphyHistory *eb)
{
    if (node == eb->priv->pages) return;

    ephy_node_signal_connect_object (node,
                     EPHY_NODE_CHILD_REMOVED,
                         (EphyNodeCallback) page_removed_from_host_cb,
                     G_OBJECT (eb));
    ephy_node_signal_connect_object (node,
                     EPHY_NODE_DESTROY,
                     (EphyNodeCallback) remove_pages_from_host_cb,
                     G_OBJECT (eb));
}

static void
disable_history_notifier (GConfClient *client,
              guint cnxn_id,
              GConfEntry *entry,
              EphyHistory *history)
{
    ephy_history_set_enabled
        (history, !eel_gconf_get_boolean (CONF_LOCKDOWN_DISABLE_HISTORY));
}

static void
ephy_history_init (EphyHistory *eb)
{
    GValue value = { 0, };
    EphyNodeDb *db;

        eb->priv = EPHY_HISTORY_GET_PRIVATE (eb);
    eb->priv->update_hosts_idle = 0;
    eb->priv->enabled = TRUE;

    db = ephy_node_db_new (EPHY_NODE_DB_HISTORY);
    eb->priv->db = db;

    eb->priv->xml_file = g_build_filename (ephy_dot_dir (),
                           "ephy-history.xml",
                           NULL);

    eb->priv->pages_hash = g_hash_table_new (g_str_hash,
                                      g_str_equal);
    eb->priv->hosts_hash = g_hash_table_new (g_str_hash,
                                     g_str_equal);

    /* Pages */
    eb->priv->pages = ephy_node_new_with_id (db, PAGES_NODE_ID);
    g_value_init (&value, G_TYPE_STRING);
    g_value_set_string (&value, _("All"));
    ephy_node_set_property (eb->priv->pages,
                    EPHY_NODE_PAGE_PROP_LOCATION,
                    &value);
    ephy_node_set_property (eb->priv->pages,
                    EPHY_NODE_PAGE_PROP_TITLE,
                    &value);
    g_value_unset (&value);
    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, EPHY_NODE_ALL_PRIORITY);
    ephy_node_set_property (eb->priv->pages,
                    EPHY_NODE_PAGE_PROP_PRIORITY,
                    &value);
    g_value_unset (&value);
    ephy_node_signal_connect_object (eb->priv->pages,
                     EPHY_NODE_CHILD_ADDED,
                         (EphyNodeCallback) pages_added_cb,
                     G_OBJECT (eb));
    ephy_node_signal_connect_object (eb->priv->pages,
                     EPHY_NODE_CHILD_REMOVED,
                         (EphyNodeCallback) pages_removed_cb,
                     G_OBJECT (eb));
    ephy_node_signal_connect_object (eb->priv->pages,
                     EPHY_NODE_CHILD_CHANGED,
                         (EphyNodeCallback) pages_changed_cb,
                     G_OBJECT (eb));

    /* Hosts */
    eb->priv->hosts = ephy_node_new_with_id (db, HOSTS_NODE_ID);
    ephy_node_signal_connect_object (eb->priv->hosts,
                     EPHY_NODE_CHILD_ADDED,
                         (EphyNodeCallback) hosts_added_cb,
                     G_OBJECT (eb));
    ephy_node_signal_connect_object (eb->priv->hosts,
                     EPHY_NODE_CHILD_REMOVED,
                         (EphyNodeCallback) hosts_removed_cb,
                     G_OBJECT (eb));
    ephy_node_signal_connect_object (eb->priv->hosts,
                     EPHY_NODE_CHILD_CHANGED,
                         (EphyNodeCallback) hosts_changed_cb,
                     G_OBJECT (eb));

    ephy_node_add_child (eb->priv->hosts, eb->priv->pages);

    ephy_node_db_load_from_file (eb->priv->db, eb->priv->xml_file,
                     EPHY_HISTORY_XML_ROOT,
                     EPHY_HISTORY_XML_VERSION);

    g_hash_table_foreach (eb->priv->hosts_hash,
                  (GHFunc) connect_page_removed_from_host,
                  eb);

    /* mark as clean */
    eb->priv->dirty = FALSE;

    /* setup the periodic history saving callback */
    eb->priv->autosave_timeout =
        g_timeout_add (HISTORY_SAVE_INTERVAL,
               (GSourceFunc)periodic_save_cb,
               eb);

    disable_history_notifier (NULL, 0, NULL, eb);
    eb->priv->disable_history_notifier_id = eel_gconf_notification_add
        (CONF_LOCKDOWN_DISABLE_HISTORY,
         (GConfClientNotifyFunc) disable_history_notifier, eb);
}

static void
ephy_history_finalize (GObject *object)
{
        EphyHistory *eb = EPHY_HISTORY (object);

    if (eb->priv->update_hosts_idle)
    {
        g_source_remove (eb->priv->update_hosts_idle);
    }

    ephy_history_save (eb);

    ephy_node_unref (eb->priv->pages);
    ephy_node_unref (eb->priv->hosts);

    g_object_unref (eb->priv->db);

    g_hash_table_destroy (eb->priv->pages_hash);
    g_hash_table_destroy (eb->priv->hosts_hash);

    g_source_remove (eb->priv->autosave_timeout);

    eel_gconf_notification_remove (eb->priv->disable_history_notifier_id);

    g_free (eb->priv->xml_file);

    LOG ("Global history finalized");

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

EphyHistory *
ephy_history_new ()
{
    return EPHY_HISTORY (g_object_new (EPHY_TYPE_HISTORY, NULL));
}

static void
ephy_history_host_visited (EphyHistory *eh,
               EphyNode *host,
               GTime now)
{
    GValue value = { 0, };
    int visits;

    LOG ("Host visited")

    visits = ephy_node_get_property_int
        (host, EPHY_NODE_PAGE_PROP_VISITS);
    if (visits < 0) visits = 0;
    visits++;

    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, visits);
    ephy_node_set_property (host, EPHY_NODE_PAGE_PROP_VISITS,
                    &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, now);
    ephy_node_set_property (host, EPHY_NODE_PAGE_PROP_LAST_VISIT,
                    &value);
    g_value_unset (&value);
}

static EphyNode *
ephy_history_add_host (EphyHistory *eh, EphyNode *page)
{
    const char *url;

    url = ephy_node_get_property_string
        (page, EPHY_NODE_PAGE_PROP_LOCATION);

    return ephy_history_get_host (eh, url);
}

EphyNode *
ephy_history_get_host (EphyHistory *eh, const char *url)
{
    GnomeVFSURI *vfs_uri = NULL;
    EphyNode *host = NULL;
    const char *host_name = NULL;
    GList *host_locations = NULL, *l;
    GValue value = { 0, };
    const char *scheme = NULL;
    GTime now;

    g_return_val_if_fail (url != NULL, NULL);

    if (eh->priv->enabled == FALSE)
    {
        return NULL;
    }

    now = time (NULL);

    vfs_uri = gnome_vfs_uri_new (url);

    if (vfs_uri)
    {
        scheme = gnome_vfs_uri_get_scheme (vfs_uri);
        host_name = gnome_vfs_uri_get_host_name (vfs_uri);
    }

    /* Build an host name */
    if (scheme == NULL || host_name == NULL)
    {
        host_name = _("Others");
        host_locations = g_list_append (host_locations,
                        g_strdup ("about:blank"));
    }
    else if (strcmp (scheme, "file") == 0)
    {
        host_name = _("Local files");
        host_locations = g_list_append (host_locations,
                        g_strdup ("file:///"));
    }
    else
    {
        char *location;
        char *tmp;

        location = g_strconcat (scheme,
                    "://", host_name, "/", NULL);
        host_locations = g_list_append (host_locations, location);

        if (g_str_has_prefix (host_name, "www."))
        {
            tmp = g_strdup (g_utf8_offset_to_pointer (host_name, 4));
        }
        else
        {
            tmp = g_strconcat ("www.", host_name, NULL);
        }
        location = g_strconcat (gnome_vfs_uri_get_scheme (vfs_uri),
                    "://", tmp, "/", NULL);
        g_free (tmp);
        host_locations = g_list_append (host_locations, location);
    }

    g_return_val_if_fail (host_locations != NULL, NULL);

    for (l = host_locations; l != NULL; l = l->next)
    {
        host = g_hash_table_lookup (eh->priv->hosts_hash,
                        (char *)l->data);
        if (host) break;
    }

    if (!host)
    {
        host = ephy_node_new (eh->priv->db);
        ephy_node_signal_connect_object (host,
                         EPHY_NODE_CHILD_REMOVED,
                             (EphyNodeCallback) page_removed_from_host_cb,
                         G_OBJECT (eh));
        ephy_node_signal_connect_object (host,
                         EPHY_NODE_DESTROY,
                         (EphyNodeCallback) remove_pages_from_host_cb,
                         G_OBJECT (eh));

        g_value_init (&value, G_TYPE_STRING);
        g_value_set_string (&value, host_name);
        ephy_node_set_property (host, EPHY_NODE_PAGE_PROP_TITLE,
                        &value);
        g_value_unset (&value);

        g_value_init (&value, G_TYPE_STRING);
        g_value_set_string (&value, (char *)host_locations->data);
        ephy_node_set_property (host, EPHY_NODE_PAGE_PROP_LOCATION,
                        &value);
        g_value_unset (&value);

        g_value_init (&value, G_TYPE_INT);
        g_value_set_int (&value, now);
        ephy_node_set_property (host, EPHY_NODE_PAGE_PROP_FIRST_VISIT,
                        &value);
        g_value_unset (&value);

        ephy_node_add_child (eh->priv->hosts, host);
    }

    ephy_history_host_visited (eh, host, now);

    if (vfs_uri)
    {
        gnome_vfs_uri_unref (vfs_uri);
    }

    g_list_foreach (host_locations, (GFunc)g_free, NULL);
    g_list_free (host_locations);

    return host;
}

static void
ephy_history_visited (EphyHistory *eh, EphyNode *node)
{
    GValue value = { 0, };
    GTime now;
    int visits;
    const char *url;
    int host_id;

    now = time (NULL);

    g_assert (node != NULL);

    url = ephy_node_get_property_string
        (node, EPHY_NODE_PAGE_PROP_LOCATION);

    visits = ephy_node_get_property_int
        (node, EPHY_NODE_PAGE_PROP_VISITS);
    if (visits < 0) visits = 0;
    visits++;

    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, visits);
    ephy_node_set_property (node, EPHY_NODE_PAGE_PROP_VISITS,
                    &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, now);
    ephy_node_set_property (node, EPHY_NODE_PAGE_PROP_LAST_VISIT,
                    &value);
    if (visits == 1)
    {
        ephy_node_set_property
            (node, EPHY_NODE_PAGE_PROP_FIRST_VISIT, &value);
    }
    g_value_unset (&value);

    host_id = ephy_node_get_property_int (node, EPHY_NODE_PAGE_PROP_HOST_ID);
    if (host_id >= 0)
    {
        EphyNode *host;

        host = ephy_node_db_get_node_from_id (eh->priv->db, host_id);
        ephy_history_host_visited (eh, host, now);
    }

    eh->priv->last_page = node;

    g_signal_emit (G_OBJECT (eh), signals[VISITED], 0, url);
}

int
ephy_history_get_page_visits (EphyHistory *gh,
                  const char *url)
{
    EphyNode *node;
    int visits = 0;

    node = ephy_history_get_page (gh, url);
    if (node)
    {
        visits = ephy_node_get_property_int
            (node, EPHY_NODE_PAGE_PROP_VISITS);
        if (visits < 0) visits = 0;
    }

    return visits;
}

void
ephy_history_add_page (EphyHistory *eb,
               const char *url)
{
    EphyNode *bm, *node, *host;
    GValue value = { 0, };

    if (eb->priv->enabled == FALSE)
    {
        return;
    }

    node = ephy_history_get_page (eb, url);
    if (node)
    {
        ephy_history_visited (eb, node);
        return;
    }

    bm = ephy_node_new (eb->priv->db);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_string (&value, url);
    ephy_node_set_property (bm, EPHY_NODE_PAGE_PROP_LOCATION,
                    &value);
    ephy_node_set_property (bm, EPHY_NODE_PAGE_PROP_TITLE,
                    &value);
    g_value_unset (&value);

    host = ephy_history_add_host (eb, bm);

    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, ephy_node_get_id (host));
    ephy_node_set_property (bm, EPHY_NODE_PAGE_PROP_HOST_ID,
                    &value);
    g_value_unset (&value);

    ephy_history_visited (eb, bm);

    ephy_node_add_child (host, bm);
    ephy_node_add_child (eb->priv->pages, bm);
}

EphyNode *
ephy_history_get_page (EphyHistory *eb,
               const char *url)
{
    EphyNode *node;

    node = g_hash_table_lookup (eb->priv->pages_hash, url);

    return node;
}

gboolean
ephy_history_is_page_visited (EphyHistory *gh,
                  const char *url)
{
    return (ephy_history_get_page (gh, url) != NULL);
}

void
ephy_history_set_page_title (EphyHistory *gh,
                 const char *url,
                 const char *title)
{
    EphyNode *node;
    GValue value = { 0, };

    LOG ("Set page title")

    if (title == NULL || title[0] == '\0') return;

    node = ephy_history_get_page (gh, url);
    if (node == NULL) return;

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_string (&value, title);
    ephy_node_set_property
        (node, EPHY_NODE_PAGE_PROP_TITLE, &value);
    g_value_unset (&value);
}

const char*
ephy_history_get_icon (EphyHistory *gh,
               const char *url)
{
    EphyNode *node, *host;
    int host_id;

    node = ephy_history_get_page (gh, url);
    if (node == NULL) return NULL;
    
    host_id = ephy_node_get_property_int (node, EPHY_NODE_PAGE_PROP_HOST_ID);
    g_return_val_if_fail (host_id >= 0, NULL);

    host = ephy_node_db_get_node_from_id (gh->priv->db, host_id);
    g_return_val_if_fail (host != NULL, NULL);

    return ephy_node_get_property_string (host, EPHY_NODE_PAGE_PROP_ICON);
}   
    
               
void
ephy_history_set_icon (EphyHistory *gh,
               const char *url,
               const char *icon)
{
    EphyNode *host;

    LOG ("Set host icon")

    host = g_hash_table_lookup (gh->priv->hosts_hash, url);
    if (host)
    {
        GValue value = { 0, };

        g_value_init (&value, G_TYPE_STRING);
        g_value_set_string (&value, icon);
        ephy_node_set_property
            (host, EPHY_NODE_PAGE_PROP_ICON, &value);
        g_value_unset (&value);
    }
}

void
ephy_history_clear (EphyHistory *gh)
{
    EphyNode *node;

    LOG ("clearing history")

    ephy_node_db_set_immutable (gh->priv->db, FALSE);

    while ((node = ephy_node_get_nth_child (gh->priv->pages, 0)) != NULL)
    {
        ephy_node_unref (node);
    }
    ephy_history_save (gh);

    ephy_node_db_set_immutable (gh->priv->db, !gh->priv->enabled);

    g_signal_emit (gh, signals[CLEARED], 0);
}

EphyNode *
ephy_history_get_hosts (EphyHistory *eb)
{
    return eb->priv->hosts;
}

EphyNode *
ephy_history_get_pages (EphyHistory *eb)
{
    return eb->priv->pages;
}

const char *
ephy_history_get_last_page (EphyHistory *gh)
{
    if (gh->priv->last_page == NULL) return NULL;

    return ephy_node_get_property_string
        (gh->priv->last_page, EPHY_NODE_PAGE_PROP_LOCATION);
}

gboolean
ephy_history_is_enabled (EphyHistory *history)
{
    g_return_val_if_fail (EPHY_IS_HISTORY (history), FALSE);

    return history->priv->enabled;
}