[devhelp] Implement DhBookListDirectory



commit 2f16efe0641d0e85f6692503af7107ed556a60ce
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Fri Apr 27 13:35:40 2018 +0200

    Implement DhBookListDirectory

 devhelp/devhelp.h                   |    1 +
 devhelp/dh-book-list-directory.c    |  520 +++++++++++++++++++++++++++++++++++
 devhelp/dh-book-list-directory.h    |   61 ++++
 devhelp/meson.build                 |    2 +
 docs/reference/devhelp-docs.xml     |    1 +
 docs/reference/devhelp-sections.txt |   17 ++
 po/POTFILES.in                      |    1 +
 7 files changed, 603 insertions(+), 0 deletions(-)
---
diff --git a/devhelp/devhelp.h b/devhelp/devhelp.h
index a90a9f1..302197d 100644
--- a/devhelp/devhelp.h
+++ b/devhelp/devhelp.h
@@ -29,6 +29,7 @@
 #include <devhelp/dh-book.h>
 #include <devhelp/dh-book-list.h>
 #include <devhelp/dh-book-list-builder.h>
+#include <devhelp/dh-book-list-directory.h>
 #include <devhelp/dh-book-manager.h>
 #include <devhelp/dh-book-tree.h>
 #include <devhelp/dh-completion.h>
diff --git a/devhelp/dh-book-list-directory.c b/devhelp/dh-book-list-directory.c
new file mode 100644
index 0000000..a61c05c
--- /dev/null
+++ b/devhelp/dh-book-list-directory.c
@@ -0,0 +1,520 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/*
+ * This file is part of Devhelp.
+ *
+ * Copyright (C) 2018 Sébastien Wilmet <swilmet gnome org>
+ *
+ * Devhelp 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 3 of the License,
+ * or (at your option) any later version.
+ *
+ * Devhelp 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 Devhelp.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "dh-book-list-directory.h"
+#include "dh-util-lib.h"
+
+/**
+ * SECTION:dh-book-list-directory
+ * @Title: DhBookListDirectory
+ * @Short_description: Subclass of #DhBookList containing the #DhBook's in one
+ *   directory
+ *
+ * #DhBookListDirectory is a subclass of #DhBookList containing the #DhBook's in
+ * #DhBookListDirectory:directory. In that directory, each book must be in a
+ * direct sub-directory, with the Devhelp index file as a direct child of that
+ * sub-directory.
+ *
+ * For example if #DhBookListDirectory:directory is "/usr/share/gtk-doc/html/",
+ * and if there is an index file at
+ * "/usr/share/gtk-doc/html/glib/glib.devhelp2", #DhBookListDirectory will
+ * contain a #DhBook for that index file.
+ *
+ * Additionally the name of (1) the sub-directory and (2) the index file minus
+ * its extension, must match ("glib" in the example).
+ *
+ * See #DhBook for the list of allowed Devhelp index file extensions
+ * ("*.devhelp2" in the example).
+ *
+ * #DhBookListDirectory listens to the #DhBook #DhBook::deleted and
+ * #DhBook::updated signals, to remove the #DhBook or to re-create it. And
+ * #DhBookListDirectory contains a #GFileMonitor on the
+ * #DhBookListDirectory:directory to add new #DhBook's when they are installed.
+ * But note that those #GFileMonitor's are not guaranteed to work perfectly,
+ * recreating the #DhBookListDirectory (or restarting the application) may be
+ * needed to see all the index files after filesystem changes in
+ * #DhBookListDirectory:directory.
+ */
+
+#define NEW_POSSIBLE_BOOK_TIMEOUT_SECS 5
+
+typedef struct {
+        DhBookListDirectory *list_directory; /* unowned */
+        GFile *book_directory;
+        guint timeout_id;
+} NewPossibleBookData;
+
+struct _DhBookListDirectoryPrivate {
+        GFile *directory;
+        GFileMonitor *directory_monitor;
+
+        /* List of NewPossibleBookData* */
+        GSList *new_possible_books_data;
+};
+
+enum {
+        PROP_0,
+        PROP_DIRECTORY,
+        N_PROPERTIES
+};
+
+/* List of unowned DhBookListDirectory*. */
+static GList *instances;
+
+static GParamSpec *properties[N_PROPERTIES];
+
+G_DEFINE_TYPE_WITH_PRIVATE (DhBookListDirectory, dh_book_list_directory, DH_TYPE_BOOK_LIST)
+
+/* Prototypes */
+static gboolean create_book_from_index_file (DhBookListDirectory *list_directory,
+                                             GFile               *index_file);
+
+static NewPossibleBookData *
+new_possible_book_data_new (DhBookListDirectory *list_directory,
+                            GFile               *book_directory)
+{
+        NewPossibleBookData *data;
+
+        data = g_new0 (NewPossibleBookData, 1);
+        data->list_directory = list_directory;
+        data->book_directory = g_object_ref (book_directory);
+
+        return data;
+}
+
+static void
+new_possible_book_data_free (gpointer _data)
+{
+        NewPossibleBookData *data = _data;
+
+        if (data == NULL)
+                return;
+
+        g_clear_object (&data->book_directory);
+
+        if (data->timeout_id != 0)
+                g_source_remove (data->timeout_id);
+
+        g_free (data);
+}
+
+static void
+book_deleted_cb (DhBook              *book,
+                 DhBookListDirectory *list_directory)
+{
+        dh_book_list_remove_book (DH_BOOK_LIST (list_directory), book);
+}
+
+static void
+book_updated_cb (DhBook              *book,
+                 DhBookListDirectory *list_directory)
+{
+        GFile *index_file;
+
+        /* Re-create the DhBook to parse again the index file. */
+
+        index_file = dh_book_get_index_file (book);
+        g_object_ref (index_file);
+
+        dh_book_list_remove_book (DH_BOOK_LIST (list_directory), book);
+
+        create_book_from_index_file (list_directory, index_file);
+        g_object_unref (index_file);
+}
+
+/* Returns TRUE if "successful", FALSE if the next possible index file in the
+ * book directory needs to be tried.
+ */
+static gboolean
+create_book_from_index_file (DhBookListDirectory *list_directory,
+                             GFile               *index_file)
+{
+        GList *books;
+        GList *l;
+        DhBook *book;
+
+        books = dh_book_list_get_books (DH_BOOK_LIST (list_directory));
+
+        /* Check if a DhBook at the same location has already been loaded. */
+        for (l = books; l != NULL; l = l->next) {
+                DhBook *cur_book = DH_BOOK (l->data);
+                GFile *cur_index_file;
+
+                cur_index_file = dh_book_get_index_file (cur_book);
+
+                if (g_file_equal (index_file, cur_index_file))
+                        return TRUE;
+        }
+
+        book = dh_book_new (index_file);
+        if (book == NULL)
+                return FALSE;
+
+        /* Check if book with same ID was already loaded (we need to force
+         * unique book IDs).
+         */
+        if (g_list_find_custom (books, book, (GCompareFunc)dh_book_cmp_by_id) != NULL) {
+                g_object_unref (book);
+                return TRUE;
+        }
+
+        g_signal_connect_object (book,
+                                 "deleted",
+                                 G_CALLBACK (book_deleted_cb),
+                                 list_directory,
+                                 0);
+
+        g_signal_connect_object (book,
+                                 "updated",
+                                 G_CALLBACK (book_updated_cb),
+                                 list_directory,
+                                 0);
+
+        dh_book_list_add_book (DH_BOOK_LIST (list_directory), book);
+        g_object_unref (book);
+
+        return TRUE;
+}
+
+/* @book_directory is a directory containing a single book, with the index file
+ * as a direct child.
+ */
+static void
+create_book_from_book_directory (DhBookListDirectory *list_directory,
+                                 GFile               *book_directory)
+{
+        GSList *possible_index_files;
+        GSList *l;
+
+        possible_index_files = _dh_util_get_possible_index_files (book_directory);
+
+        for (l = possible_index_files; l != NULL; l = l->next) {
+                GFile *index_file = G_FILE (l->data);
+
+                if (create_book_from_index_file (list_directory, index_file))
+                        break;
+        }
+
+        g_slist_free_full (possible_index_files, g_object_unref);
+}
+
+static gboolean
+new_possible_book_timeout_cb (gpointer user_data)
+{
+        NewPossibleBookData *data = user_data;
+        DhBookListDirectoryPrivate *priv = data->list_directory->priv;
+
+        data->timeout_id = 0;
+
+        create_book_from_book_directory (data->list_directory, data->book_directory);
+
+        priv->new_possible_books_data = g_slist_remove (priv->new_possible_books_data, data);
+        new_possible_book_data_free (data);
+
+        return G_SOURCE_REMOVE;
+}
+
+static void
+books_directory_changed_cb (GFileMonitor        *directory_monitor,
+                            GFile               *file,
+                            GFile               *other_file,
+                            GFileMonitorEvent    event_type,
+                            DhBookListDirectory *list_directory)
+{
+        DhBookListDirectoryPrivate *priv = list_directory->priv;
+        NewPossibleBookData *data;
+
+        /* With the GFileMonitor here we only handle events for new directories
+         * created. Book deletions and updates are handled by the GFileMonitor
+         * in each DhBook object.
+         */
+        if (event_type != G_FILE_MONITOR_EVENT_CREATED)
+                return;
+
+        data = new_possible_book_data_new (list_directory, file);
+
+        /* We add a timeout of several seconds so that we give time to the whole
+         * documentation to get installed. If we don't do this, we may end up
+         * trying to add the new book when even the *.devhelp2 index file is not
+         * installed yet.
+         */
+        data->timeout_id = g_timeout_add_seconds (NEW_POSSIBLE_BOOK_TIMEOUT_SECS,
+                                                  new_possible_book_timeout_cb,
+                                                  data);
+
+        priv->new_possible_books_data = g_slist_prepend (priv->new_possible_books_data, data);
+}
+
+static void
+monitor_books_directory (DhBookListDirectory *list_directory)
+{
+        GError *error = NULL;
+
+        g_assert (list_directory->priv->directory_monitor == NULL);
+        list_directory->priv->directory_monitor = g_file_monitor_directory (list_directory->priv->directory,
+                                                                            G_FILE_MONITOR_NONE,
+                                                                            NULL,
+                                                                            &error);
+
+        if (error != NULL) {
+                gchar *parse_name;
+
+                parse_name = g_file_get_parse_name (list_directory->priv->directory);
+
+                g_warning ("Failed to create file monitor on directory “%s”: %s",
+                           parse_name,
+                           error->message);
+
+                g_free (parse_name);
+                g_clear_error (&error);
+        }
+
+        if (list_directory->priv->directory_monitor != NULL) {
+                g_signal_connect_object (list_directory->priv->directory_monitor,
+                                         "changed",
+                                         G_CALLBACK (books_directory_changed_cb),
+                                         list_directory,
+                                         0);
+        }
+}
+
+static void
+find_books (DhBookListDirectory *list_directory)
+{
+        GFileEnumerator *enumerator;
+        GError *error = NULL;
+
+        enumerator = g_file_enumerate_children (list_directory->priv->directory,
+                                                G_FILE_ATTRIBUTE_STANDARD_NAME,
+                                                G_FILE_QUERY_INFO_NONE,
+                                                NULL,
+                                                &error);
+
+        if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+                g_clear_error (&error);
+                goto out;
+        }
+
+        if (error != NULL) {
+                gchar *parse_name;
+
+                parse_name = g_file_get_parse_name (list_directory->priv->directory);
+
+                g_warning ("Error when reading directory '%s': %s",
+                           parse_name,
+                           error->message);
+
+                g_free (parse_name);
+                g_clear_error (&error);
+                goto out;
+        }
+
+        monitor_books_directory (list_directory);
+
+        while (TRUE) {
+                GFile *book_directory = NULL;
+
+                g_file_enumerator_iterate (enumerator, NULL, &book_directory, NULL, &error);
+
+                if (error != NULL) {
+                        gchar *parse_name;
+
+                        parse_name = g_file_get_parse_name (list_directory->priv->directory);
+
+                        g_warning ("Error when enumerating directory '%s': %s",
+                                   parse_name,
+                                   error->message);
+
+                        g_free (parse_name);
+                        g_clear_error (&error);
+                        break;
+                }
+
+                if (book_directory == NULL)
+                        break;
+
+                create_book_from_book_directory (list_directory, book_directory);
+        }
+
+out:
+        g_clear_object (&enumerator);
+}
+
+static void
+set_directory (DhBookListDirectory *list_directory,
+               GFile               *directory)
+{
+        g_assert (list_directory->priv->directory == NULL);
+        g_return_if_fail (G_IS_FILE (directory));
+
+        list_directory->priv->directory = g_object_ref (directory);
+        find_books (list_directory);
+}
+
+static void
+dh_book_list_directory_get_property (GObject    *object,
+                                     guint       prop_id,
+                                     GValue     *value,
+                                     GParamSpec *pspec)
+{
+        DhBookListDirectory *list_directory = DH_BOOK_LIST_DIRECTORY (object);
+
+        switch (prop_id) {
+                case PROP_DIRECTORY:
+                        g_value_set_object (value, dh_book_list_directory_get_directory (list_directory));
+                        break;
+
+                default:
+                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                        break;
+        }
+}
+
+static void
+dh_book_list_directory_set_property (GObject      *object,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+        DhBookListDirectory *list_directory = DH_BOOK_LIST_DIRECTORY (object);
+
+        switch (prop_id) {
+                case PROP_DIRECTORY:
+                        set_directory (list_directory, g_value_get_object (value));
+                        break;
+
+                default:
+                        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                        break;
+        }
+}
+
+static void
+dh_book_list_directory_dispose (GObject *object)
+{
+        DhBookListDirectory *list_directory = DH_BOOK_LIST_DIRECTORY (object);
+
+        g_clear_object (&list_directory->priv->directory);
+        g_clear_object (&list_directory->priv->directory_monitor);
+
+        g_slist_free_full (list_directory->priv->new_possible_books_data, new_possible_book_data_free);
+        list_directory->priv->new_possible_books_data = NULL;
+
+        G_OBJECT_CLASS (dh_book_list_directory_parent_class)->dispose (object);
+}
+
+static void
+dh_book_list_directory_finalize (GObject *object)
+{
+        DhBookListDirectory *list_directory = DH_BOOK_LIST_DIRECTORY (object);
+
+        instances = g_list_remove (instances, list_directory);
+
+        G_OBJECT_CLASS (dh_book_list_directory_parent_class)->finalize (object);
+}
+
+static void
+dh_book_list_directory_class_init (DhBookListDirectoryClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = dh_book_list_directory_get_property;
+        object_class->set_property = dh_book_list_directory_set_property;
+        object_class->dispose = dh_book_list_directory_dispose;
+        object_class->finalize = dh_book_list_directory_finalize;
+
+        /**
+         * DhBookListDirectory:directory:
+         *
+         * The directory, as a #GFile, containing a set of Devhelp books.
+         *
+         * Since: 3.30
+         */
+        properties[PROP_DIRECTORY] =
+                g_param_spec_object ("directory",
+                                     "Directory",
+                                     "",
+                                     G_TYPE_FILE,
+                                     G_PARAM_READWRITE |
+                                     G_PARAM_CONSTRUCT_ONLY |
+                                     G_PARAM_STATIC_STRINGS);
+
+        g_object_class_install_properties (object_class, N_PROPERTIES, properties);
+}
+
+static void
+dh_book_list_directory_init (DhBookListDirectory *list_directory)
+{
+        list_directory->priv = dh_book_list_directory_get_instance_private (list_directory);
+
+        instances = g_list_prepend (instances, list_directory);
+}
+
+/**
+ * dh_book_list_directory_new:
+ * @directory: the #DhBookListDirectory:directory.
+ *
+ * Returns a #DhBookListDirectory for @directory.
+ *
+ * If a #DhBookListDirectory instance is still alive for @directory (according
+ * to g_file_equal()), the same instance is returned with the reference count
+ * increased by one, to avoid data duplication. If no #DhBookListDirectory
+ * instance already exists for @directory, this function returns a new instance
+ * with a reference count of one (so it's the responsibility of the caller to
+ * keep the object alive if wanted, to avoid destroying and re-creating the same
+ * #DhBookListDirectory repeatedly).
+ *
+ * Returns: (transfer full): a #DhBookListDirectory for @directory.
+ * Since: 3.30
+ */
+DhBookListDirectory *
+dh_book_list_directory_new (GFile *directory)
+{
+        GList *l;
+
+        g_return_val_if_fail (G_IS_FILE (directory), NULL);
+
+        for (l = instances; l != NULL; l = l->next) {
+                DhBookListDirectory *cur_list_directory = DH_BOOK_LIST_DIRECTORY (l->data);
+
+                if (cur_list_directory->priv->directory != NULL &&
+                    g_file_equal (cur_list_directory->priv->directory, directory))
+                        return g_object_ref (cur_list_directory);
+        }
+
+        return g_object_new (DH_TYPE_BOOK_LIST_DIRECTORY,
+                             "directory", directory,
+                             NULL);
+}
+
+/**
+ * dh_book_list_directory_get_directory:
+ * @list_directory: a #DhBookListDirectory.
+ *
+ * Returns: (transfer none): the #DhBookListDirectory:directory.
+ * Since: 3.30
+ */
+GFile *
+dh_book_list_directory_get_directory (DhBookListDirectory *list_directory)
+{
+        g_return_val_if_fail (DH_IS_BOOK_LIST_DIRECTORY (list_directory), NULL);
+
+        return list_directory->priv->directory;
+}
diff --git a/devhelp/dh-book-list-directory.h b/devhelp/dh-book-list-directory.h
new file mode 100644
index 0000000..1aade54
--- /dev/null
+++ b/devhelp/dh-book-list-directory.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
+/*
+ * This file is part of Devhelp.
+ *
+ * Copyright (C) 2018 Sébastien Wilmet <swilmet gnome org>
+ *
+ * Devhelp 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 3 of the License,
+ * or (at your option) any later version.
+ *
+ * Devhelp 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 Devhelp.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DH_BOOK_LIST_DIRECTORY_H
+#define DH_BOOK_LIST_DIRECTORY_H
+
+#include <gio/gio.h>
+#include <devhelp/dh-book-list.h>
+
+G_BEGIN_DECLS
+
+#define DH_TYPE_BOOK_LIST_DIRECTORY             (dh_book_list_directory_get_type ())
+#define DH_BOOK_LIST_DIRECTORY(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
DH_TYPE_BOOK_LIST_DIRECTORY, DhBookListDirectory))
+#define DH_BOOK_LIST_DIRECTORY_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), 
DH_TYPE_BOOK_LIST_DIRECTORY, DhBookListDirectoryClass))
+#define DH_IS_BOOK_LIST_DIRECTORY(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
DH_TYPE_BOOK_LIST_DIRECTORY))
+#define DH_IS_BOOK_LIST_DIRECTORY_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), 
DH_TYPE_BOOK_LIST_DIRECTORY))
+#define DH_BOOK_LIST_DIRECTORY_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), 
DH_TYPE_BOOK_LIST_DIRECTORY, DhBookListDirectoryClass))
+
+typedef struct _DhBookListDirectory         DhBookListDirectory;
+typedef struct _DhBookListDirectoryClass    DhBookListDirectoryClass;
+typedef struct _DhBookListDirectoryPrivate  DhBookListDirectoryPrivate;
+
+struct _DhBookListDirectory {
+        DhBookList parent;
+
+        DhBookListDirectoryPrivate *priv;
+};
+
+struct _DhBookListDirectoryClass {
+        DhBookListClass parent_class;
+
+        /* Padding for future expansion */
+        gpointer padding[12];
+};
+
+GType                   dh_book_list_directory_get_type         (void);
+
+DhBookListDirectory *   dh_book_list_directory_new              (GFile *directory);
+
+GFile *                 dh_book_list_directory_get_directory    (DhBookListDirectory *list_directory);
+
+G_END_DECLS
+
+#endif /* DH_BOOK_LIST_DIRECTORY_H */
diff --git a/devhelp/meson.build b/devhelp/meson.build
index d3093e2..57300f5 100644
--- a/devhelp/meson.build
+++ b/devhelp/meson.build
@@ -4,6 +4,7 @@ libdevhelp_public_headers = [
         'dh-book.h',
         'dh-book-list.h',
         'dh-book-list-builder.h',
+        'dh-book-list-directory.h',
         'dh-book-manager.h',
         'dh-book-tree.h',
         'dh-completion.h',
@@ -22,6 +23,7 @@ libdevhelp_public_c_files = [
         'dh-book.c',
         'dh-book-list.c',
         'dh-book-list-builder.c',
+        'dh-book-list-directory.c',
         'dh-book-manager.c',
         'dh-book-tree.c',
         'dh-completion.c',
diff --git a/docs/reference/devhelp-docs.xml b/docs/reference/devhelp-docs.xml
index 3e3f4ec..9486f15 100644
--- a/docs/reference/devhelp-docs.xml
+++ b/docs/reference/devhelp-docs.xml
@@ -30,6 +30,7 @@
       <title>The Data</title>
       <xi:include href="xml/dh-book-manager.xml"/>
       <xi:include href="xml/dh-book-list.xml"/>
+      <xi:include href="xml/dh-book-list-directory.xml"/>
       <xi:include href="xml/dh-book-list-builder.xml"/>
       <xi:include href="xml/dh-book.xml"/>
       <xi:include href="xml/dh-link.xml"/>
diff --git a/docs/reference/devhelp-sections.txt b/docs/reference/devhelp-sections.txt
index 0d9905c..7f1d466 100644
--- a/docs/reference/devhelp-sections.txt
+++ b/docs/reference/devhelp-sections.txt
@@ -89,6 +89,23 @@ dh_book_list_builder_get_type
 </SECTION>
 
 <SECTION>
+<FILE>dh-book-list-directory</FILE>
+DhBookListDirectory
+dh_book_list_directory_new
+dh_book_list_directory_get_directory
+<SUBSECTION Standard>
+DH_BOOK_LIST_DIRECTORY
+DH_BOOK_LIST_DIRECTORY_CLASS
+DH_BOOK_LIST_DIRECTORY_GET_CLASS
+DH_IS_BOOK_LIST_DIRECTORY
+DH_IS_BOOK_LIST_DIRECTORY_CLASS
+DH_TYPE_BOOK_LIST_DIRECTORY
+DhBookListDirectoryClass
+DhBookListDirectoryPrivate
+dh_book_list_directory_get_type
+</SECTION>
+
+<SECTION>
 <FILE>dh-book-manager</FILE>
 DhBookManager
 dh_book_manager_new
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 5649132..ad19b86 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -8,6 +8,7 @@ devhelp/dh-assistant-view.c
 devhelp/dh-book.c
 devhelp/dh-book-list-builder.c
 devhelp/dh-book-list.c
+devhelp/dh-book-list-directory.c
 devhelp/dh-book-list-simple.c
 devhelp/dh-book-manager.c
 devhelp/dh-book-tree.c


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]