aboutsummaryrefslogblamecommitdiffstats
path: root/src/bookmarks/ephy-bookmarks.c
blob: 0043d845aef0fea93b51c2a76f8ee85e52b65de1 (plain) (tree)
1
2
3
  
                                                
                                             













                                                                              

        

   
                   
 
                           
                                         
                              
                             

                         
                       
                                 
                             
                                    
                                  
                                  
                                     


                                 

                   
                       
                                        

                                 
 
                                                   
                                         
                                       
                            
                                         
 

                                                                                                                             
                            
 
                                              
                               

                              
                       
                       
                       


                            
                                 
                                 

                            
                                  
                                                   

  



                             



                                                             



                                                                            
                                                                                           




                                                                  
                            
                   


                       



                                                            





                           








                                                         


                                                                    





                                         
                              
 
                                   












                                                                   



                                                                            
 


                                                               

         
                    


           

                                                
              


                                              
                                                                      



                                                 
                              
 

                                                                            

                                                                                      


         








































                                                                   
         
                                                     
 
                                                            
 
                                                  
 
                                             
         





















                                                                                            
         
 
                                                   







                                                 

                                    







                                               
                                                    



                                         
                                                                                           















                                                                 









                                                                                 

                                                             
                                                                               

                                                                               

                                                                                           
 
                                                                              

 
           

                                       
 
                                 
 
                                      




                                                                                                                




                                                                                       

                                     
                                                           

 










                                                 
                                                                 



                                              













                                                                                    


         










                                                          


                                
                                                                                   


















                                                                   
















                                                                         


                           
                                                                          
 
                                                                                

                                                                  
                                         














                                                                     



                                                                             

                                                      
 
                                        


           













                                                                        
                                                      


















                                                                        
           


                                                
 
                              
                     
 
                                            
         


                                                                            
                                                     
                                                     



                                                                              







































                                                                                      
                                                              


                                                   
                                                                         
                                                   
                                                                   

                                                                                   
                                                                          










                                                                                       


                                                                 
                                                 

 
           

                                                

                             
                                                                                   
 




                                                       

                                                                    

                                                               

                                                        


           


















































                                                                                
                                     
                                      
                                        

                                        




                                                     
                                                               




                                      
                                      

                                        
                                                               

 

                                                               


                            







                                                               
                                                       



                                                        
                                    

                 
 
                     

 
           

                                   
                                   

                                     






                                                  

                                                      
 
                                                         





                                                                         
                                                   
         


           
















                                                                                                  




                                                                  




                                                                         






















                                                                                 


                                       
                       
 
                                                   
 
                                                       
                          
 
                                                               



                                                                    

                                                     


                                                                    
                       
                                                                            
                                             


                                                                                 
                                                         



                                                            







                                                                                 

                      
                                                                          
                                          
                                                         
                                                    

                                                                
                               



                                                                              


                                                  
 
                       
                                                                            
                                             


                                                                                 
                                                                  




                                                            
                                                             




                                                                      
 
                             
                                                                                           
                                             


                                                                                 
                                                                     




                                                            
                                                             





                                                                           

                                                                                      
 





                                                                               

                                                                                               
         









                                                                             
                                                                                
                 

                                                                
                                                       

                 
 




                                                                                   






                                             
                                                    
 

                                                                                       

                                                       




                                                            

                                 
                                                        
                                               




                                                          


                                              
                                                   
 

                                      


                                    
                                    
 



                                                         
                         
 
                          
 
                                                                       
 
                  

 
           
                                                                                       
 
                             
                               

                                                     





                                                       










                                                              
                                                                 

                 

 


                                      
                                    



                              
                                          
 

                                    











                                                                
                                               
                                          
 
                                                      
                                                           
 
                                            



                  












                                                                      
                                                         

 
          

                                                
 

                            
 
                                                            
                                                                 

                                                 

                                                                
         





                                                           
 
                                                                    
                 
                                   
                 
         
 
                    

 









                                           

                                                      







                                                              


















































                                                                                   










                                             








                                                                 
                                                                                         


























































                                                                                                      
                           




                                                      
                                








                                                                 
                                     
                                                    




                                                
                                         

                                
                                       

                     
                             












                                              
                                           
 

                                     





                                                                 
                                          
                                                            



                                                                     




                                                      


















































                                                                                          
    
                                                 
                                                 



                                                             






























                                                                  





                                                    

                            
                               


                                                  
                          

                                          
         
                                                        
                            
         
 
















                                                                      





                                                               
 

                                                                                        
 

                                                                                 


                                    




                    







                                                       




                                               

                                                            
                                                
 

                                                                     
                                                          



                                                             
                                                

                                                                               






                                                 

                                                             
                                                   
 
                                                      

                                                                      
                                                         



                                                             
                                                

                                                                               

 
          





                                                      
















                                                
          





                                                      



                                                                
/*
 *  Copyright (C) 2002-2004 Marco Pesenti Gritti
 *  Copyright (C) 2003, 2004 Christian Persch
 *
 *  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-bookmarks.h"
#include "ephy-bookmarks-type-builtins.h"
#include "ephy-file-helpers.h"
#include "ephy-embed-shell.h"
#include "ephy-shell.h"
#include "ephy-history.h"
#include "ephy-debug.h"
#include "ephy-tree-model-node.h"
#include "ephy-node-common.h"
#include "ephy-bookmarksbar-model.h"
#include "ephy-bookmarks-export.h"
#include "ephy-bookmarks-import.h"
#include "ephy-bookmark-properties.h"
#include "ephy-prefs.h"

#include "eel-gconf-extensions.h"

#include <string.h>
#include <glib/gi18n.h>
#include <libgnomevfs/gnome-vfs-utils.h>
#include <gtk/gtkmessagedialog.h>
#include <gtk/gtkdialog.h>

#define EPHY_BOOKMARKS_XML_ROOT    "ephy_bookmarks"
#define EPHY_BOOKMARKS_XML_VERSION "1.03"
#define BOOKMARKS_SAVE_DELAY (3 * 1000)
#define MAX_FAVORITES_NUM 10
#define UPDATE_URI_DATA_KEY "updated-uri"

#define EPHY_BOOKMARKS_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_BOOKMARKS, EphyBookmarksPrivate))

struct _EphyBookmarksPrivate
{
    EphyBookmarksBarModel *toolbars_model;
    gboolean init_defaults;
    gboolean dirty;
    guint save_timeout_id;
    char *xml_file;
    char *rdf_file;
    EphyNodeDb *db;
    EphyNode *bookmarks;
    EphyNode *keywords;
    EphyNode *favorites;
    EphyNode *notcategorized;
    EphyNode *smartbookmarks;
    EphyNode *lower_fav;
    double lower_score;
    GHashTable *props_dialogs;
    guint disable_bookmark_editing_notifier_id;
};

typedef struct
{
    const char *title;
    const char *location;
} EphyBookmarksBookmarkInfo;

static const EphyBookmarksBookmarkInfo default_bookmarks [] =
{
    /* Translators you should change these links to respect your locale.
     * For instance in .nl these should be
     * "http://www.google.nl" and "http://www.google.nl/search?q=%s"
     */
    { N_("Search the web"), N_("http://www.google.com/search?q=%s&ie=UTF-8&oe=UTF-8") }
};
static int n_default_bookmarks = G_N_ELEMENTS (default_bookmarks);

static const char *default_topics [] =
{
    N_("Entertainment"),
    N_("News"),
    N_("Shopping"),
    N_("Sports"),
    N_("Travel"),
    N_("Work")
};
static int n_default_topics = G_N_ELEMENTS (default_topics);

enum
{
    PROP_0,
    PROP_TOOLBARS_MODEL
};

/* Signals */
enum
{
    TREE_CHANGED,
    LAST_SIGNAL
};

static guint ephy_bookmarks_signals[LAST_SIGNAL] = { 0 };

static void ephy_bookmarks_class_init   (EphyBookmarksClass *klass);
static void ephy_bookmarks_init     (EphyBookmarks *tab);
static void ephy_bookmarks_finalize (GObject *object);

static GObjectClass *parent_class = NULL;

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

        if (G_UNLIKELY (type == 0))
        {
                static const GTypeInfo our_info =
                {
                        sizeof (EphyBookmarksClass),
                        NULL, /* base_init */
                        NULL, /* base_finalize */
                        (GClassInitFunc) ephy_bookmarks_class_init,
                        NULL,
                        NULL, /* class_data */
                        sizeof (EphyBookmarks),
                        0, /* n_preallocs */
                        (GInstanceInitFunc) ephy_bookmarks_init
                };
        volatile GType flags_type; /* work around gcc's optimiser */
      
        /* make sure the flags type is known */
            flags_type = EPHY_TYPE_BOOKMARK_PROPERTY;

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

        return type;
}

static void
ephy_bookmarks_init_defaults (EphyBookmarks *eb)
{
    int i;

    for (i = 0; i < n_default_topics; i++)
    {
        ephy_bookmarks_add_keyword (eb, _(default_topics[i]));
    }

    for (i = 0; i < n_default_bookmarks; i++)
    {
        EphyNode *bmk;

        bmk = ephy_bookmarks_add (eb, _(default_bookmarks[i].title),
                          _(default_bookmarks[i].location));
        ephy_bookmarksbar_model_add_bookmark (eb->priv->toolbars_model, FALSE,
                                ephy_node_get_id (bmk));
    }
}

static char *
get_item_type_forward_cb (EggToolbarsModel *model,
              GdkAtom type,
              EggToolbarsModel *bookmarksbar_model)
{
    char *retval;

    g_signal_emit_by_name (bookmarksbar_model, "get_item_type",
                   type, &retval);

    return retval;
}

static char *
get_item_id_forward_cb (EggToolbarsModel *model,
            const char *type,
            const char *name,
            EggToolbarsModel *bookmarksbar_model)
{
    char *retval;

    g_signal_emit_by_name (bookmarksbar_model, "get_item_id",
                   type, name, &retval);

    return retval;
}

static char *
get_item_data_forward_cb (EggToolbarsModel *model,
              const char *type,
              const char *id,
              EggToolbarsModel *bookmarksbar_model)
{
    char *retval;

    g_signal_emit_by_name (bookmarksbar_model, "get_item_data",
                   type, id, &retval);

    return retval;
}

GObject *
ephy_bookmarks_get_toolbars_model (EphyBookmarks *eb)
{
    g_return_val_if_fail (EPHY_IS_BOOKMARKS (eb), NULL);

    LOG ("ephy_bookmarks_get_toolbars_model");

    if (eb->priv->toolbars_model == NULL)
    {
        GObject *toolbars_model;

        eb->priv->toolbars_model = EPHY_BOOKMARKSBAR_MODEL
            (ephy_bookmarksbar_model_new (eb));

        /* forward those signals, so that bookmarks can also be on the main model */
        toolbars_model = ephy_shell_get_toolbars_model (ephy_shell, FALSE);

        g_signal_connect_after (toolbars_model, "get_item_type",
                    G_CALLBACK (get_item_type_forward_cb),
                    eb->priv->toolbars_model);
        g_signal_connect_after (toolbars_model, "get_item_id",
                    G_CALLBACK (get_item_id_forward_cb),
                    eb->priv->toolbars_model);
        g_signal_connect_after (toolbars_model, "get_item_data",
                    G_CALLBACK (get_item_data_forward_cb),
                    eb->priv->toolbars_model);

        if (eb->priv->init_defaults)
        {
            ephy_bookmarks_init_defaults (eb);
        }
    }

    return G_OBJECT (eb->priv->toolbars_model);
}

static void
ephy_bookmarks_set_property (GObject *object,
                             guint prop_id,
                             const GValue *value,
                             GParamSpec *pspec)
{
    /* no writable properties */
    g_assert_not_reached ();
}

static void
ephy_bookmarks_get_property (GObject *object,
                             guint prop_id,
                             GValue *value,
                             GParamSpec *pspec)
{
    EphyBookmarks *eb = EPHY_BOOKMARKS (object);

    switch (prop_id)
    {
        case PROP_TOOLBARS_MODEL:
            g_value_set_object (value, ephy_bookmarks_get_toolbars_model (eb));
            break;
    }
}


static void
ephy_bookmarks_class_init (EphyBookmarksClass *klass)
{
        GObjectClass *object_class = G_OBJECT_CLASS (klass);

        parent_class = g_type_class_peek_parent (klass);

        object_class->finalize = ephy_bookmarks_finalize;
    object_class->set_property = ephy_bookmarks_set_property;
    object_class->get_property = ephy_bookmarks_get_property;

    ephy_bookmarks_signals[TREE_CHANGED] =
        g_signal_new ("tree_changed",
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (EphyBookmarksClass, tree_changed),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE,
                  0);

    g_object_class_install_property (object_class,
                                         PROP_TOOLBARS_MODEL,
                                         g_param_spec_object ("toolbars-model",
                                                              "Toolbars model",
                                                              "Toolbars model",
                                                              EPHY_TYPE_BOOKMARKSBAR_MODEL,
                                                              G_PARAM_READABLE));

    g_type_class_add_private (object_class, sizeof(EphyBookmarksPrivate));
}

static void
ephy_bookmarks_save (EphyBookmarks *eb)
{

    LOG ("Saving bookmarks");

    ephy_node_db_write_to_xml_safe
        (eb->priv->db,
         (xmlChar *) eb->priv->xml_file,
         (xmlChar *) EPHY_BOOKMARKS_XML_ROOT,
         (xmlChar *) EPHY_BOOKMARKS_XML_VERSION,
         (xmlChar *) "Do not rely on this file, it's only for internal use. Use bookmarks.rdf instead.",
         eb->priv->keywords,
         3, eb->priv->bookmarks, eb->priv->favorites, eb->priv->notcategorized,
         eb->priv->bookmarks,
         0,
         NULL);

    /* Export bookmarks in rdf */
    ephy_bookmarks_export_rdf (eb, eb->priv->rdf_file);
}

static gboolean
save_bookmarks_delayed (EphyBookmarks *bookmarks)
{
    ephy_bookmarks_save (bookmarks);
    bookmarks->priv->dirty = FALSE;
    bookmarks->priv->save_timeout_id = 0;

    return FALSE;
}

static void
ephy_bookmarks_save_delayed (EphyBookmarks *bookmarks, int delay)
{
    if (!bookmarks->priv->dirty)
    {
        bookmarks->priv->dirty = TRUE;

        if (delay > 0)
        {
            bookmarks->priv->save_timeout_id =
                g_timeout_add (BOOKMARKS_SAVE_DELAY,
                               (GSourceFunc) save_bookmarks_delayed,
                           bookmarks);
        }
        else
        {
            bookmarks->priv->save_timeout_id =
                g_idle_add ((GSourceFunc) save_bookmarks_delayed,
                        bookmarks);
        }
    }
}

static double
get_history_item_score (EphyHistory *eh, const char *page)
{
    return ephy_history_get_page_visits (eh, page);
}

static EphyNode *
compute_lower_fav (EphyNode *favorites, double *score)
{
    GPtrArray *children;
    int i;
    EphyHistory *history;
    EphyNode *result = NULL;

    history = EPHY_HISTORY (ephy_embed_shell_get_global_history (embed_shell));

    *score = DBL_MAX;
    children = ephy_node_get_children (favorites);
    for (i = 0; i < children->len; i++)
    {
        const char *url;
        EphyNode *child;
        double item_score;

        child = g_ptr_array_index (children, i);
        url = ephy_node_get_property_string
            (child, EPHY_NODE_BMK_PROP_LOCATION);
        item_score = get_history_item_score (history, url);
        if (*score > item_score)
        {
            *score = item_score;
            result = child;
        }
    }

    if (result == NULL) *score = 0;

    return result;
}

static void
ephy_bookmarks_update_favorites (EphyBookmarks *eb)
{
    eb->priv->lower_fav = compute_lower_fav (eb->priv->favorites,
                         &eb->priv->lower_score);
}

static gboolean
add_to_favorites (EphyBookmarks *eb, EphyNode *node, EphyHistory *eh)
{
    const char *url;
    gboolean full_menu;
    double score;

    if (ephy_node_has_child (eb->priv->favorites, node)) return FALSE;

    url = ephy_node_get_property_string (node, EPHY_NODE_BMK_PROP_LOCATION);
    score = get_history_item_score (eh, url);
    full_menu = ephy_node_get_n_children (eb->priv->favorites)
            >= MAX_FAVORITES_NUM;
    if (full_menu && score < eb->priv->lower_score) return FALSE;

    if (eb->priv->lower_fav && full_menu)
    {
        ephy_node_remove_child (eb->priv->favorites,
                        eb->priv->lower_fav);
    }

    ephy_node_add_child (eb->priv->favorites, node);
    ephy_bookmarks_update_favorites (eb);

    return TRUE;
}

static void
history_site_visited_cb (EphyHistory *gh, const char *url, EphyBookmarks *eb)
{
    EphyNode *node;

    node = ephy_bookmarks_find_bookmark (eb, url);
    if (node == NULL) return;

    add_to_favorites (eb, node, gh);
}

static void
clear_favorites (EphyBookmarks *bookmarks)
{
    EphyNode *node;
    GPtrArray *children;
    int i;
    gboolean was_immutable;

    /* clear the favourites */

    was_immutable = ephy_node_db_is_immutable (bookmarks->priv->db);
    ephy_node_db_set_immutable (bookmarks->priv->db, FALSE);

    node = bookmarks->priv->favorites;
    children = ephy_node_get_children (node);
    for (i = (int) children->len - 1; i >= 0; i--)
    {
        EphyNode *kid;

        kid = g_ptr_array_index (children, i);

        ephy_node_remove_child (node, kid);
    }

    ephy_node_db_set_immutable (bookmarks->priv->db, was_immutable);

    ephy_bookmarks_update_favorites (bookmarks);
}

static void
history_cleared_cb (EphyHistory *history, EphyBookmarks *bookmarks)
{
    clear_favorites (bookmarks);
}

static void
update_bookmark_response_cb (GtkWidget *dialog,
                 int response,
                 EphyNode *bookmark)
{
    GValue value = { 0, };
    char *to_uri;

    if (response == GTK_RESPONSE_ACCEPT)
    {
        to_uri = (char *) g_object_steal_data (G_OBJECT (dialog),
                               UPDATE_URI_DATA_KEY);

        g_value_init (&value, G_TYPE_STRING);
        g_value_take_string (&value, to_uri);
        ephy_node_set_property (bookmark, EPHY_NODE_BMK_PROP_LOCATION,
                    &value);
        g_value_unset (&value);
    }

    gtk_widget_destroy (dialog);
}

static void
update_bookmark_destroy_cb (EphyNode *zombie,
                GtkWidget *dialog)
{
    gtk_widget_destroy (dialog);
}

static void
redirect_cb (EphyHistory *history,
          const char *from_uri,
          const char *to_uri,
          EphyBookmarks *eb)
{
    EphyNode *bookmark;
    GtkWidget *dialog;
    const char *title;

    bookmark = ephy_bookmarks_find_bookmark (eb, from_uri);

    /* FIXME check if there's another update-bookmark dialog up
     * for from_uri' -> from_uri and update it accordingly
     */
    if (bookmark == NULL) return;

    title = ephy_node_get_property_string (bookmark, EPHY_NODE_BMK_PROP_TITLE);

    dialog = gtk_message_dialog_new
            (NULL, GTK_DIALOG_DESTROY_WITH_PARENT,
                GTK_MESSAGE_QUESTION,
                GTK_BUTTONS_NONE,
                /* translators: the %s is the title of the bookmark */
                _("Update bookmark \"%s\"?"),
                title);
    gtk_message_dialog_format_secondary_markup
        (GTK_MESSAGE_DIALOG (dialog),
        /* translators: the %s is a URL */
        _("The bookmarked page has moved to \"%s\"."),
        to_uri);

    gtk_dialog_add_button (GTK_DIALOG (dialog),
                _("_Don't Update"), GTK_RESPONSE_REJECT);
    gtk_dialog_add_button (GTK_DIALOG (dialog),
                _("_Update"), GTK_RESPONSE_ACCEPT);
    gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_REJECT);

    gtk_window_set_title (GTK_WINDOW (dialog), _("Update Bookmark?"));
    gtk_window_set_icon_name (GTK_WINDOW (dialog), "web-browser");

    g_object_set_data_full (G_OBJECT (dialog), UPDATE_URI_DATA_KEY,
                g_strdup (to_uri), (GDestroyNotify) g_free);
    g_signal_connect (dialog, "response",
              G_CALLBACK (update_bookmark_response_cb), bookmark);
    ephy_node_signal_connect_object (bookmark,
                     EPHY_NODE_DESTROY,
                     (EphyNodeCallback) update_bookmark_destroy_cb,
                     G_OBJECT (dialog));

    /* this dialogue is unexpected */
    gtk_window_set_focus_on_map (GTK_WINDOW (dialog), FALSE);

    gtk_window_present (GTK_WINDOW (dialog));
}

static void
ephy_setup_history_notifiers (EphyBookmarks *eb)
{
    EphyHistory *history;

    history = EPHY_HISTORY (ephy_embed_shell_get_global_history (embed_shell));

    if (ephy_history_is_enabled (history) == FALSE)
    {
        clear_favorites (eb);
    }

    g_signal_connect (history, "visited",
              G_CALLBACK (history_site_visited_cb), eb);
    g_signal_connect (history, "cleared",
              G_CALLBACK (history_cleared_cb), eb);
    g_signal_connect (history, "redirect",
              G_CALLBACK (redirect_cb), eb);
}

static void
update_bookmark_keywords (EphyBookmarks *eb, EphyNode *bookmark)
{
    GValue value = { 0, };
    GPtrArray *children;
    int i;
    GString *list;
    const char *title;
    char *normalized_keywords, *case_normalized_keywords;

    list = g_string_new (NULL);

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

        kid = g_ptr_array_index (children, i);

        if (kid != eb->priv->notcategorized && 
            kid != eb->priv->favorites &&
            kid != eb->priv->bookmarks &&
            ephy_node_has_child (kid, bookmark))
        {
            const char *topic;
            topic = ephy_node_get_property_string
                (kid, EPHY_NODE_KEYWORD_PROP_NAME);
            g_string_append (list, topic);
            g_string_append (list, " ");
        }
    }

    title = ephy_node_get_property_string
        (bookmark, EPHY_NODE_BMK_PROP_TITLE);
    g_string_append (list, " ");
    g_string_append (list, title);

    normalized_keywords = g_utf8_normalize (list->str, -1, G_NORMALIZE_ALL);
    case_normalized_keywords = g_utf8_casefold (normalized_keywords, -1);

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_string (&value, case_normalized_keywords);
    ephy_node_set_property (bookmark, EPHY_NODE_BMK_PROP_KEYWORDS,
                    &value);
    g_value_unset (&value);

    g_string_free (list, TRUE);
    g_free (normalized_keywords);
    g_free (case_normalized_keywords);
}

static void
bookmarks_changed_cb (EphyNode *node,
              EphyNode *child,
              guint property_id,
              EphyBookmarks *eb)
{
    if (property_id == EPHY_NODE_BMK_PROP_TITLE)
    {
        update_bookmark_keywords (eb, child);
    }

    ephy_bookmarks_save_delayed (eb, BOOKMARKS_SAVE_DELAY);
}

static void
bookmarks_removed_cb (EphyNode *node,
              EphyNode *child,
              guint old_index,
              EphyBookmarks *eb)
{
    ephy_bookmarks_save_delayed (eb, BOOKMARKS_SAVE_DELAY);
}

static gboolean
bookmark_is_categorized (EphyBookmarks *eb, EphyNode *bookmark)
{
    GPtrArray *children;
    int i;

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

        kid = g_ptr_array_index (children, i);

        if (kid != eb->priv->notcategorized && 
            kid != eb->priv->favorites &&
            kid != eb->priv->bookmarks &&
            ephy_node_has_child (kid, bookmark))
        {
            return TRUE;
        }
    }

    return FALSE;
}

static void
topics_removed_cb (EphyNode *node,
           EphyNode *child,
           guint old_index,
           EphyBookmarks *eb)
{
    GPtrArray *children;
    int i;

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

        kid = g_ptr_array_index (children, i);

        if (!bookmark_is_categorized (eb, kid) &&
            !ephy_node_has_child (eb->priv->notcategorized, kid))
        {
            ephy_node_add_child
                (eb->priv->notcategorized, kid);
        }

        update_bookmark_keywords (eb, kid);
    }
}

static void
update_bookmark_editing (EphyBookmarks *eb)
{
    g_object_set (G_OBJECT (eb->priv->db),
              "immutable", eel_gconf_get_boolean (CONF_LOCKDOWN_DISABLE_BOOKMARK_EDITING),
              NULL);
}

static void
disable_bookmark_editing_notifier (GConfClient *client,
                   guint cnxn_id,
                   GConfEntry *entry,
                   EphyBookmarks *eb)
{
    update_bookmark_editing (eb);
}

static void
backup_file (const char *original_filename, const char *extension)
{
    char *template, *backup_filename;
    int result = 0;

    if (g_file_test (original_filename, G_FILE_TEST_EXISTS) == FALSE)
    {
        return;
    }

    template = g_strconcat (original_filename, ".backup-XXXXXX", NULL);
    backup_filename = ephy_file_tmp_filename (template, extension);

    if (backup_filename != NULL)
    {
        result = rename (original_filename, backup_filename);
    }

    if (result >= 0)
    {
        g_message ("Your old bookmarks file was backed up as \"%s\".\n",
               backup_filename);
    }
    else
    {
        g_warning ("Backup failed! Your old bookmarks file was lost.\n");
    }

    g_free (template);
    g_free (backup_filename);
}

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

    eb->priv = EPHY_BOOKMARKS_GET_PRIVATE (eb);

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

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

    eb->priv->props_dialogs = g_hash_table_new (g_direct_hash,
                            g_direct_equal);

    /* Bookmarks */
    eb->priv->bookmarks = ephy_node_new_with_id (db, BOOKMARKS_NODE_ID);
    g_value_init (&value, G_TYPE_STRING);
    /* Translators: The text before the "|" is context to help you decide on
     * the correct translation. You MUST OMIT it in the translated string. */
    /* Translators: this topic contains all bookmarks */
    g_value_set_string (&value, Q_("bookmarks|All"));
    ephy_node_set_property (eb->priv->bookmarks,
                    EPHY_NODE_KEYWORD_PROP_NAME,
                    &value);
    g_value_unset (&value);
    ephy_node_signal_connect_object (eb->priv->bookmarks,
                         EPHY_NODE_CHILD_REMOVED,
                         (EphyNodeCallback) bookmarks_removed_cb,
                         G_OBJECT (eb));
    ephy_node_signal_connect_object (eb->priv->bookmarks,
                         EPHY_NODE_CHILD_CHANGED,
                         (EphyNodeCallback) bookmarks_changed_cb,
                         G_OBJECT (eb));

    /* Keywords */
    eb->priv->keywords = ephy_node_new_with_id (db, KEYWORDS_NODE_ID);
    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, EPHY_NODE_ALL_PRIORITY);
    ephy_node_set_property (eb->priv->bookmarks,
                    EPHY_NODE_KEYWORD_PROP_PRIORITY,
                    &value);
    g_value_unset (&value);
    ephy_node_signal_connect_object (eb->priv->keywords,
                         EPHY_NODE_CHILD_REMOVED,
                         (EphyNodeCallback) topics_removed_cb,
                         G_OBJECT (eb));

    ephy_node_add_child (eb->priv->keywords,
                 eb->priv->bookmarks);

    /* Favorites */
    eb->priv->favorites = ephy_node_new_with_id (db, FAVORITES_NODE_ID);
    g_value_init (&value, G_TYPE_STRING);
    /* Translators: The text before the "|" is context to help you decide on
     * the correct translation. You MUST OMIT it in the translated string. */
    /* Translators: this topic contains the most used bookmarks */
    g_value_set_string (&value, Q_("bookmarks|Most Visited"));
    ephy_node_set_property (eb->priv->favorites,
                    EPHY_NODE_KEYWORD_PROP_NAME,
                    &value);
    g_value_unset (&value);
    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, EPHY_NODE_SPECIAL_PRIORITY);
    ephy_node_set_property (eb->priv->favorites,
                EPHY_NODE_KEYWORD_PROP_PRIORITY,
                &value);
    g_value_unset (&value);
    ephy_node_add_child (eb->priv->keywords, eb->priv->favorites);

    /* Not categorized */
    eb->priv->notcategorized = ephy_node_new_with_id (db, BMKS_NOTCATEGORIZED_NODE_ID);
    g_value_init (&value, G_TYPE_STRING);
    /* Translators: The text before the "|" is context to help you decide on
     * the correct translation. You MUST OMIT it in the translated string. */
    /* Translators: this topic contains the not categorized bookmarks */
    g_value_set_string (&value, Q_("bookmarks|Not Categorized"));
    ephy_node_set_property (eb->priv->notcategorized,
                    EPHY_NODE_KEYWORD_PROP_NAME,
                    &value);
    g_value_unset (&value);
    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, EPHY_NODE_SPECIAL_PRIORITY);
    ephy_node_set_property (eb->priv->notcategorized,
                EPHY_NODE_KEYWORD_PROP_PRIORITY,
                &value);
    g_value_unset (&value);
    ephy_node_add_child (eb->priv->keywords, eb->priv->notcategorized);

    /* Smart bookmarks */
    eb->priv->smartbookmarks = ephy_node_new_with_id (db, SMARTBOOKMARKS_NODE_ID);

    if (g_file_test (eb->priv->xml_file, G_FILE_TEST_EXISTS) == FALSE
        && g_file_test (eb->priv->rdf_file, G_FILE_TEST_EXISTS) == FALSE)
    {
        eb->priv->init_defaults = TRUE;
    }
    else if (ephy_node_db_load_from_file (eb->priv->db, eb->priv->xml_file,
                          (xmlChar *) EPHY_BOOKMARKS_XML_ROOT,
                          (xmlChar *) EPHY_BOOKMARKS_XML_VERSION) == FALSE)
    {
        /* save the corrupted files so the user can late try to
         * manually recover them. See bug #128308.
         */

        g_warning ("Could not read bookmarks file \"%s\", trying to "
               "re-import bookmarks from \"%s\"\n",
               eb->priv->xml_file, eb->priv->rdf_file);

        backup_file (eb->priv->xml_file, "xml");

        if (ephy_bookmarks_import_rdf (eb, eb->priv->rdf_file) == FALSE)
        {
            backup_file (eb->priv->rdf_file, "rdf");

            eb->priv->init_defaults = TRUE;
        }
    }

    eb->priv->disable_bookmark_editing_notifier_id = eel_gconf_notification_add
        (CONF_LOCKDOWN_DISABLE_BOOKMARK_EDITING,
         (GConfClientNotifyFunc)disable_bookmark_editing_notifier, eb);
    update_bookmark_editing (eb);

    ephy_setup_history_notifiers (eb);
    ephy_bookmarks_update_favorites (eb);
}

static void
ephy_bookmarks_finalize (GObject *object)
{
    EphyBookmarks *eb = EPHY_BOOKMARKS (object);

    eel_gconf_notification_remove (eb->priv->disable_bookmark_editing_notifier_id);

    g_hash_table_destroy (eb->priv->props_dialogs);

    if (eb->priv->save_timeout_id != 0)
    {
        g_source_remove (eb->priv->save_timeout_id);
    }

    ephy_bookmarks_save (eb);

    /* have to do this before unreffing the nodes */
        LOG ("Unref bookmarks toolbars model");
        if (eb->priv->toolbars_model != NULL)
        {
                g_object_unref (eb->priv->toolbars_model);
        }

    ephy_node_unref (eb->priv->bookmarks);
    ephy_node_unref (eb->priv->keywords);
    ephy_node_unref (eb->priv->favorites);
    ephy_node_unref (eb->priv->notcategorized);

    g_object_unref (eb->priv->db);

    g_free (eb->priv->xml_file);
    g_free (eb->priv->rdf_file);

    LOG ("Bookmarks finalized");

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

EphyBookmarks *
ephy_bookmarks_new (void)
{
    EphyBookmarks *eb;

    eb = EPHY_BOOKMARKS (g_object_new (EPHY_TYPE_BOOKMARKS, NULL));

    return eb;
}

static void
update_has_smart_address (EphyBookmarks *bookmarks, EphyNode *bmk, const char *address)
{
    EphyNode *smart_bmks;
    gboolean smart = FALSE;

    smart_bmks = bookmarks->priv->smartbookmarks;

    if (address)
    {
        smart = strstr (address, "%s") != NULL;
    }

    if (smart)
    {
        if (!ephy_node_has_child (smart_bmks, bmk))
        {
            ephy_node_add_child (smart_bmks, bmk);
        }
    }
    else
    {
        if (ephy_node_has_child (smart_bmks, bmk))
        {
            ephy_node_remove_child (smart_bmks, bmk);
        }
    }
}

EphyNode *
ephy_bookmarks_add (EphyBookmarks *eb,
            const char *title,
            const char *url)
{
    EphyNode *bm;
    GValue value = { 0, };

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

    if (bm == NULL) return NULL;

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_string (&value, title);
    ephy_node_set_property (bm, EPHY_NODE_BMK_PROP_TITLE,
                    &value);
    g_value_unset (&value);

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

    update_has_smart_address (eb, bm, url);
    update_bookmark_keywords (eb, bm);

    ephy_node_add_child (eb->priv->bookmarks, bm);
    ephy_node_add_child (eb->priv->notcategorized, bm);

    ephy_bookmarks_save_delayed (eb, 0);

    return bm;
}

void
ephy_bookmarks_set_address (EphyBookmarks *eb,
                EphyNode *bookmark,
                const char *address)
{
    GValue value = { 0, };

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_string (&value, address);
    ephy_node_set_property (bookmark, EPHY_NODE_BMK_PROP_LOCATION,
                    &value);
    g_value_unset (&value);

    update_has_smart_address (eb, bookmark, address);
}

EphyNode *
ephy_bookmarks_find_bookmark (EphyBookmarks *eb,
                  const char *url)
{
    GPtrArray *children;
    int i;

    g_return_val_if_fail (EPHY_IS_BOOKMARKS (eb), NULL);
    g_return_val_if_fail (eb->priv->bookmarks != NULL, NULL);
    g_return_val_if_fail (url != NULL, NULL);

    children = ephy_node_get_children (eb->priv->bookmarks);
    for (i = 0; i < children->len; i++)
    {
        EphyNode *kid;
        const char *location;

        kid = g_ptr_array_index (children, i);
        location = ephy_node_get_property_string
            (kid, EPHY_NODE_BMK_PROP_LOCATION);

        if (location != NULL && strcmp (url, location) == 0)
        {
            return kid;
        }
    }

    return NULL;
}

void
ephy_bookmarks_set_icon (EphyBookmarks *eb,
             const char *url,
             const char *icon)
{
    EphyNode *node;
    GValue value = { 0, };

    g_return_if_fail (icon != NULL);

    node = ephy_bookmarks_find_bookmark (eb, url);
    if (node == NULL) return;

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_string (&value, icon);
    ephy_node_set_property (node, EPHY_NODE_BMK_PROP_ICON,
                    &value);
    g_value_unset (&value);
}

static gchar *
options_skip_spaces (const gchar *str)
{
    const gchar *ret = str;
    while (*ret && g_ascii_isspace (*ret))
    {
        ++ret;
    }
    return (gchar *) ret;
}

static char *
options_find_value_end (const gchar *value_start)
{
    const gchar *value_end;
    if (*value_start == '"')
    {
        for (value_end = value_start + 1;
             *value_end && (*value_end != '"' || *(value_end - 1) == '\\');
             ++value_end) ;
    }
    else
    {
        for (value_end = value_start;
             *value_end && ! (g_ascii_isspace (*value_end)
                      || *value_end == ','
                      || *value_end == ';');
             ++value_end) ;
    }
    return (gchar *) value_end;
}

static char *
options_find_next_option (const char *current)
{
    const gchar *value_start;
    const gchar *value_end;
    const gchar *ret;
    value_start = strchr (current, '=');
    if (!value_start) return NULL;
    value_start = options_skip_spaces (value_start + 1);
    value_end = options_find_value_end (value_start);
    if (! (*value_end)) return NULL;
    for (ret = value_end + 1;
         *ret && (g_ascii_isspace (*ret)
              || *ret == ','
              || *ret == ';');
         ++ret);
    return (char *) ret;
}

static char *
options_find_first_option (const char *input)
{
    const char *start;

    if (!input) return NULL;
    start = strrchr (input, '{');
    if (!start || !(*start)) return NULL;
    return options_skip_spaces (start+1);
}

/**
 * Very simple parser for option strings in the
 * form a=b,c=d,e="f g",...
 */
static gchar *
smart_url_options_get (const gchar *options, const gchar *option)
{
    gchar *ret = NULL;
    gsize optionlen = strlen (option);
    const gchar *current = options_find_first_option (options_skip_spaces (options));

    while (current)
    {
        if (!strncmp (option, current, optionlen))
        {
            if (g_ascii_isspace (*(current + optionlen)) || *(current + optionlen) == '=')
            {
                const gchar *value_start;
                const gchar *value_end;
                value_start = strchr (current + optionlen, '=');
                if (!value_start) continue;
                value_start = options_skip_spaces (value_start + 1);
                value_end = options_find_value_end (value_start);
                if (*value_start == '"') value_start++;
                if (value_end >= value_start)
                {
                    ret = g_strndup (value_start, value_end - value_start);
                    break;
                }
            }
        }
        current = options_find_next_option (current);
    }

    return ret;
}

static char *
get_smarturl_only (const char *smarturl)
{
    const gchar *openbrace;
    const gchar *closebrace;
    const gchar *c;

    openbrace = strchr (smarturl, '{');
    if (!openbrace) return g_strdup (smarturl);
    for (c = smarturl; c < openbrace; ++c)
    {
        if (!strchr (" \t\n", *c)) return g_strdup (smarturl);
    }

    closebrace = strchr (openbrace + 1, '}');
    if (!closebrace) return g_strdup (smarturl);

    return g_strdup (closebrace + 1);
}

char *
ephy_bookmarks_solve_smart_url (EphyBookmarks *eb,
                const char *smart_url,
                const char *content)
{
    gchar *ret;
    GString *s;
    gchar *t1;
    gchar *t2;
    gchar *encoding;
    gchar *smarturl_only;
    gchar *arg;
    gchar *escaped_arg;

    g_return_val_if_fail (content != NULL, NULL);

    smarturl_only = get_smarturl_only (smart_url);

    s = g_string_new (NULL);

    encoding = smart_url_options_get (smart_url, "encoding");
    if (!encoding)
    {
        encoding = g_strdup ("UTF-8");
    }

    arg = g_convert (content, strlen (content),
             encoding, "UTF-8", NULL, NULL, NULL);
    if (arg == NULL) return NULL;
    escaped_arg = gnome_vfs_escape_string (arg);

    t1 = smarturl_only;
    t2 = strstr (t1, "%s");
    g_return_val_if_fail (t2 != NULL, NULL);
    g_string_append_len (s, t1, t2 - t1);
    g_string_append (s, escaped_arg);
    t1 = t2 + 2;
    g_string_append (s, t1);
    ret = g_string_free (s, FALSE);

    g_free (arg);
    g_free (escaped_arg);
    g_free (encoding);
    g_free (smarturl_only);

    return ret;
}

EphyNode *
ephy_bookmarks_add_keyword (EphyBookmarks *eb,
                const char *name)
{
    EphyNode *key;
    GValue value = { 0, };

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

    if (key == NULL) return NULL;

    g_value_init (&value, G_TYPE_STRING);
    g_value_set_string (&value, name);
    ephy_node_set_property (key, EPHY_NODE_KEYWORD_PROP_NAME,
                    &value);
    g_value_unset (&value);

    g_value_init (&value, G_TYPE_INT);
    g_value_set_int (&value, EPHY_NODE_NORMAL_PRIORITY);
    ephy_node_set_property (key, EPHY_NODE_KEYWORD_PROP_PRIORITY,
                    &value);
    g_value_unset (&value);

    ephy_node_add_child (eb->priv->keywords, key);

    return key;
}


static void
prop_dialog_destroy_cb (EphyBookmarkProperties *dialog,
            EphyBookmarks *bookmarks)
{
    EphyNode *bookmark;

    bookmark = ephy_bookmark_properties_get_node (dialog);

    g_hash_table_remove (bookmarks->priv->props_dialogs, bookmark);
}

static void
bookmark_destroyed_cb (EphyNode *bookmark,
               GtkWidget *dialog)
{
    gtk_widget_destroy (dialog);
}        

GtkWidget *
ephy_bookmarks_show_bookmark_properties (EphyBookmarks *bookmarks,
                     EphyNode *bookmark,
                         GtkWidget *parent)
{
    GtkWidget *dialog = NULL;

    g_return_val_if_fail (EPHY_IS_BOOKMARKS (bookmarks), NULL);
    g_return_val_if_fail (EPHY_IS_NODE (bookmark), NULL);

    dialog = g_hash_table_lookup (bookmarks->priv->props_dialogs, bookmark);

    if (dialog == NULL)
    {
        dialog = ephy_bookmark_properties_new (bookmarks, bookmark, parent);

        ephy_node_signal_connect_object (bookmark,
                         EPHY_NODE_DESTROY,
                         (EphyNodeCallback) bookmark_destroyed_cb,
                         G_OBJECT (dialog));

        g_signal_connect (dialog, "destroy",
                  G_CALLBACK (prop_dialog_destroy_cb), bookmarks);
        g_hash_table_insert (bookmarks->priv->props_dialogs,
                     bookmark, dialog);
    }

    gtk_window_present (GTK_WINDOW (dialog));

    return dialog;
}

void
ephy_bookmarks_remove_keyword (EphyBookmarks *eb,
                   EphyNode *keyword)
{
    ephy_node_remove_child (eb->priv->keywords, keyword);
}

char *
ephy_bookmarks_get_topic_uri (EphyBookmarks *eb,
                  EphyNode *node)
{
    char *uri;

    if (ephy_bookmarks_get_bookmarks (eb) == node)
    {
        uri = g_strdup ("topic://Special/All");
    }
    else if (ephy_bookmarks_get_not_categorized (eb) == node)
    {
        uri = g_strdup ("topic://Special/NotCategorized");
    }
    else if (ephy_bookmarks_get_favorites (eb) == node)
    {
        uri = g_strdup ("topic://Special/Favorites");
    }
    else
    {
        const char *name;

        name = ephy_node_get_property_string
            (node, EPHY_NODE_KEYWORD_PROP_NAME);

        uri = g_strdup_printf ("topic://%s", name);
    }

    return uri;
}

EphyNode *
ephy_bookmarks_find_keyword (EphyBookmarks *eb,
                 const char *name,
                 gboolean partial_match)
{
    EphyNode *node;
    GPtrArray *children;
    int i;
    const char *topic_name;

    g_return_val_if_fail (name != NULL, NULL);

    topic_name = name;

    if (g_utf8_strlen (name, -1) == 0)
    {
        LOG ("Empty name, no keyword matches.");
        return NULL;
    }

    if (strcmp (name, "topic://Special/All") == 0)
    {
        return ephy_bookmarks_get_bookmarks (eb);
    }
    else if (strcmp (name, "topic://Special/NotCategorized") == 0)
    {
        return ephy_bookmarks_get_not_categorized (eb);
    }
    else if (strcmp (name, "topic://Special/Favorites") == 0)
    {
        return ephy_bookmarks_get_favorites (eb);
    }
    else if (g_str_has_prefix (name, "topic://"))
    {
        topic_name += strlen ("topic://");
    }

    children = ephy_node_get_children (eb->priv->keywords);
    node = NULL;
    for (i = 0; i < children->len; i++)
    {
         EphyNode *kid;
         const char *key;

         kid = g_ptr_array_index (children, i);
         key = ephy_node_get_property_string (kid, EPHY_NODE_KEYWORD_PROP_NAME);

         if ((partial_match && g_str_has_prefix (key, topic_name) > 0) ||
             (!partial_match && strcmp (key, topic_name) == 0))
         {
             node = kid;
         }
    }

    return node;
}

gboolean
ephy_bookmarks_has_keyword (EphyBookmarks *eb,
                EphyNode *keyword,
                EphyNode *bookmark)
{
    return ephy_node_has_child (keyword, bookmark);
}

void
ephy_bookmarks_set_keyword (EphyBookmarks *eb,
                EphyNode *keyword,
                EphyNode *bookmark)
{
    if (ephy_node_has_child (keyword, bookmark)) return;

    ephy_node_add_child (keyword, bookmark);

    if (ephy_node_has_child (eb->priv->notcategorized, bookmark))
    {
        LOG ("Remove from categorized bookmarks");
        ephy_node_remove_child
            (eb->priv->notcategorized, bookmark);
    }

    update_bookmark_keywords (eb, bookmark);

    g_signal_emit (G_OBJECT (eb), ephy_bookmarks_signals[TREE_CHANGED], 0);
}

void
ephy_bookmarks_unset_keyword (EphyBookmarks *eb,
                  EphyNode *keyword,
                  EphyNode *bookmark)
{
    if (!ephy_node_has_child (keyword, bookmark)) return;

    ephy_node_remove_child (keyword, bookmark);

    if (!bookmark_is_categorized (eb, bookmark) &&
        !ephy_node_has_child (eb->priv->notcategorized, bookmark))
    {
        LOG ("Add to not categorized bookmarks");
        ephy_node_add_child
            (eb->priv->notcategorized, bookmark);
    }

    update_bookmark_keywords (eb, bookmark);

    g_signal_emit (G_OBJECT (eb), ephy_bookmarks_signals[TREE_CHANGED], 0);
}

EphyNode *
ephy_bookmarks_get_smart_bookmarks (EphyBookmarks *eb)
{
    return eb->priv->smartbookmarks;
}

EphyNode *
ephy_bookmarks_get_keywords (EphyBookmarks *eb)
{
    return eb->priv->keywords;
}

EphyNode *
ephy_bookmarks_get_bookmarks (EphyBookmarks *eb)
{
    return eb->priv->bookmarks;
}

EphyNode *
ephy_bookmarks_get_favorites (EphyBookmarks *eb)
{
    return eb->priv->favorites;
}

EphyNode *
ephy_bookmarks_get_not_categorized (EphyBookmarks *eb)
{
    return eb->priv->notcategorized;
}

EphyNode *
ephy_bookmarks_get_from_id (EphyBookmarks *eb, long id)
{
    return ephy_node_db_get_node_from_id (eb->priv->db, id);
}