Blame src/dh-book-manager.c

Packit 116408
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
Packit 116408
/*
Packit 116408
 * Copyright (C) 2002 CodeFactory AB
Packit 116408
 * Copyright (C) 2002 Mikael Hallendal <micke@imendio.com>
Packit 116408
 * Copyright (C) 2004-2008 Imendio AB
Packit 116408
 * Copyright (C) 2010 Lanedo GmbH
Packit 116408
 * Copyright (C) 2012 Thomas Bechtold <toabctl@gnome.org>
Packit 116408
 * Copyright (C) 2017 Sébastien Wilmet <swilmet@gnome.org>
Packit 116408
 *
Packit 116408
 * This program is free software; you can redistribute it and/or
Packit 116408
 * modify it under the terms of the GNU General Public License as
Packit 116408
 * published by the Free Software Foundation; either version 2 of the
Packit 116408
 * License, or (at your option) any later version.
Packit 116408
 *
Packit 116408
 * This program is distributed in the hope that it will be useful,
Packit 116408
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 116408
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 116408
 * General Public License for more details.
Packit 116408
 *
Packit 116408
 * You should have received a copy of the GNU General Public License
Packit 116408
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
Packit 116408
 */
Packit 116408
Packit 116408
#include "config.h"
Packit 116408
#include "dh-book-manager.h"
Packit 116408
#include "dh-book.h"
Packit 116408
#include "dh-settings.h"
Packit 116408
#include "dh-util.h"
Packit 116408
Packit 116408
/**
Packit 116408
 * SECTION:dh-book-manager
Packit 116408
 * @Title: DhBookManager
Packit 116408
 * @Short_description: Aggregation of all #DhBook's
Packit 116408
 *
Packit 116408
 * #DhBookManager is a singleton class containing all the #DhBook's.
Packit 116408
 */
Packit 116408
Packit 116408
/* TODO: Re-architect DhBookManager and DhBook.
Packit 116408
 *
Packit 116408
 * DhBookManager and DhBook are not very flexible:
Packit 116408
 * 1. Whether a DhBook is enabled or disabled is hard-coded into the DhBook
Packit 116408
 * objects. It's bound to the "books-disabled" GSetting.
Packit 116408
 *
Packit 116408
 * 2. The list of directories where DhBookManager searches the books is more or
Packit 116408
 * less hard-coded inside DhBookManager (it's just configurable with XDG env
Packit 116408
 * variables, see the populate() function, it's documented in the README). It
Packit 116408
 * would be nice to have total control over which directories are searched,
Packit 116408
 * without duplicating them if two different "views" have a directory in common
Packit 116408
 * (especially not duplicating GFileMonitor's).
Packit 116408
 *
Packit 116408
 * Ideas:
Packit 116408
 * - Create a DhBookSelection class (or set of classes, with maybe an interface
Packit 116408
 *   or base class), and remove the "enabled" property from DhBook. The
Packit 116408
 *   books-disabled GSetting would be implemented by one implementation of
Packit 116408
 *   DhBookSelection. A :book-selection property could be added to some classes,
Packit 116408
 *   and if that property is NULL take the "default selection" (by default the
Packit 116408
 *   one for the books-disabled GSetting). Another possible name: DhBookFilter
Packit 116408
 *   (or have both).
Packit 116408
 *
Packit 116408
 *   Have ::book-added and ::book-removed signals. A single ::changed signal is
Packit 116408
 *   I think not appropriate: for example for DhBookTree, a full repopulate
Packit 116408
 *   could be done when ::changed is emitted, but in that case DhBookTree would
Packit 116408
 *   loose its selection.
Packit 116408
 *
Packit 116408
 * - Factor out a DhBookListDirectory class, finding and monitoring a list of
Packit 116408
 *   books in one directory. The constructor would roughly be
Packit 116408
 *   find_books_in_dir(). DhBookManager would just contain a list of
Packit 116408
 *   DhBookListDirectory objects, ensuring that there are no duplicates. A list
Packit 116408
 *   of DhBookListDirectory's could be added to a DhBookSelection, and it's
Packit 116408
 *   DhBookSelection which applies priorities. So two different DhBookSelection
Packit 116408
 *   objects could apply different priorities between the directories.
Packit 116408
 *   Ensuring that a book ID is unique would be done by each DhBookListDirectory
Packit 116408
 *   object, and also by DhBookSelection; which means that Devhelp would use
Packit 116408
 *   more memory since some DhBooks would not be freed since they are contained
Packit 116408
 *   in different DhBookListDirectory objects, but the index files anyway needed
Packit 116408
 *   to be parsed to know the book ID, so it's not a big issue.
Packit 116408
 *
Packit 116408
 * Relevant bugzilla tickets:
Packit 116408
 * - https://bugzilla.gnome.org/show_bug.cgi?id=784491
Packit 116408
 *   "BookManager: allow custom search paths for documentation"
Packit 116408
 *
Packit 116408
 *   For gnome-builder needs.
Packit 116408
 *
Packit 116408
 * - https://bugzilla.gnome.org/show_bug.cgi?id=792068
Packit 116408
 *   "Make it work with Flatpak"
Packit 116408
 *
Packit 116408
 *   The directories probably need to be adjusted.
Packit 116408
 *
Packit 116408
 * - https://bugzilla.gnome.org/show_bug.cgi?id=761284
Packit 116408
 *   "Have the latest stable/unstable GNOME API references"
Packit 116408
 *
Packit 116408
 *   The books can be downloaded in different directories, one directory for
Packit 116408
 *   "GNOME stable" and another directory for "GNOME unstable" (or for specific
Packit 116408
 *   versions). Switching between versions would just be a matter of changing
Packit 116408
 *   the DhBookSelection.
Packit 116408
 *
Packit 116408
 * - https://bugzilla.gnome.org/show_bug.cgi?id=118423
Packit 116408
 *   "Individual bookshelfs"
Packit 116408
 *
Packit 116408
 * - https://bugzilla.gnome.org/show_bug.cgi?id=764441
Packit 116408
 *   "Implement language switching feature"
Packit 116408
 *
Packit 116408
 *   Basically have the same book ID/name available for different programming
Packit 116408
 *   languages. DhBookSelection could filter by programming language. Out of
Packit 116408
 *   scope for now, because language switching is implemented in JavaScript for
Packit 116408
 *   hot-doc, and gtk-doc doesn't support yet producing documentation for
Packit 116408
 *   different programming languages.
Packit 116408
 */
Packit 116408
Packit 116408
#define NEW_POSSIBLE_BOOK_TIMEOUT_SECS 5
Packit 116408
Packit 116408
typedef struct {
Packit 116408
        DhBookManager *book_manager; /* unowned */
Packit 116408
        GFile *book_directory;
Packit 116408
        guint timeout_id;
Packit 116408
} NewPossibleBookData;
Packit 116408
Packit 116408
typedef struct {
Packit 116408
        /* The list of all DhBooks* found in the system */
Packit 116408
        GList *books;
Packit 116408
Packit 116408
        /* GFile* -> GFileMonitor* */
Packit 116408
        GHashTable *monitors;
Packit 116408
Packit 116408
        /* List of NewPossibleBookData* */
Packit 116408
        GSList *new_possible_books_data;
Packit 116408
Packit 116408
        /* List of book IDs (gchar*) currently disabled */
Packit 116408
        GList *books_disabled;
Packit 116408
Packit 116408
        guint group_by_language : 1;
Packit 116408
} DhBookManagerPrivate;
Packit 116408
Packit 116408
enum {
Packit 116408
        SIGNAL_BOOK_CREATED,
Packit 116408
        SIGNAL_BOOK_DELETED,
Packit 116408
        SIGNAL_BOOK_ENABLED,
Packit 116408
        SIGNAL_BOOK_DISABLED,
Packit 116408
        N_SIGNALS
Packit 116408
};
Packit 116408
Packit 116408
enum {
Packit 116408
        PROP_0,
Packit 116408
        PROP_GROUP_BY_LANGUAGE
Packit 116408
};
Packit 116408
Packit 116408
static guint signals[N_SIGNALS] = { 0 };
Packit 116408
Packit 116408
static DhBookManager *singleton = NULL;
Packit 116408
Packit 116408
G_DEFINE_TYPE_WITH_PRIVATE (DhBookManager, dh_book_manager, G_TYPE_OBJECT);
Packit 116408
Packit 116408
static gboolean create_book_from_index_file (DhBookManager *book_manager,
Packit 116408
                                             GFile         *index_file);
Packit 116408
Packit 116408
static NewPossibleBookData *
Packit 116408
new_possible_book_data_new (DhBookManager *book_manager,
Packit 116408
                            GFile         *book_directory)
Packit 116408
{
Packit 116408
        NewPossibleBookData *data;
Packit 116408
Packit 116408
        data = g_new0 (NewPossibleBookData, 1);
Packit 116408
        data->book_manager = book_manager;
Packit 116408
        data->book_directory = g_object_ref (book_directory);
Packit 116408
Packit 116408
        return data;
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
new_possible_book_data_free (gpointer _data)
Packit 116408
{
Packit 116408
        NewPossibleBookData *data = _data;
Packit 116408
Packit 116408
        if (data == NULL)
Packit 116408
                return;
Packit 116408
Packit 116408
        g_clear_object (&data->book_directory);
Packit 116408
Packit 116408
        if (data->timeout_id != 0)
Packit 116408
                g_source_remove (data->timeout_id);
Packit 116408
Packit 116408
        g_free (data);
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
dh_book_manager_get_property (GObject    *object,
Packit 116408
                              guint       prop_id,
Packit 116408
                              GValue     *value,
Packit 116408
                              GParamSpec *pspec)
Packit 116408
{
Packit 116408
        DhBookManager *book_manager = DH_BOOK_MANAGER (object);
Packit 116408
Packit 116408
        switch (prop_id)
Packit 116408
        {
Packit 116408
        case PROP_GROUP_BY_LANGUAGE:
Packit 116408
                g_value_set_boolean (value, dh_book_manager_get_group_by_language (book_manager));
Packit 116408
                break;
Packit 116408
Packit 116408
        default:
Packit 116408
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Packit 116408
                break;
Packit 116408
        }
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
dh_book_manager_set_property (GObject      *object,
Packit 116408
                              guint         prop_id,
Packit 116408
                              const GValue *value,
Packit 116408
                              GParamSpec   *pspec)
Packit 116408
{
Packit 116408
        DhBookManager *book_manager = DH_BOOK_MANAGER (object);
Packit 116408
Packit 116408
        switch (prop_id)
Packit 116408
        {
Packit 116408
        case PROP_GROUP_BY_LANGUAGE:
Packit 116408
                dh_book_manager_set_group_by_language (book_manager, g_value_get_boolean (value));
Packit 116408
                break;
Packit 116408
Packit 116408
        default:
Packit 116408
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Packit 116408
                break;
Packit 116408
        }
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
dh_book_manager_dispose (GObject *object)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv;
Packit 116408
Packit 116408
        priv = dh_book_manager_get_instance_private (DH_BOOK_MANAGER (object));
Packit 116408
Packit 116408
        g_list_free_full (priv->books, g_object_unref);
Packit 116408
        priv->books = NULL;
Packit 116408
Packit 116408
        if (priv->monitors != NULL) {
Packit 116408
                g_hash_table_destroy (priv->monitors);
Packit 116408
                priv->monitors = NULL;
Packit 116408
        }
Packit 116408
Packit 116408
        g_slist_free_full (priv->new_possible_books_data, new_possible_book_data_free);
Packit 116408
        priv->new_possible_books_data = NULL;
Packit 116408
Packit 116408
        G_OBJECT_CLASS (dh_book_manager_parent_class)->dispose (object);
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
dh_book_manager_finalize (GObject *object)
Packit 116408
{
Packit 116408
        DhBookManager *book_manager = DH_BOOK_MANAGER (object);
Packit 116408
        DhBookManagerPrivate *priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
Packit 116408
        g_list_free_full (priv->books_disabled, g_free);
Packit 116408
Packit 116408
        if (singleton == book_manager)
Packit 116408
                singleton = NULL;
Packit 116408
Packit 116408
        G_OBJECT_CLASS (dh_book_manager_parent_class)->finalize (object);
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
dh_book_manager_class_init (DhBookManagerClass *klass)
Packit 116408
{
Packit 116408
        GObjectClass *object_class = G_OBJECT_CLASS (klass);
Packit 116408
Packit 116408
        object_class->get_property = dh_book_manager_get_property;
Packit 116408
        object_class->set_property = dh_book_manager_set_property;
Packit 116408
        object_class->dispose = dh_book_manager_dispose;
Packit 116408
        object_class->finalize = dh_book_manager_finalize;
Packit 116408
Packit 116408
        /**
Packit 116408
         * DhBookManager::book-created:
Packit 116408
         * @book_manager: the #DhBookManager.
Packit 116408
         * @book: the created #DhBook.
Packit 116408
         */
Packit 116408
        signals[SIGNAL_BOOK_CREATED] =
Packit 116408
                g_signal_new ("book-created",
Packit 116408
                              G_TYPE_FROM_CLASS (klass),
Packit 116408
                              G_SIGNAL_RUN_LAST,
Packit 116408
                              0,
Packit 116408
                              NULL, NULL, NULL,
Packit 116408
                              G_TYPE_NONE,
Packit 116408
                              1,
Packit 116408
                              DH_TYPE_BOOK);
Packit 116408
Packit 116408
        /**
Packit 116408
         * DhBookManager::book-deleted:
Packit 116408
         * @book_manager: the #DhBookManager.
Packit 116408
         * @book: the deleted #DhBook.
Packit 116408
         */
Packit 116408
        signals[SIGNAL_BOOK_DELETED] =
Packit 116408
                g_signal_new ("book-deleted",
Packit 116408
                              G_TYPE_FROM_CLASS (klass),
Packit 116408
                              G_SIGNAL_RUN_LAST,
Packit 116408
                              0,
Packit 116408
                              NULL, NULL, NULL,
Packit 116408
                              G_TYPE_NONE,
Packit 116408
                              1,
Packit 116408
                              DH_TYPE_BOOK);
Packit 116408
Packit 116408
        /**
Packit 116408
         * DhBookManager::book-enabled:
Packit 116408
         * @book_manager: the #DhBookManager.
Packit 116408
         * @book: the enabled #DhBook.
Packit 116408
         */
Packit 116408
        signals[SIGNAL_BOOK_ENABLED] =
Packit 116408
                g_signal_new ("book-enabled",
Packit 116408
                              G_TYPE_FROM_CLASS (klass),
Packit 116408
                              G_SIGNAL_RUN_LAST,
Packit 116408
                              0,
Packit 116408
                              NULL, NULL, NULL,
Packit 116408
                              G_TYPE_NONE,
Packit 116408
                              1,
Packit 116408
                              DH_TYPE_BOOK);
Packit 116408
Packit 116408
        /**
Packit 116408
         * DhBookManager::book-disabled:
Packit 116408
         * @book_manager: the #DhBookManager.
Packit 116408
         * @book: the disabled #DhBook.
Packit 116408
         */
Packit 116408
        signals[SIGNAL_BOOK_DISABLED] =
Packit 116408
                g_signal_new ("book-disabled",
Packit 116408
                              G_TYPE_FROM_CLASS (klass),
Packit 116408
                              G_SIGNAL_RUN_LAST,
Packit 116408
                              0,
Packit 116408
                              NULL, NULL, NULL,
Packit 116408
                              G_TYPE_NONE,
Packit 116408
                              1,
Packit 116408
                              DH_TYPE_BOOK);
Packit 116408
Packit 116408
        /**
Packit 116408
         * DhBookManager:group-by-language:
Packit 116408
         *
Packit 116408
         * Whether books should be grouped by programming language.
Packit 116408
         */
Packit 116408
        g_object_class_install_property (object_class,
Packit 116408
                                         PROP_GROUP_BY_LANGUAGE,
Packit 116408
                                         g_param_spec_boolean ("group-by-language",
Packit 116408
                                                               "Group by language",
Packit 116408
                                                               "",
Packit 116408
                                                               FALSE,
Packit 116408
                                                               G_PARAM_READWRITE |
Packit 116408
                                                               G_PARAM_STATIC_STRINGS));
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
load_books_disabled (DhBookManager *book_manager)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
        DhSettings *settings;
Packit 116408
        GSettings *contents_settings;
Packit 116408
        gchar **books_disabled_strv;
Packit 116408
        gint i;
Packit 116408
Packit 116408
        g_assert (priv->books_disabled == NULL);
Packit 116408
Packit 116408
        settings = dh_settings_get_singleton ();
Packit 116408
        contents_settings = dh_settings_peek_contents_settings (settings);
Packit 116408
        books_disabled_strv = g_settings_get_strv (contents_settings, "books-disabled");
Packit 116408
Packit 116408
        if (books_disabled_strv == NULL)
Packit 116408
                return;
Packit 116408
Packit 116408
        for (i = 0; books_disabled_strv[i] != NULL; i++) {
Packit 116408
                gchar *book_id = books_disabled_strv[i];
Packit 116408
                priv->books_disabled = g_list_prepend (priv->books_disabled, book_id);
Packit 116408
        }
Packit 116408
Packit 116408
        priv->books_disabled = g_list_reverse (priv->books_disabled);
Packit 116408
Packit 116408
        g_free (books_disabled_strv);
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
store_books_disabled (DhBookManager *book_manager)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
        DhSettings *settings;
Packit 116408
        GSettings *contents_settings;
Packit 116408
        GVariantBuilder *builder;
Packit 116408
        GVariant *variant;
Packit 116408
        GList *l;
Packit 116408
Packit 116408
        builder = g_variant_builder_new (G_VARIANT_TYPE_STRING_ARRAY);
Packit 116408
Packit 116408
        for (l = priv->books_disabled; l != NULL; l = l->next) {
Packit 116408
                const gchar *book_id = l->data;
Packit 116408
                g_variant_builder_add (builder, "s", book_id);
Packit 116408
        }
Packit 116408
Packit 116408
        variant = g_variant_builder_end (builder);
Packit 116408
        g_variant_builder_unref (builder);
Packit 116408
Packit 116408
        settings = dh_settings_get_singleton ();
Packit 116408
        contents_settings = dh_settings_peek_contents_settings (settings);
Packit 116408
        g_settings_set_value (contents_settings, "books-disabled", variant);
Packit 116408
}
Packit 116408
Packit 116408
static GList *
Packit 116408
find_book_in_disabled_list (GList  *books_disabled,
Packit 116408
                            DhBook *book)
Packit 116408
{
Packit 116408
        const gchar *book_id;
Packit 116408
        GList *node;
Packit 116408
Packit 116408
        book_id = dh_book_get_id (book);
Packit 116408
Packit 116408
        for (node = books_disabled; node != NULL; node = node->next) {
Packit 116408
                const gchar *cur_book_id = node->data;
Packit 116408
Packit 116408
                if (g_strcmp0 (book_id, cur_book_id) == 0)
Packit 116408
                        return node;
Packit 116408
        }
Packit 116408
Packit 116408
        return NULL;
Packit 116408
}
Packit 116408
Packit 116408
static gboolean
Packit 116408
is_book_disabled_in_conf (DhBookManager *book_manager,
Packit 116408
                          DhBook        *book)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
Packit 116408
        return find_book_in_disabled_list (priv->books_disabled, book) != NULL;
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
remove_book (DhBookManager *book_manager,
Packit 116408
             DhBook        *book)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
        GList *node;
Packit 116408
Packit 116408
        node = g_list_find (priv->books, book);
Packit 116408
Packit 116408
        if (node != NULL) {
Packit 116408
                g_signal_emit (book_manager,
Packit 116408
                               signals[SIGNAL_BOOK_DELETED],
Packit 116408
                               0,
Packit 116408
                               book);
Packit 116408
Packit 116408
                priv->books = g_list_delete_link (priv->books, node);
Packit 116408
                g_object_unref (book);
Packit 116408
        }
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
book_deleted_cb (DhBook        *book,
Packit 116408
                 DhBookManager *book_manager)
Packit 116408
{
Packit 116408
        remove_book (book_manager, book);
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
book_updated_cb (DhBook        *book,
Packit 116408
                 DhBookManager *book_manager)
Packit 116408
{
Packit 116408
        GFile *index_file;
Packit 116408
Packit 116408
        /* Re-create the DhBook to parse again the index file. */
Packit 116408
Packit 116408
        index_file = dh_book_get_index_file (book);
Packit 116408
        g_object_ref (index_file);
Packit 116408
Packit 116408
        remove_book (book_manager, book);
Packit 116408
Packit 116408
        create_book_from_index_file (book_manager, index_file);
Packit 116408
        g_object_unref (index_file);
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
book_enabled_cb (DhBook        *book,
Packit 116408
                 DhBookManager *book_manager)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
        GList *node;
Packit 116408
        gchar *book_id;
Packit 116408
Packit 116408
        node = find_book_in_disabled_list (priv->books_disabled, book);
Packit 116408
Packit 116408
        /* When setting as enabled a given book, we should have it in the
Packit 116408
         * disabled books list!
Packit 116408
         */
Packit 116408
        g_return_if_fail (node != NULL);
Packit 116408
Packit 116408
        book_id = node->data;
Packit 116408
        g_free (book_id);
Packit 116408
        priv->books_disabled = g_list_delete_link (priv->books_disabled, node);
Packit 116408
Packit 116408
        store_books_disabled (book_manager);
Packit 116408
Packit 116408
        g_signal_emit (book_manager,
Packit 116408
                       signals[SIGNAL_BOOK_ENABLED],
Packit 116408
                       0,
Packit 116408
                       book);
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
book_disabled_cb (DhBook        *book,
Packit 116408
                  DhBookManager *book_manager)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
        GList *node;
Packit 116408
        const gchar *book_id;
Packit 116408
Packit 116408
        node = find_book_in_disabled_list (priv->books_disabled, book);
Packit 116408
Packit 116408
        /* When setting as disabled a given book, we shouldn't have it in the
Packit 116408
         * disabled books list!
Packit 116408
         */
Packit 116408
        g_return_if_fail (node == NULL);
Packit 116408
Packit 116408
        book_id = dh_book_get_id (book);
Packit 116408
        priv->books_disabled = g_list_append (priv->books_disabled,
Packit 116408
                                              g_strdup (book_id));
Packit 116408
        store_books_disabled (book_manager);
Packit 116408
Packit 116408
        g_signal_emit (book_manager,
Packit 116408
                       signals[SIGNAL_BOOK_DISABLED],
Packit 116408
                       0,
Packit 116408
                       book);
Packit 116408
}
Packit 116408
Packit 116408
/* Returns TRUE if "successful", FALSE if the next possible index file in the
Packit 116408
 * book directory needs to be tried.
Packit 116408
 */
Packit 116408
static gboolean
Packit 116408
create_book_from_index_file (DhBookManager *book_manager,
Packit 116408
                             GFile         *index_file)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv;
Packit 116408
        DhBook *book;
Packit 116408
        gboolean book_enabled;
Packit 116408
        GList *l;
Packit 116408
Packit 116408
        priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
Packit 116408
        /* Check if a DhBook at the same location has already been loaded. */
Packit 116408
        for (l = priv->books; l != NULL; l = l->next) {
Packit 116408
                DhBook *cur_book = DH_BOOK (l->data);
Packit 116408
                GFile *cur_index_file;
Packit 116408
Packit 116408
                cur_index_file = dh_book_get_index_file (cur_book);
Packit 116408
Packit 116408
                if (g_file_equal (index_file, cur_index_file))
Packit 116408
                        return TRUE;
Packit 116408
        }
Packit 116408
Packit 116408
        book = dh_book_new (index_file);
Packit 116408
        if (book == NULL)
Packit 116408
                return FALSE;
Packit 116408
Packit 116408
        /* Check if book with same ID was already loaded in the manager (we need
Packit 116408
         * to force unique book IDs).
Packit 116408
         */
Packit 116408
        if (g_list_find_custom (priv->books,
Packit 116408
                                book,
Packit 116408
                                (GCompareFunc)dh_book_cmp_by_id)) {
Packit 116408
                g_object_unref (book);
Packit 116408
                return TRUE;
Packit 116408
        }
Packit 116408
Packit 116408
        priv->books = g_list_insert_sorted (priv->books,
Packit 116408
                                            book,
Packit 116408
                                            (GCompareFunc)dh_book_cmp_by_title);
Packit 116408
Packit 116408
        book_enabled = !is_book_disabled_in_conf (book_manager, book);
Packit 116408
        dh_book_set_enabled (book, book_enabled);
Packit 116408
Packit 116408
        g_signal_connect_object (book,
Packit 116408
                                 "deleted",
Packit 116408
                                 G_CALLBACK (book_deleted_cb),
Packit 116408
                                 book_manager,
Packit 116408
                                 0);
Packit 116408
Packit 116408
        g_signal_connect_object (book,
Packit 116408
                                 "updated",
Packit 116408
                                 G_CALLBACK (book_updated_cb),
Packit 116408
                                 book_manager,
Packit 116408
                                 0);
Packit 116408
Packit 116408
        g_signal_connect_object (book,
Packit 116408
                                 "enabled",
Packit 116408
                                 G_CALLBACK (book_enabled_cb),
Packit 116408
                                 book_manager,
Packit 116408
                                 0);
Packit 116408
Packit 116408
        g_signal_connect_object (book,
Packit 116408
                                 "disabled",
Packit 116408
                                 G_CALLBACK (book_disabled_cb),
Packit 116408
                                 book_manager,
Packit 116408
                                 0);
Packit 116408
Packit 116408
        g_signal_emit (book_manager,
Packit 116408
                       signals[SIGNAL_BOOK_CREATED],
Packit 116408
                       0,
Packit 116408
                       book);
Packit 116408
Packit 116408
        return TRUE;
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
create_book_from_directory (DhBookManager *book_manager,
Packit 116408
                            GFile         *book_directory)
Packit 116408
{
Packit 116408
        GSList *possible_index_files;
Packit 116408
        GSList *l;
Packit 116408
Packit 116408
        possible_index_files = _dh_util_get_possible_index_files (book_directory);
Packit 116408
Packit 116408
        for (l = possible_index_files; l != NULL; l = l->next) {
Packit 116408
                GFile *index_file = G_FILE (l->data);
Packit 116408
Packit 116408
                if (create_book_from_index_file (book_manager, index_file))
Packit 116408
                        break;
Packit 116408
        }
Packit 116408
Packit 116408
        g_slist_free_full (possible_index_files, g_object_unref);
Packit 116408
}
Packit 116408
Packit 116408
static gboolean
Packit 116408
new_possible_book_timeout_cb (gpointer user_data)
Packit 116408
{
Packit 116408
        NewPossibleBookData *data = user_data;
Packit 116408
        DhBookManagerPrivate *priv = dh_book_manager_get_instance_private (data->book_manager);
Packit 116408
Packit 116408
        data->timeout_id = 0;
Packit 116408
Packit 116408
        create_book_from_directory (data->book_manager, data->book_directory);
Packit 116408
Packit 116408
        priv->new_possible_books_data = g_slist_remove (priv->new_possible_books_data, data);
Packit 116408
        new_possible_book_data_free (data);
Packit 116408
Packit 116408
        return G_SOURCE_REMOVE;
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
books_directory_changed_cb (GFileMonitor      *directory_monitor,
Packit 116408
                            GFile             *file,
Packit 116408
                            GFile             *other_file,
Packit 116408
                            GFileMonitorEvent  event_type,
Packit 116408
                            DhBookManager     *book_manager)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
        NewPossibleBookData *data;
Packit 116408
Packit 116408
        /* With the GFileMonitor here we only handle events for new directories
Packit 116408
         * created. Book deletions and updates are handled by the GFileMonitor
Packit 116408
         * in each DhBook object.
Packit 116408
         */
Packit 116408
        if (event_type != G_FILE_MONITOR_EVENT_CREATED)
Packit 116408
                return;
Packit 116408
Packit 116408
        data = new_possible_book_data_new (book_manager, file);
Packit 116408
Packit 116408
        /* We add a timeout of several seconds so that we give time to the whole
Packit 116408
         * documentation to get installed. If we don't do this, we may end up
Packit 116408
         * trying to add the new book when even the *.devhelp2 index file is not
Packit 116408
         * installed yet.
Packit 116408
         */
Packit 116408
        data->timeout_id = g_timeout_add_seconds (NEW_POSSIBLE_BOOK_TIMEOUT_SECS,
Packit 116408
                                                  new_possible_book_timeout_cb,
Packit 116408
                                                  data);
Packit 116408
Packit 116408
        priv->new_possible_books_data = g_slist_prepend (priv->new_possible_books_data, data);
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
monitor_books_directory (DhBookManager *book_manager,
Packit 116408
                         GFile         *books_directory)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
        GFileMonitor *directory_monitor;
Packit 116408
        GError *error = NULL;
Packit 116408
Packit 116408
        /* If monitor already exists, do not re-create it. */
Packit 116408
        if (priv->monitors != NULL &&
Packit 116408
            g_hash_table_lookup (priv->monitors, books_directory) != NULL) {
Packit 116408
                return;
Packit 116408
        }
Packit 116408
Packit 116408
        directory_monitor = g_file_monitor_directory (books_directory,
Packit 116408
                                                      G_FILE_MONITOR_NONE,
Packit 116408
                                                      NULL,
Packit 116408
                                                      &error);
Packit 116408
Packit 116408
        if (error != NULL) {
Packit 116408
                gchar *parse_name;
Packit 116408
Packit 116408
                parse_name = g_file_get_parse_name (books_directory);
Packit 116408
Packit 116408
                g_warning ("Failed to create file monitor on directory “%s”: %s",
Packit 116408
                           parse_name,
Packit 116408
                           error->message);
Packit 116408
Packit 116408
                g_free (parse_name);
Packit 116408
                g_clear_error (&error);
Packit 116408
        }
Packit 116408
Packit 116408
        if (directory_monitor != NULL) {
Packit 116408
                if (G_UNLIKELY (priv->monitors == NULL)) {
Packit 116408
                        priv->monitors = g_hash_table_new_full (g_file_hash,
Packit 116408
                                                                (GEqualFunc) g_file_equal,
Packit 116408
                                                                g_object_unref,
Packit 116408
                                                                g_object_unref);
Packit 116408
                }
Packit 116408
Packit 116408
                g_hash_table_insert (priv->monitors,
Packit 116408
                                     g_object_ref (books_directory),
Packit 116408
                                     directory_monitor);
Packit 116408
Packit 116408
                g_signal_connect_object (directory_monitor,
Packit 116408
                                         "changed",
Packit 116408
                                         G_CALLBACK (books_directory_changed_cb),
Packit 116408
                                         book_manager,
Packit 116408
                                         0);
Packit 116408
        }
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
find_books_in_dir (DhBookManager *book_manager,
Packit 116408
                   const gchar   *dir_path)
Packit 116408
{
Packit 116408
        GFile *directory;
Packit 116408
        GFileEnumerator *enumerator;
Packit 116408
        GError *error = NULL;
Packit 116408
Packit 116408
        g_return_if_fail (dir_path != NULL);
Packit 116408
Packit 116408
        directory = g_file_new_for_path (dir_path);
Packit 116408
Packit 116408
        enumerator = g_file_enumerate_children (directory,
Packit 116408
                                                G_FILE_ATTRIBUTE_STANDARD_NAME,
Packit 116408
                                                G_FILE_QUERY_INFO_NONE,
Packit 116408
                                                NULL,
Packit 116408
                                                &error);
Packit 116408
Packit 116408
        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
Packit 116408
                g_clear_error (&error);
Packit 116408
                goto out;
Packit 116408
        }
Packit 116408
Packit 116408
        if (error != NULL) {
Packit 116408
                g_warning ("Error when reading directory '%s': %s",
Packit 116408
                           dir_path,
Packit 116408
                           error->message);
Packit 116408
                g_clear_error (&error);
Packit 116408
                goto out;
Packit 116408
        }
Packit 116408
Packit 116408
        monitor_books_directory (book_manager, directory);
Packit 116408
Packit 116408
        while (TRUE) {
Packit 116408
                GFile *book_directory = NULL;
Packit 116408
Packit 116408
                g_file_enumerator_iterate (enumerator, NULL, &book_directory, NULL, &error);
Packit 116408
Packit 116408
                if (error != NULL) {
Packit 116408
                        g_warning ("Error when enumerating directory '%s': %s",
Packit 116408
                                   dir_path,
Packit 116408
                                   error->message);
Packit 116408
                        g_clear_error (&error);
Packit 116408
                        break;
Packit 116408
                }
Packit 116408
Packit 116408
                if (book_directory == NULL)
Packit 116408
                        break;
Packit 116408
Packit 116408
                create_book_from_directory (book_manager, book_directory);
Packit 116408
        }
Packit 116408
Packit 116408
out:
Packit 116408
        g_object_unref (directory);
Packit 116408
        g_clear_object (&enumerator);
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
find_books_in_data_dir (DhBookManager *book_manager,
Packit 116408
                        const gchar   *data_dir)
Packit 116408
{
Packit 116408
        gchar *dir;
Packit 116408
Packit 116408
        g_return_if_fail (data_dir != NULL);
Packit 116408
Packit 116408
        dir = g_build_filename (data_dir, "gtk-doc", "html", NULL);
Packit 116408
        find_books_in_dir (book_manager, dir);
Packit 116408
        g_free (dir);
Packit 116408
Packit 116408
        dir = g_build_filename (data_dir, "devhelp", "books", NULL);
Packit 116408
        find_books_in_dir (book_manager, dir);
Packit 116408
        g_free (dir);
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
populate (DhBookManager *book_manager)
Packit 116408
{
Packit 116408
        const gchar * const *system_dirs;
Packit 116408
        gint i;
Packit 116408
Packit 116408
        find_books_in_data_dir (book_manager, g_get_user_data_dir ());
Packit 116408
Packit 116408
        system_dirs = g_get_system_data_dirs ();
Packit 116408
        g_return_if_fail (system_dirs != NULL);
Packit 116408
Packit 116408
        for (i = 0; system_dirs[i] != NULL; i++) {
Packit 116408
                find_books_in_data_dir (book_manager, system_dirs[i]);
Packit 116408
        }
Packit 116408
Packit 116408
        /* For Flatpak, to see the books installed on the host by traditional
Packit 116408
         * Linux distro packages.
Packit 116408
         *
Packit 116408
         * It is not a good idea to add the directory to XDG_DATA_DIRS, see:
Packit 116408
         * https://github.com/flatpak/flatpak/issues/1299
Packit 116408
         * "all sorts of things will break if we add all host config to each
Packit 116408
         * app, which is totally opposite to the entire point of flatpak."
Packit 116408
         * "i don't think XDG_DATA_DIRS is the right thing, because all sorts of
Packit 116408
         * libraries will start reading files from there, like dconf, dbus,
Packit 116408
         * service files, mimetypes, etc. It would be preferable to have
Packit 116408
         * something that targeted just gtk-doc files."
Packit 116408
         *
Packit 116408
         * So instead of adapting XDG_DATA_DIRS, add the directory here, with
Packit 116408
         * the path hard-coded.
Packit 116408
         *
Packit 116408
         * https://bugzilla.gnome.org/show_bug.cgi?id=792068
Packit 116408
         */
Packit 116408
#ifdef FLATPAK_BUILD
Packit 116408
        find_books_in_data_dir (book_manager, "/run/host/usr/share");
Packit 116408
#endif
Packit 116408
}
Packit 116408
Packit 116408
static void
Packit 116408
dh_book_manager_init (DhBookManager *book_manager)
Packit 116408
{
Packit 116408
        DhSettings *settings;
Packit 116408
        GSettings *contents_settings;
Packit 116408
Packit 116408
        load_books_disabled (book_manager);
Packit 116408
Packit 116408
        settings = dh_settings_get_singleton ();
Packit 116408
        contents_settings = dh_settings_peek_contents_settings (settings);
Packit 116408
        g_settings_bind (contents_settings, "group-books-by-language",
Packit 116408
                         book_manager, "group-by-language",
Packit 116408
                         G_SETTINGS_BIND_DEFAULT);
Packit 116408
Packit 116408
        populate (book_manager);
Packit 116408
}
Packit 116408
Packit 116408
/**
Packit 116408
 * dh_book_manager_new:
Packit 116408
 *
Packit 116408
 * Returns: (transfer full): the #DhBookManager singleton instance. You need to
Packit 116408
 * unref it when no longer needed.
Packit 116408
 * Deprecated: 3.26: Call dh_book_manager_get_singleton() instead.
Packit 116408
 */
Packit 116408
DhBookManager *
Packit 116408
dh_book_manager_new (void)
Packit 116408
{
Packit 116408
        return g_object_ref (dh_book_manager_get_singleton ());
Packit 116408
}
Packit 116408
Packit 116408
/**
Packit 116408
 * dh_book_manager_get_singleton:
Packit 116408
 *
Packit 116408
 * Returns: (transfer none): the #DhBookManager singleton instance.
Packit 116408
 * Since: 3.26
Packit 116408
 */
Packit 116408
DhBookManager *
Packit 116408
dh_book_manager_get_singleton (void)
Packit 116408
{
Packit 116408
        if (singleton == NULL)
Packit 116408
                singleton = g_object_new (DH_TYPE_BOOK_MANAGER, NULL);
Packit 116408
Packit 116408
        return singleton;
Packit 116408
}
Packit 116408
Packit 116408
void
Packit 116408
_dh_book_manager_unref_singleton (void)
Packit 116408
{
Packit 116408
        if (singleton != NULL)
Packit 116408
                g_object_unref (singleton);
Packit 116408
Packit 116408
        /* singleton is not set to NULL here, it is set to NULL in
Packit 116408
         * dh_book_manager_finalize() (i.e. when we are sure that the ref count
Packit 116408
         * reaches 0).
Packit 116408
         */
Packit 116408
}
Packit 116408
Packit 116408
/**
Packit 116408
 * dh_book_manager_populate:
Packit 116408
 * @book_manager: a #DhBookManager.
Packit 116408
 *
Packit 116408
 * Populates the #DhBookManager with all books found on the system and user
Packit 116408
 * directories.
Packit 116408
 *
Packit 116408
 * Deprecated: 3.26: The #DhBookManager is now automatically populated when the
Packit 116408
 * object is created, there is no need to call this function anymore.
Packit 116408
 */
Packit 116408
void
Packit 116408
dh_book_manager_populate (DhBookManager *book_manager)
Packit 116408
{
Packit 116408
}
Packit 116408
Packit 116408
/**
Packit 116408
 * dh_book_manager_get_books:
Packit 116408
 * @book_manager: a #DhBookManager.
Packit 116408
 *
Packit 116408
 * Returns: (element-type DhBook) (transfer none): the list of all #DhBook's
Packit 116408
 * found.
Packit 116408
 */
Packit 116408
GList *
Packit 116408
dh_book_manager_get_books (DhBookManager *book_manager)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv;
Packit 116408
Packit 116408
        g_return_val_if_fail (DH_IS_BOOK_MANAGER (book_manager), NULL);
Packit 116408
Packit 116408
        priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
Packit 116408
        return priv->books;
Packit 116408
}
Packit 116408
Packit 116408
/**
Packit 116408
 * dh_book_manager_get_group_by_language:
Packit 116408
 * @book_manager: a #DhBookManager.
Packit 116408
 *
Packit 116408
 * Returns: whether the books should be grouped by programming language.
Packit 116408
 */
Packit 116408
gboolean
Packit 116408
dh_book_manager_get_group_by_language (DhBookManager *book_manager)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv;
Packit 116408
Packit 116408
        g_return_val_if_fail (DH_IS_BOOK_MANAGER (book_manager), FALSE);
Packit 116408
Packit 116408
        priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
Packit 116408
        return priv->group_by_language;
Packit 116408
}
Packit 116408
Packit 116408
/**
Packit 116408
 * dh_book_manager_set_group_by_language:
Packit 116408
 * @book_manager: a #DhBookManager.
Packit 116408
 * @group_by_language: the new value.
Packit 116408
 *
Packit 116408
 * Sets whether the books should be grouped by programming language.
Packit 116408
 */
Packit 116408
void
Packit 116408
dh_book_manager_set_group_by_language (DhBookManager *book_manager,
Packit 116408
                                       gboolean       group_by_language)
Packit 116408
{
Packit 116408
        DhBookManagerPrivate *priv;
Packit 116408
Packit 116408
        g_return_if_fail (DH_IS_BOOK_MANAGER (book_manager));
Packit 116408
Packit 116408
        priv = dh_book_manager_get_instance_private (book_manager);
Packit 116408
Packit 116408
        group_by_language = group_by_language != FALSE;
Packit 116408
Packit 116408
        if (priv->group_by_language != group_by_language) {
Packit 116408
                priv->group_by_language = group_by_language;
Packit 116408
                g_object_notify (G_OBJECT (book_manager), "group-by-language");
Packit 116408
        }
Packit 116408
}