aboutsummaryrefslogblamecommitdiffstats
path: root/shell/e-shell-backend.c
blob: 0749aac4e6d800aa00df1fa5a9f8eaef21ae2c51 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14













                                                                    
                                                                             
  

                                                        

                                                        
                                       

   





                                                      



                    



                            
 



                          
 



                                                            

                              





                                                                   


                                                         

                          
                               
 


                                            



                            
               

                            


      



                       

                                  
                                                                         
 
           

































                                                                            



                                                                             




                                                                        


                                       




                                                           

                                                            
                                                       


                                                                             
                                      

                                                                     







                                                                

                                                            





                                                                     

                                                                  



                                                                         

 



                                                                       
 
                                     

                                          
                      
                        
 


                                                                             
 

                                                          
 

                                                      
                                                          
                                                                     






                                                                       

                      


           










                                                                























                                                                       





                                                                       



                                       
                                                    





                                                            




                                               





                                                              
                                                    
                                                                        


           



                                        
                                                    
 


                                                             
                                  


                                                     
                                                                         

 


                                                           




                                                                   
                                                                   





                                                                              









                                                         


                                                                 







                                                                            



                                             
           
                                                      

                                   
                                         
 


                                                                        
                                                              
                                                                
                                                                
                                                      

                                                        


                                                        


                                                             
           














                                                                       














                                                                          
















                                                                     
                                                   
 
                                                                          
                                                         




































                                                                            

                                  
                                                                        
 



                                                                   














                                                                      

                                  
                                                                        
 



                                                                 












                                                        

                                

                                                                        


                                                                              






                                    

                                                                          




                                                           
                             
 


                                                              
                                                
 





                                                          

                       

                                                                      






                                                                            






                                                          






















                                                                           

















                                                                        










                                                                    
                                                                  













                                                                                   





























                                                                        



























                                                                       




                                    


                                                                         







                                                                         

   
                           





























                                                                           
/*
 * e-shell-backend.c
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 * Authors:
 *   Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 * Copyright (C) 2009 Intel Corporation
 */

/**
 * SECTION: e-shell-backend
 * @short_description: dynamically loaded capabilities
 * @include: shell/e-shell-backend.h
 **/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "e-shell-backend.h"

#include <errno.h>
#include <glib/gi18n.h>

#include "e-util/e-util.h"

#include "e-shell.h"
#include "e-shell-view.h"

#define E_SHELL_BACKEND_GET_PRIVATE(obj) \
    (G_TYPE_INSTANCE_GET_PRIVATE \
    ((obj), E_TYPE_SHELL_BACKEND, EShellBackendPrivate))

struct _EShellBackendPrivate {

    /* We keep a reference to corresponding EShellView subclass
     * since it keeps a reference back to us.  This ensures the
     * subclass is not finalized before we are, otherwise it
     * would leak its EShellBackend reference. */
    EShellViewClass *shell_view_class;

    /* This tracks what the backend is busy doing. */
    GQueue *activities;

    gchar *config_dir;
    gchar *data_dir;
    gchar *prefer_new_item;

    /* This is set to delay shutdown. */
    gulong notify_busy_handler_id;

    guint started   : 1;
};

enum {
    PROP_0,
    PROP_BUSY,
    PROP_PREFER_NEW_ITEM
};

enum {
    ACTIVITY_ADDED,
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

G_DEFINE_ABSTRACT_TYPE (EShellBackend, e_shell_backend, E_TYPE_EXTENSION)

static void
shell_backend_debug_list_activities (EShellBackend *shell_backend)
{
    EShellBackendClass *class;
    GList *head, *link;
    guint n_activities;

    class = E_SHELL_BACKEND_GET_CLASS (shell_backend);

    n_activities = g_queue_get_length (shell_backend->priv->activities);

    if (n_activities == 0)
        return;

    g_debug (
        "%u active '%s' %s:",
        n_activities, class->name,
        (n_activities == 1) ? "activity" : "activities");

    head = g_queue_peek_head_link (shell_backend->priv->activities);

    for (link = head; link != NULL; link = g_list_next (link)) {
        EActivity *activity = E_ACTIVITY (link->data);
        gchar *description;

        description = e_activity_describe (activity);
        if (description != NULL)
            g_debug ("* %s", description);
        else
            g_debug ("* (no description)");
        g_free (description);
    }
}

static void
shell_backend_activity_finalized_cb (EShellBackend *shell_backend,
                                     EActivity *finalized_activity)
{
    g_queue_remove (shell_backend->priv->activities, finalized_activity);

    /* Only emit "notify::busy" when switching from busy to idle. */
    if (g_queue_is_empty (shell_backend->priv->activities))
        g_object_notify (G_OBJECT (shell_backend), "busy");

    g_object_unref (shell_backend);
}

static void
shell_backend_notify_busy_cb (EShellBackend *shell_backend,
                              GParamSpec *pspec,
                              EActivity *activity)
{
    shell_backend_debug_list_activities (shell_backend);

    if (!e_shell_backend_is_busy (shell_backend)) {
        /* Disconnecting this signal handler will unreference the
         * EActivity and allow the shell to proceed with shutdown. */
        g_signal_handler_disconnect (
            shell_backend,
            shell_backend->priv->notify_busy_handler_id);
        shell_backend->priv->notify_busy_handler_id = 0;
    }
}

static void
shell_backend_prepare_for_quit_cb (EShell *shell,
                                   EActivity *activity,
                                   EShellBackend *shell_backend)
{
    shell_backend_debug_list_activities (shell_backend);

    if (e_shell_backend_is_busy (shell_backend)) {
        gulong handler_id;

        /* Referencing the EActivity delays shutdown; the
         * reference count acts like a counting semaphore. */
        handler_id = g_signal_connect_data (
            shell_backend, "notify::busy",
            G_CALLBACK (shell_backend_notify_busy_cb),
            g_object_ref (activity),
            (GClosureNotify) g_object_unref, 0);
        shell_backend->priv->notify_busy_handler_id = handler_id;
    }
}

static GObject *
shell_backend_constructor (GType type,
                           guint n_construct_properties,
                           GObjectConstructParam *construct_properties)
{
    EShellBackend *shell_backend;
    EShellBackendClass *class;
    EShellViewClass *shell_view_class;
    EShell *shell;
    GObject *object;

    /* Chain up to parent's construct() method. */
    object = G_OBJECT_CLASS (e_shell_backend_parent_class)->constructor (
        type, n_construct_properties, construct_properties);

    shell_backend = E_SHELL_BACKEND (object);
    shell = e_shell_backend_get_shell (shell_backend);

    /* Install a reference to ourselves in the
     * corresponding EShellViewClass structure. */
    class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
    shell_view_class = g_type_class_ref (class->shell_view_type);
    shell_view_class->shell_backend = g_object_ref (shell_backend);
    shell_backend->priv->shell_view_class = shell_view_class;

    g_signal_connect (
        shell, "prepare-for-quit",
        G_CALLBACK (shell_backend_prepare_for_quit_cb),
        shell_backend);

    return object;
}

static void
shell_backend_get_property (GObject *object,
                            guint property_id,
                            GValue *value,
                            GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_BUSY:
            g_value_set_boolean (
                value, e_shell_backend_is_busy (
                E_SHELL_BACKEND (object)));
            return;

        case PROP_PREFER_NEW_ITEM:
            g_value_set_string (
                value,
                e_shell_backend_get_prefer_new_item (
                E_SHELL_BACKEND (object)));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
shell_backend_set_property (GObject *object,
                            guint property_id,
                            const GValue *value,
                            GParamSpec *pspec)
{
    switch (property_id) {
        case PROP_PREFER_NEW_ITEM:
            e_shell_backend_set_prefer_new_item (
                E_SHELL_BACKEND (object),
                g_value_get_string (value));
            return;
    }

    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
shell_backend_dispose (GObject *object)
{
    EShellBackendPrivate *priv;

    priv = E_SHELL_BACKEND_GET_PRIVATE (object);

    if (priv->shell_view_class != NULL) {
        g_type_class_unref (priv->shell_view_class);
        priv->shell_view_class = NULL;
    }

    if (priv->prefer_new_item) {
        g_free (priv->prefer_new_item);
        priv->prefer_new_item = NULL;
    }

    if (priv->notify_busy_handler_id > 0) {
        g_signal_handler_disconnect (
            object, priv->notify_busy_handler_id);
        priv->notify_busy_handler_id = 0;
    }

    /* Chain up to parent's dispose() method. */
    G_OBJECT_CLASS (e_shell_backend_parent_class)->dispose (object);
}

static void
shell_backend_finalize (GObject *object)
{
    EShellBackendPrivate *priv;

    priv = E_SHELL_BACKEND_GET_PRIVATE (object);

    g_warn_if_fail (g_queue_is_empty (priv->activities));
    g_queue_free (priv->activities);

    g_free (priv->config_dir);
    g_free (priv->data_dir);

    /* Chain up to parent's finalize() method. */
    G_OBJECT_CLASS (e_shell_backend_parent_class)->finalize (object);
}

static const gchar *
shell_backend_get_config_dir (EShellBackend *shell_backend)
{
    EShellBackendClass *class;

    class = E_SHELL_BACKEND_GET_CLASS (shell_backend);

    /* Determine the user config directory for this backend. */
    if (G_UNLIKELY (shell_backend->priv->config_dir == NULL)) {
        const gchar *user_config_dir;

        user_config_dir = e_get_user_config_dir ();
        shell_backend->priv->config_dir =
            g_build_filename (user_config_dir, class->name, NULL);
        g_mkdir_with_parents (shell_backend->priv->config_dir, 0700);
    }

    return shell_backend->priv->config_dir;
}

static const gchar *
shell_backend_get_data_dir (EShellBackend *shell_backend)
{
    EShellBackendClass *class;

    class = E_SHELL_BACKEND_GET_CLASS (shell_backend);

    /* Determine the user data directory for this backend. */
    if (G_UNLIKELY (shell_backend->priv->data_dir == NULL)) {
        const gchar *user_data_dir;

        user_data_dir = e_get_user_data_dir ();
        shell_backend->priv->data_dir =
            g_build_filename (user_data_dir, class->name, NULL);
        g_mkdir_with_parents (shell_backend->priv->data_dir, 0700);
    }

    return shell_backend->priv->data_dir;
}

static void
e_shell_backend_class_init (EShellBackendClass *class)
{
    GObjectClass *object_class;
    EExtensionClass *extension_class;

    g_type_class_add_private (class, sizeof (EShellBackendPrivate));

    object_class = G_OBJECT_CLASS (class);
    object_class->constructor = shell_backend_constructor;
    object_class->get_property = shell_backend_get_property;
    object_class->set_property = shell_backend_set_property;
    object_class->dispose = shell_backend_dispose;
    object_class->finalize = shell_backend_finalize;

    extension_class = E_EXTENSION_CLASS (class);
    extension_class->extensible_type = E_TYPE_SHELL;

    class->get_config_dir = shell_backend_get_config_dir;
    class->get_data_dir = shell_backend_get_data_dir;

    /**
     * EShellBackend:busy
     *
     * Whether any activities are still in progress.
     **/
    g_object_class_install_property (
        object_class,
        PROP_BUSY,
        g_param_spec_boolean (
            "busy",
            "Busy",
            "Whether any activities are still in progress",
            FALSE,
            G_PARAM_READABLE));

    /**
     * EShellBackend:prefer-new-item
     *
     * Name of an item to prefer in New toolbar button; can be NULL
     **/
    g_object_class_install_property (
        object_class,
        PROP_PREFER_NEW_ITEM,
        g_param_spec_string (
            "prefer-new-item",
            "Prefer New Item",
            "Name of an item to prefer in New toolbar button",
            NULL,
            G_PARAM_READWRITE));

    /**
     * EShellBackend::activity-added
     * @shell_backend: the #EShellBackend that emitted the signal
     * @activity: an #EActivity
     *
     * Broadcasts a newly added activity.
     **/
    signals[ACTIVITY_ADDED] = g_signal_new (
        "activity-added",
        G_OBJECT_CLASS_TYPE (object_class),
        G_SIGNAL_RUN_LAST,
        0, NULL, NULL,
        g_cclosure_marshal_VOID__OBJECT,
        G_TYPE_NONE, 1,
        E_TYPE_ACTIVITY);
}

static void
e_shell_backend_init (EShellBackend *shell_backend)
{
    shell_backend->priv = E_SHELL_BACKEND_GET_PRIVATE (shell_backend);
    shell_backend->priv->activities = g_queue_new ();
}

/**
 * e_shell_backend_compare:
 * @shell_backend_a: an #EShellBackend
 * @shell_backend_b: an #EShellBackend
 *
 * Using the <structfield>sort_order</structfield> field from both backends'
 * #EShellBackendClass, compares @shell_backend_a with @shell_mobule_b and
 * returns -1, 0 or +1 if @shell_backend_a is found to be less than, equal
 * to or greater than @shell_backend_b, respectively.
 *
 * Returns: -1, 0 or +1, for a less than, equal to or greater than result
 **/
gint
e_shell_backend_compare (EShellBackend *shell_backend_a,
                         EShellBackend *shell_backend_b)
{
    gint a = E_SHELL_BACKEND_GET_CLASS (shell_backend_a)->sort_order;
    gint b = E_SHELL_BACKEND_GET_CLASS (shell_backend_b)->sort_order;

    return (a < b) ? -1 : (a > b);
}

/**
 * e_shell_backend_get_config_dir:
 * @shell_backend: an #EShellBackend
 *
 * Returns the absolute path to the configuration directory for
 * @shell_backend.  The string is owned by @shell_backend and should
 * not be modified or freed.
 *
 * Returns: the backend's configuration directory
 **/
const gchar *
e_shell_backend_get_config_dir (EShellBackend *shell_backend)
{
    EShellBackendClass *class;

    g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), NULL);

    class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
    g_return_val_if_fail (class->get_config_dir != NULL, NULL);

    return class->get_config_dir (shell_backend);
}

/**
 * e_shell_backend_get_data_dir:
 * @shell_backend: an #EShellBackend
 *
 * Returns the absolute path to the data directory for @shell_backend.
 * The string is owned by @shell_backend and should not be modified or
 * freed.
 *
 * Returns: the backend's data directory
 **/
const gchar *
e_shell_backend_get_data_dir (EShellBackend *shell_backend)
{
    EShellBackendClass *class;

    g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), NULL);

    class = E_SHELL_BACKEND_GET_CLASS (shell_backend);
    g_return_val_if_fail (class->get_data_dir != NULL, NULL);

    return class->get_data_dir (shell_backend);
}

/**
 * e_shell_backend_get_shell:
 * @shell_backend: an #EShellBackend
 *
 * Returns the #EShell singleton.
 *
 * Returns: the #EShell
 **/
EShell *
e_shell_backend_get_shell (EShellBackend *shell_backend)
{
    EExtensible *extensible;

    g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), NULL);

    extensible = e_extension_get_extensible (E_EXTENSION (shell_backend));

    return E_SHELL (extensible);
}

/**
 * e_shell_backend_add_activity:
 * @shell_backend: an #EShellBackend
 * @activity: an #EActivity
 *
 * Emits an #EShellBackend::activity-added signal and tracks the @activity
 * until it is finalized.
 **/
void
e_shell_backend_add_activity (EShellBackend *shell_backend,
                              EActivity *activity)
{
    EActivityState state;

    g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend));
    g_return_if_fail (E_IS_ACTIVITY (activity));

    state = e_activity_get_state (activity);

    /* Disregard cancelled or completed activities. */

    if (state == E_ACTIVITY_CANCELLED)
        return;

    if (state == E_ACTIVITY_COMPLETED)
        return;

    g_queue_push_tail (shell_backend->priv->activities, activity);

    /* Emit the "activity-added" signal before adding a weak reference
     * to the EActivity because EShellTaskbar's signal handler also adds
     * a weak reference to the EActivity, and we want its GWeakNotify
     * to run before ours, since ours may destroy the EShellTaskbar
     * during application shutdown. */
    g_signal_emit (shell_backend, signals[ACTIVITY_ADDED], 0, activity);

    /* We reference the backend on every activity to
     * guarantee the backend outlives the activity. */
    g_object_weak_ref (
        G_OBJECT (activity), (GWeakNotify)
        shell_backend_activity_finalized_cb,
        g_object_ref (shell_backend));

    /* Only emit "notify::busy" when switching from idle to busy. */
    if (g_queue_get_length (shell_backend->priv->activities) == 1)
        g_object_notify (G_OBJECT (shell_backend), "busy");
}

/**
 * e_shell_backend_is_busy:
 * @shell_backend: an #EShellBackend
 *
 * Returns %TRUE if any activities passed to e_shell_backend_add_activity()
 * are still in progress, %FALSE if the @shell_backend is currently idle.
 *
 * Returns: %TRUE if activities are still in progress
 **/
gboolean
e_shell_backend_is_busy (EShellBackend *shell_backend)
{
    g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), FALSE);

    return !g_queue_is_empty (shell_backend->priv->activities);
}

/**
 * e_shell_backend_get_prefer_new_item:
 * @shell_backend: an #EShellBackend
 *
 * Returns: Name of a preferred item in New toolbar button, %NULL or
 * an empty string for no preference.
 *
 * Since: 3.4
 **/
const gchar *
e_shell_backend_get_prefer_new_item (EShellBackend *shell_backend)
{
    g_return_val_if_fail (shell_backend != NULL, NULL);
    g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), NULL);

    return shell_backend->priv->prefer_new_item;
}

/**
 * e_shell_backend_set_prefer_new_item:
 * @shell_backend: an #EShellBackend
 * @prefer_new_item: name of an item
 *
 * Sets name of a preferred item in New toolbar button. Use %NULL or
 * an empty string for no preference.
 *
 * Since: 3.4
 **/
void
e_shell_backend_set_prefer_new_item (EShellBackend *shell_backend,
                                     const gchar *prefer_new_item)
{
    g_return_if_fail (shell_backend != NULL);
    g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend));

    if (g_strcmp0 (shell_backend->priv->prefer_new_item, prefer_new_item) == 0)
        return;

    g_free (shell_backend->priv->prefer_new_item);
    shell_backend->priv->prefer_new_item = g_strdup (prefer_new_item);

    g_object_notify (G_OBJECT (shell_backend), "prefer-new-item");
}

/**
 * e_shell_backend_cancel_all:
 * @shell_backend: an #EShellBackend
 *
 * Cancels all activities passed to e_shell_backend_add_activity() that
 * have not already been finalized.  Note that an #EActivity can only be
 * cancelled if it was given a #GCancellable object.
 *
 * Also, assuming all activities are cancellable, there may still be a
 * delay before e_shell_backend_is_busy() returns %FALSE, because some
 * activities may not be able to respond to the cancellation request
 * immediately.  Connect to the "notify::busy" signal if you need
 * notification of @shell_backend becoming idle.
 **/
void
e_shell_backend_cancel_all (EShellBackend *shell_backend)
{
    GList *list, *iter;

    g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend));

    list = g_queue_peek_head_link (shell_backend->priv->activities);

    for (iter = list; iter != NULL; iter = g_list_next (iter)) {
        EActivity *activity;
        GCancellable *cancellable;

        activity = E_ACTIVITY (iter->data);
        cancellable = e_activity_get_cancellable (activity);
        g_cancellable_cancel (cancellable);
    }
}

/**
 * e_shell_backend_start:
 * @shell_backend: an #EShellBackend
 *
 * Tells the @shell_backend to begin loading data or running background
 * tasks which may consume significant resources.  This gets called in
 * reponse to the user switching to the corresponding #EShellView for
 * the first time.  The function is idempotent for each @shell_backend.
 **/
void
e_shell_backend_start (EShellBackend *shell_backend)
{
    EShellBackendClass *class;

    g_return_if_fail (E_IS_SHELL_BACKEND (shell_backend));

    if (shell_backend->priv->started)
        return;

    class = E_SHELL_BACKEND_GET_CLASS (shell_backend);

    if (class->start != NULL)
        class->start (shell_backend);

    shell_backend->priv->started = TRUE;
}

/**
 * e_shell_backend_is_started:
 * @shell_backend: an #EShellBackend
 *
 * Returns whether e_shell_backend_start() was called for @shell_backend.
 *
 * Returns: whether @shell_backend is started
 **/
gboolean
e_shell_backend_is_started (EShellBackend *shell_backend)
{
    g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), FALSE);

    return shell_backend->priv->started;
}

/**
 * e_shell_backend_migrate:
 * @shell_backend: an #EShellBackend
 * @major: major part of version to migrate from
 * @minor: minor part of version to migrate from
 * @micro: micro part of version to migrate from
 * @error: return location for a #GError, or %NULL
 *
 * Attempts to migrate data and settings from version %major.%minor.%micro.
 * Returns %TRUE if the migration was successful or if no action was
 * necessary.  Returns %FALSE and sets %error if the migration failed.
 *
 * Returns: %TRUE if successful, %FALSE otherwise
 **/
gboolean
e_shell_backend_migrate (EShellBackend *shell_backend,
                        gint major,
                        gint minor,
                        gint micro,
                        GError **error)
{
    EShellBackendClass *class;

    g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), TRUE);

    class = E_SHELL_BACKEND_GET_CLASS (shell_backend);

    if (class->migrate == NULL)
        return TRUE;

    return class->migrate (shell_backend, major, minor, micro, error);
}