[tepl] MetadataStore: start to create class



commit 2b14b8cfd81423cbfd622564767c4e84af5e5544
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Thu Apr 16 23:36:49 2020 +0200

    MetadataStore: start to create class

 docs/reference/tepl-docs.xml      |   5 +
 docs/reference/tepl-sections.txt  |  16 ++
 po/POTFILES.in                    |   2 +
 tepl/meson.build                  |   4 +
 tepl/tepl-metadata-store-loader.c | 380 ++++++++++++++++++++++++++++++++++++++
 tepl/tepl-metadata-store-loader.h |  34 ++++
 tepl/tepl-metadata-store.c        | 304 ++++++++++++++++++++++++++++++
 tepl/tepl-metadata-store.h        |  86 +++++++++
 tepl/tepl.h                       |   1 +
 9 files changed, 832 insertions(+)
---
diff --git a/docs/reference/tepl-docs.xml b/docs/reference/tepl-docs.xml
index 73222f9..3a00cdb 100644
--- a/docs/reference/tepl-docs.xml
+++ b/docs/reference/tepl-docs.xml
@@ -49,6 +49,11 @@
       <xi:include href="xml/io-error-info-bars.xml"/>
     </chapter>
 
+    <chapter id="file-metadata">
+      <title>File Metadata</title>
+      <xi:include href="xml/metadata-store.xml"/>
+    </chapter>
+
     <chapter id="code-folding">
       <title>Code Folding</title>
       <xi:include href="xml/fold-region.xml"/>
diff --git a/docs/reference/tepl-sections.txt b/docs/reference/tepl-sections.txt
index 985b5d0..eb65833 100644
--- a/docs/reference/tepl-sections.txt
+++ b/docs/reference/tepl-sections.txt
@@ -290,6 +290,22 @@ tepl_iter_get_line_indentation
 tepl_menu_shell_append_edit_actions
 </SECTION>
 
+<SECTION>
+<FILE>metadata-store</FILE>
+TeplMetadataStore
+tepl_metadata_store_get_singleton
+<SUBSECTION Standard>
+TEPL_IS_METADATA_STORE
+TEPL_IS_METADATA_STORE_CLASS
+TEPL_METADATA_STORE
+TEPL_METADATA_STORE_CLASS
+TEPL_METADATA_STORE_GET_CLASS
+TEPL_TYPE_METADATA_STORE
+TeplMetadataStoreClass
+TeplMetadataStorePrivate
+tepl_metadata_store_get_type
+</SECTION>
+
 <SECTION>
 <FILE>tab</FILE>
 TeplTab
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6747813..35f7f07 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -19,6 +19,8 @@ tepl/tepl-init.c
 tepl/tepl-io-error-info-bars.c
 tepl/tepl-iter.c
 tepl/tepl-menu-shell.c
+tepl/tepl-metadata-store.c
+tepl/tepl-metadata-store-loader.c
 tepl/tepl-notebook.c
 tepl/tepl-signal-group.c
 tepl/tepl-tab.c
diff --git a/tepl/meson.build b/tepl/meson.build
index da9d786..59fd1be 100644
--- a/tepl/meson.build
+++ b/tepl/meson.build
@@ -17,6 +17,7 @@ tepl_public_headers = [
   'tepl-io-error-info-bars.h',
   'tepl-iter.h',
   'tepl-menu-shell.h',
+  'tepl-metadata-store.h',
   'tepl-notebook.h',
   'tepl-tab.h',
   'tepl-tab-group.h',
@@ -43,6 +44,7 @@ tepl_public_c_files = [
   'tepl-io-error-info-bars.c',
   'tepl-iter.c',
   'tepl-menu-shell.c',
+  'tepl-metadata-store.c',
   'tepl-notebook.c',
   'tepl-tab.c',
   'tepl-tab-group.c',
@@ -59,6 +61,7 @@ TEPL_PRIVATE_HEADERS = [
   'tepl-file-content.h',
   'tepl-file-content-loader.h',
   'tepl-io-error-info-bar.h',
+  'tepl-metadata-store-loader.h',
   'tepl-progress-info-bar.h',
   'tepl-signal-group.h',
   'tepl-tab-saving.h'
@@ -71,6 +74,7 @@ tepl_private_c_files = [
   'tepl-file-content.c',
   'tepl-file-content-loader.c',
   'tepl-io-error-info-bar.c',
+  'tepl-metadata-store-loader.c',
   'tepl-progress-info-bar.c',
   'tepl-signal-group.c',
   'tepl-tab-saving.c'
diff --git a/tepl/tepl-metadata-store-loader.c b/tepl/tepl-metadata-store-loader.c
new file mode 100644
index 0000000..cfbf091
--- /dev/null
+++ b/tepl/tepl-metadata-store-loader.c
@@ -0,0 +1,380 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2020 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "tepl-metadata-store-loader.h"
+#include <glib/gi18n-lib.h>
+#include "tepl-file-metadata.h"
+#include "tepl-utils.h"
+
+typedef struct _ParsingData ParsingData;
+struct _ParsingData
+{
+       GHashTable *hash_table;
+
+       gchar *cur_document_uri;
+       TeplFileMetadata *cur_file_metadata;
+
+       guint metadata_element_open : 1;
+       guint document_element_open : 1;
+};
+
+static ParsingData *
+parsing_data_new (GHashTable *hash_table)
+{
+       ParsingData *parsing_data;
+
+       parsing_data = g_new0 (ParsingData, 1);
+       parsing_data->hash_table = g_hash_table_ref (hash_table);
+
+       return parsing_data;
+}
+
+static void
+parsing_data_check_invariants (ParsingData *parsing_data)
+{
+       if (!parsing_data->metadata_element_open)
+       {
+               g_assert (!parsing_data->document_element_open);
+               g_assert (parsing_data->cur_document_uri == NULL);
+               g_assert (parsing_data->cur_file_metadata == NULL);
+               return;
+       }
+
+       if (!parsing_data->document_element_open)
+       {
+               g_assert (parsing_data->cur_document_uri == NULL);
+               g_assert (parsing_data->cur_file_metadata == NULL);
+               return;
+       }
+
+       g_assert (parsing_data->cur_document_uri != NULL);
+       g_assert (parsing_data->cur_file_metadata != NULL);
+}
+
+static void
+parsing_data_free (ParsingData *parsing_data)
+{
+       if (parsing_data != NULL)
+       {
+               g_hash_table_unref (parsing_data->hash_table);
+               g_free (parsing_data->cur_document_uri);
+               g_clear_object (&parsing_data->cur_file_metadata);
+
+               g_free (parsing_data);
+       }
+}
+
+/* <metadata> */
+static void
+parse_metadata_element (GMarkupParseContext  *context,
+                       const gchar          *element_name,
+                       ParsingData          *parsing_data,
+                       GError              **error)
+{
+       g_assert (!parsing_data->metadata_element_open);
+
+       if (!g_str_equal (element_name, "metadata"))
+       {
+               g_set_error (error,
+                            G_MARKUP_ERROR,
+                            G_MARKUP_ERROR_INVALID_CONTENT,
+                            /* Translators: do not translate <metadata>. */
+                            _("The XML file must start with a <metadata> element, not “%s”."),
+                            element_name);
+               return;
+       }
+
+       parsing_data->metadata_element_open = TRUE;
+}
+
+/* <document uri="..." atime="..."> */
+static void
+parse_document_element (GMarkupParseContext  *context,
+                       const gchar          *element_name,
+                       const gchar         **attribute_names,
+                       const gchar         **attribute_values,
+                       ParsingData          *parsing_data,
+                       GError              **error)
+{
+       gboolean got_uri = FALSE;
+       gboolean got_atime = FALSE;
+       gint attr_num;
+
+       g_assert (parsing_data->metadata_element_open);
+       g_assert (!parsing_data->document_element_open);
+       g_assert (parsing_data->cur_document_uri == NULL);
+       g_assert (parsing_data->cur_file_metadata == NULL);
+
+       if (!g_str_equal (element_name, "document"))
+       {
+               g_set_error (error,
+                            G_MARKUP_ERROR,
+                            G_MARKUP_ERROR_INVALID_CONTENT,
+                            /* Translators: do not translate <document>. */
+                            _("Expected a <document> element, got “%s” instead."),
+                            element_name);
+               return;
+       }
+
+       parsing_data->cur_file_metadata = tepl_file_metadata_new ();
+
+       for (attr_num = 0; attribute_names[attr_num] != NULL; attr_num++)
+       {
+               const gchar *cur_attr_name = attribute_names[attr_num];
+               const gchar *cur_attr_value = attribute_values[attr_num];
+
+               if (!got_uri && g_str_equal (cur_attr_name, "uri"))
+               {
+                       parsing_data->cur_document_uri = g_strdup (cur_attr_value);
+                       got_uri = TRUE;
+               }
+               else if (!got_atime && g_str_equal (cur_attr_name, "atime"))
+               {
+                       if (!_tepl_file_metadata_set_atime_str (parsing_data->cur_file_metadata,
+                                                               cur_attr_value))
+                       {
+                               g_set_error (error,
+                                            G_MARKUP_ERROR,
+                                            G_MARKUP_ERROR_INVALID_CONTENT,
+                                            /* Translators: do not translate “atime”. */
+                                            _("Failed to parse the “atime” attribute value “%s”."),
+                                            cur_attr_value);
+                               return;
+                       }
+
+                       got_atime = TRUE;
+               }
+       }
+
+       if (!got_uri || !got_atime)
+       {
+               g_set_error_literal (error,
+                                    G_MARKUP_ERROR,
+                                    G_MARKUP_ERROR_MISSING_ATTRIBUTE,
+                                    /* Translators: do not translate <document>, “uri” and “atime”. */
+                                    _("The <document> element must contain the “uri” and “atime” 
attributes."));
+       }
+
+       parsing_data->document_element_open = TRUE;
+}
+
+/* <entry key="..." value="..." /> */
+static void
+parse_entry_element (GMarkupParseContext  *context,
+                    const gchar          *element_name,
+                    const gchar         **attribute_names,
+                    const gchar         **attribute_values,
+                    ParsingData          *parsing_data,
+                    GError              **error)
+{
+       const gchar *key = NULL;
+       const gchar *value = NULL;
+       gint attr_num;
+
+       g_assert (parsing_data->metadata_element_open);
+       g_assert (parsing_data->document_element_open);
+       g_assert (parsing_data->cur_file_metadata != NULL);
+
+       if (!g_str_equal (element_name, "entry"))
+       {
+               g_set_error (error,
+                            G_MARKUP_ERROR,
+                            G_MARKUP_ERROR_INVALID_CONTENT,
+                            /* Translators: do not translate <entry>. */
+                            _("Expected an <entry> element, got “%s” instead."),
+                            element_name);
+               return;
+       }
+
+       for (attr_num = 0; attribute_names[attr_num] != NULL; attr_num++)
+       {
+               const gchar *cur_attr_name = attribute_names[attr_num];
+               const gchar *cur_attr_value = attribute_values[attr_num];
+
+               if (key == NULL && g_str_equal (cur_attr_name, "key"))
+               {
+                       key = cur_attr_value;
+               }
+               else if (value == NULL && g_str_equal (cur_attr_name, "value"))
+               {
+                       value = cur_attr_value;
+               }
+       }
+
+       if (key == NULL || value == NULL)
+       {
+               g_set_error_literal (error,
+                                    G_MARKUP_ERROR,
+                                    G_MARKUP_ERROR_MISSING_ATTRIBUTE,
+                                    /* Translators: do not translate <entry>, “key” and “value”. */
+                                    _("The <entry> element is missing the “key” or “value” attribute."));
+               return;
+       }
+
+       _tepl_file_metadata_insert_entry (parsing_data->cur_file_metadata, key, value);
+}
+
+static void
+parser_start_element_cb (GMarkupParseContext  *context,
+                        const gchar          *element_name,
+                        const gchar         **attribute_names,
+                        const gchar         **attribute_values,
+                        gpointer              user_data,
+                        GError              **error)
+{
+       ParsingData *parsing_data = user_data;
+
+       g_return_if_fail (element_name != NULL);
+
+       parsing_data_check_invariants (parsing_data);
+
+       /* <metadata> */
+       if (!parsing_data->metadata_element_open)
+       {
+               parse_metadata_element (context,
+                                       element_name,
+                                       parsing_data,
+                                       error);
+               return;
+       }
+
+       /* <document uri="..." atime="..."> */
+       if (!parsing_data->document_element_open)
+       {
+               parse_document_element (context,
+                                       element_name,
+                                       attribute_names,
+                                       attribute_values,
+                                       parsing_data,
+                                       error);
+               return;
+       }
+
+       /* <entry key="..." value="..." /> */
+       parse_entry_element (context,
+                            element_name,
+                            attribute_names,
+                            attribute_values,
+                            parsing_data,
+                            error);
+}
+
+static void
+insert_document_to_hash_table (ParsingData *parsing_data)
+{
+       g_assert (parsing_data->document_element_open);
+       parsing_data_check_invariants (parsing_data);
+
+       g_hash_table_replace (parsing_data->hash_table,
+                             g_file_new_for_uri (parsing_data->cur_document_uri),
+                             parsing_data->cur_file_metadata);
+
+       g_free (parsing_data->cur_document_uri);
+       parsing_data->cur_document_uri = NULL;
+       parsing_data->cur_file_metadata = NULL;
+
+       parsing_data->document_element_open = FALSE;
+}
+
+static void
+parser_end_element_cb (GMarkupParseContext  *context,
+                      const gchar          *element_name,
+                      gpointer              user_data,
+                      GError              **error)
+{
+       ParsingData *parsing_data = user_data;
+
+       g_return_if_fail (element_name != NULL);
+
+       /* </document> */
+       if (g_str_equal (element_name, "document"))
+       {
+               g_return_if_fail (parsing_data->document_element_open);
+               insert_document_to_hash_table (parsing_data);
+       }
+}
+
+static void
+parse_xml_file_content (GBytes      *xml_file_bytes,
+                       GHashTable  *hash_table,
+                       GError     **error)
+{
+       GMarkupParser parser = { parser_start_element_cb, parser_end_element_cb, NULL, NULL, NULL };
+       GMarkupParseContext *parse_context;
+       ParsingData *parsing_data;
+       gboolean ok;
+
+       parsing_data = parsing_data_new (hash_table);
+       parse_context = g_markup_parse_context_new (&parser, 0, parsing_data, NULL);
+       ok = g_markup_parse_context_parse (parse_context,
+                                          g_bytes_get_data (xml_file_bytes, NULL),
+                                          g_bytes_get_size (xml_file_bytes),
+                                          error);
+       if (ok)
+       {
+               g_markup_parse_context_end_parse (parse_context, error);
+       }
+
+       g_markup_parse_context_free (parse_context);
+       parsing_data_free (parsing_data);
+}
+
+gboolean
+_tepl_metadata_store_loader (GFile       *from_file,
+                            GHashTable  *hash_table,
+                            GError     **error)
+{
+       GBytes *bytes;
+       GError *my_error = NULL;
+       gboolean ok = TRUE;
+
+       g_return_val_if_fail (G_IS_FILE (from_file), FALSE);
+       g_return_val_if_fail (hash_table != NULL, FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+       bytes = g_file_load_bytes (from_file, NULL, NULL, &my_error);
+
+       /* If the XML file has not yet been created, e.g. on the first run of
+        * the application.
+        */
+       if (g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+       {
+               g_clear_error (&my_error);
+               goto out;
+       }
+
+       if (my_error != NULL)
+       {
+               g_propagate_error (error, my_error);
+               ok = FALSE;
+               goto out;
+       }
+
+       parse_xml_file_content (bytes, hash_table, &my_error);
+       if (my_error != NULL)
+       {
+               g_propagate_error (error, my_error);
+               ok = FALSE;
+       }
+
+out:
+       g_bytes_unref (bytes);
+       return ok;
+}
diff --git a/tepl/tepl-metadata-store-loader.h b/tepl/tepl-metadata-store-loader.h
new file mode 100644
index 0000000..fcde6cc
--- /dev/null
+++ b/tepl/tepl-metadata-store-loader.h
@@ -0,0 +1,34 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2020 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEPL_METADATA_STORE_LOADER_H
+#define TEPL_METADATA_STORE_LOADER_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+G_GNUC_INTERNAL
+gboolean       _tepl_metadata_store_loader     (GFile       *from_file,
+                                                GHashTable  *hash_table,
+                                                GError     **error);
+
+G_END_DECLS
+
+#endif /* TEPL_METADATA_STORE_LOADER_H */
diff --git a/tepl/tepl-metadata-store.c b/tepl/tepl-metadata-store.c
new file mode 100644
index 0000000..f1cc515
--- /dev/null
+++ b/tepl/tepl-metadata-store.c
@@ -0,0 +1,304 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2020 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tepl-metadata-store.h"
+#include "tepl-metadata-store-loader.h"
+#include "tepl-utils.h"
+
+/**
+ * SECTION:metadata-store
+ * @Title: TeplMetadataStore
+ * @Short_description: To store file metadata on disk
+ */
+
+/* This code is inspired by TeplMetadataManager, which was itself a modified
+ * version of GeditMetadataManager, coming from gedit:
+ *
+ * Copyright 2003-2007 - Paolo Maggi
+ *
+ * The XML format is the same. TeplMetadataStore can read a file generated by
+ * TeplMetadataManager; but the reverse is maybe not true, it hasn't been
+ * tested. Not tested either with the older GeditMetadataManager.
+ *
+ * A better implementation would be to use a database, so that several processes
+ * can read and write to it at the same time, to be able to share metadata
+ * between apps.
+ */
+
+struct _TeplMetadataStorePrivate
+{
+       /* Keys: GFile *
+        * Values: TeplFileMetadata *
+        * Never NULL.
+        */
+       GHashTable *hash_table;
+
+       guint modified : 1;
+};
+
+/* TeplMetadataStore is a singleton. */
+static TeplMetadataStore *singleton = NULL;
+
+#define DEFAULT_MAX_NUMBER_OF_LOCATIONS (1000)
+
+G_DEFINE_TYPE_WITH_PRIVATE (TeplMetadataStore, tepl_metadata_store, G_TYPE_OBJECT)
+
+static void
+tepl_metadata_store_finalize (GObject *object)
+{
+       TeplMetadataStore *store = TEPL_METADATA_STORE (object);
+
+       if (singleton == store)
+       {
+               singleton = NULL;
+       }
+
+       g_hash_table_unref (store->priv->hash_table);
+
+       G_OBJECT_CLASS (tepl_metadata_store_parent_class)->finalize (object);
+}
+
+static void
+tepl_metadata_store_class_init (TeplMetadataStoreClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tepl_metadata_store_finalize;
+}
+
+static void
+tepl_metadata_store_init (TeplMetadataStore *store)
+{
+       store->priv = tepl_metadata_store_get_instance_private (store);
+
+       store->priv->hash_table = g_hash_table_new_full (g_file_hash,
+                                                        (GEqualFunc) g_file_equal,
+                                                        g_object_unref,
+                                                        g_object_unref);
+}
+
+/**
+ * tepl_metadata_store_get_singleton:
+ *
+ * Returns: (transfer none): the #TeplMetadataStore singleton instance.
+ * Since: 5.0
+ */
+TeplMetadataStore *
+tepl_metadata_store_get_singleton (void)
+{
+       if (singleton == NULL)
+       {
+               singleton = g_object_new (TEPL_TYPE_METADATA_STORE, NULL);
+       }
+
+       return singleton;
+}
+
+void
+_tepl_metadata_store_unref_singleton (void)
+{
+       if (singleton != NULL)
+       {
+               g_object_unref (singleton);
+       }
+
+       /* singleton is not set to NULL here, it is set to NULL in
+        * tepl_metadata_store_finalize() (i.e. when we are sure that the ref
+        * count reaches 0).
+        */
+}
+
+void
+tepl_metadata_store_trim (TeplMetadataStore *store,
+                         gint               max_number_of_locations)
+{
+       guint my_max_number_of_locations;
+
+       g_return_if_fail (TEPL_IS_METADATA_STORE (store));
+       g_return_if_fail (max_number_of_locations >= -1);
+
+       if (max_number_of_locations == -1)
+       {
+               my_max_number_of_locations = DEFAULT_MAX_NUMBER_OF_LOCATIONS;
+       }
+       else
+       {
+               my_max_number_of_locations = max_number_of_locations;
+       }
+
+       while (g_hash_table_size (store->priv->hash_table) > my_max_number_of_locations)
+       {
+               GHashTableIter iter;
+               gpointer key;
+               gpointer value;
+               GFile *oldest_location = NULL;
+               TeplFileMetadata *oldest_file_metadata = NULL;
+
+               g_hash_table_iter_init (&iter, store->priv->hash_table);
+               while (g_hash_table_iter_next (&iter, &key, &value))
+               {
+                       GFile *location = key;
+                       TeplFileMetadata *file_metadata = value;
+
+                       if (oldest_location == NULL ||
+                           _tepl_file_metadata_compare_atime (file_metadata, oldest_file_metadata) < 0)
+                       {
+                               oldest_location = location;
+                               oldest_file_metadata = file_metadata;
+                       }
+               }
+
+               g_hash_table_remove (store->priv->hash_table, oldest_location);
+               store->priv->modified = TRUE;
+       }
+}
+
+gboolean
+tepl_metadata_store_load (TeplMetadataStore  *store,
+                         GFile              *from_file,
+                         GError            **error)
+{
+       g_return_val_if_fail (TEPL_IS_METADATA_STORE (store), FALSE);
+       g_return_val_if_fail (G_IS_FILE (from_file), FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+       return _tepl_metadata_store_loader (from_file,
+                                           store->priv->hash_table,
+                                           error);
+}
+
+static GBytes *
+to_string (TeplMetadataStore *store)
+{
+       GString *string;
+       GHashTableIter iter;
+       gpointer key;
+       gpointer value;
+
+       string = g_string_new (NULL);
+       g_string_append (string, "<metadata>\n");
+
+       g_hash_table_iter_init (&iter, store->priv->hash_table);
+       while (g_hash_table_iter_next (&iter, &key, &value))
+       {
+               GFile *location = key;
+               TeplFileMetadata *file_metadata = value;
+
+               _tepl_file_metadata_append_xml_to_string (file_metadata, location, string);
+       }
+
+       g_string_append (string, "</metadata>\n");
+
+       return g_string_free_to_bytes (string);
+}
+
+gboolean
+tepl_metadata_store_save (TeplMetadataStore  *store,
+                         GFile              *to_file,
+                         gboolean            trim,
+                         GError            **error)
+{
+       GBytes *bytes;
+       gboolean ok;
+
+       g_return_val_if_fail (TEPL_IS_METADATA_STORE (store), FALSE);
+       g_return_val_if_fail (G_IS_FILE (to_file), FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+       if (trim)
+       {
+               tepl_metadata_store_trim (store, -1);
+       }
+
+       if (!store->priv->modified)
+       {
+               return TRUE;
+       }
+
+       if (!tepl_utils_create_parent_directories (to_file, NULL, error))
+       {
+               return FALSE;
+       }
+
+       bytes = to_string (store);
+
+       ok = g_file_replace_contents (to_file,
+                                     g_bytes_get_data (bytes, NULL),
+                                     g_bytes_get_size (bytes),
+                                     NULL,
+                                     FALSE,
+                                     G_FILE_CREATE_NONE,
+                                     NULL,
+                                     NULL,
+                                     error);
+
+       if (ok)
+       {
+               store->priv->modified = FALSE;
+       }
+
+       g_bytes_unref (bytes);
+       return ok;
+}
+
+void
+tepl_metadata_store_load_file_metadata (TeplMetadataStore *store,
+                                       GFile             *location,
+                                       TeplFileMetadata  *file_metadata)
+{
+       TeplFileMetadata *file_metadata_from_store;
+
+       g_return_if_fail (TEPL_IS_METADATA_STORE (store));
+       g_return_if_fail (G_IS_FILE (location));
+       g_return_if_fail (TEPL_IS_FILE_METADATA (file_metadata));
+
+       file_metadata_from_store = g_hash_table_lookup (store->priv->hash_table, location);
+
+       if (file_metadata_from_store != NULL)
+       {
+               _tepl_file_metadata_copy_into (file_metadata_from_store, file_metadata);
+       }
+}
+
+void
+tepl_metadata_store_save_file_metadata (TeplMetadataStore *store,
+                                       GFile             *location,
+                                       TeplFileMetadata  *file_metadata)
+{
+       TeplFileMetadata *file_metadata_from_store;
+
+       g_return_if_fail (TEPL_IS_METADATA_STORE (store));
+       g_return_if_fail (G_IS_FILE (location));
+       g_return_if_fail (TEPL_IS_FILE_METADATA (file_metadata));
+
+       file_metadata_from_store = g_hash_table_lookup (store->priv->hash_table, location);
+
+       if (file_metadata_from_store == NULL)
+       {
+               file_metadata_from_store = tepl_file_metadata_new ();
+
+               g_hash_table_replace (store->priv->hash_table,
+                                     g_object_ref (location),
+                                     file_metadata_from_store);
+       }
+
+       _tepl_file_metadata_copy_into (file_metadata, file_metadata_from_store);
+
+       store->priv->modified = TRUE;
+}
diff --git a/tepl/tepl-metadata-store.h b/tepl/tepl-metadata-store.h
new file mode 100644
index 0000000..6cba6b1
--- /dev/null
+++ b/tepl/tepl-metadata-store.h
@@ -0,0 +1,86 @@
+/*
+ * This file is part of Tepl, a text editor library.
+ *
+ * Copyright 2020 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * Tepl 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.1 of the License, or (at your
+ * option) any later version.
+ *
+ * Tepl 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 this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TEPL_METADATA_STORE_H
+#define TEPL_METADATA_STORE_H
+
+#if !defined (TEPL_H_INSIDE) && !defined (TEPL_COMPILATION)
+#error "Only <tepl/tepl.h> can be included directly."
+#endif
+
+#include <gio/gio.h>
+#include <tepl/tepl-file-metadata.h>
+
+G_BEGIN_DECLS
+
+#define TEPL_TYPE_METADATA_STORE             (tepl_metadata_store_get_type ())
+#define TEPL_METADATA_STORE(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEPL_TYPE_METADATA_STORE, 
TeplMetadataStore))
+#define TEPL_METADATA_STORE_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), TEPL_TYPE_METADATA_STORE, 
TeplMetadataStoreClass))
+#define TEPL_IS_METADATA_STORE(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEPL_TYPE_METADATA_STORE))
+#define TEPL_IS_METADATA_STORE_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), TEPL_TYPE_METADATA_STORE))
+#define TEPL_METADATA_STORE_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), TEPL_TYPE_METADATA_STORE, 
TeplMetadataStoreClass))
+
+typedef struct _TeplMetadataStore         TeplMetadataStore;
+typedef struct _TeplMetadataStoreClass    TeplMetadataStoreClass;
+typedef struct _TeplMetadataStorePrivate  TeplMetadataStorePrivate;
+
+struct _TeplMetadataStore
+{
+       GObject parent;
+
+       TeplMetadataStorePrivate *priv;
+};
+
+struct _TeplMetadataStoreClass
+{
+       GObjectClass parent_class;
+
+       gpointer padding[12];
+};
+
+GType                  tepl_metadata_store_get_type            (void);
+
+TeplMetadataStore *    tepl_metadata_store_get_singleton       (void);
+
+G_GNUC_INTERNAL
+void                   _tepl_metadata_store_unref_singleton    (void);
+
+void                   tepl_metadata_store_trim                (TeplMetadataStore *store,
+                                                                gint               max_number_of_locations);
+
+gboolean               tepl_metadata_store_load                (TeplMetadataStore  *store,
+                                                                GFile              *from_file,
+                                                                GError            **error);
+
+gboolean               tepl_metadata_store_save                (TeplMetadataStore  *store,
+                                                                GFile              *to_file,
+                                                                gboolean            trim,
+                                                                GError            **error);
+
+void                   tepl_metadata_store_load_file_metadata  (TeplMetadataStore *store,
+                                                                GFile             *location,
+                                                                TeplFileMetadata  *file_metadata);
+
+void                   tepl_metadata_store_save_file_metadata  (TeplMetadataStore *store,
+                                                                GFile             *location,
+                                                                TeplFileMetadata  *file_metadata);
+
+G_END_DECLS
+
+#endif /* TEPL_METADATA_STORE_H */
diff --git a/tepl/tepl.h b/tepl/tepl.h
index 2ccad32..d9e59d0 100644
--- a/tepl/tepl.h
+++ b/tepl/tepl.h
@@ -43,6 +43,7 @@
 #include <tepl/tepl-io-error-info-bars.h>
 #include <tepl/tepl-iter.h>
 #include <tepl/tepl-menu-shell.h>
+#include <tepl/tepl-metadata-store.h>
 #include <tepl/tepl-notebook.h>
 #include <tepl/tepl-tab.h>
 #include <tepl/tepl-tab-group.h>


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