/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
/* vim:set et sts=4: */
/* bus - The Input Bus
* Copyright (C) 2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
* Copyright (C) 2017 Red Hat, Inc.
*
* This library 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.
*
* This library 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <glib.h>
#include <glib/gstdio.h>
#include "ibusemoji.h"
#include "ibusinternal.h"
#define IBUS_EMOJI_DATA_MAGIC "IBusEmojiData"
#define IBUS_EMOJI_DATA_VERSION (5)
enum {
PROP_0 = 0,
PROP_EMOJI,
PROP_ANNOTATIONS,
PROP_DESCRIPTION,
PROP_CATEGORY,
};
struct _IBusEmojiDataPrivate {
gchar *emoji;
GSList *annotations;
gchar *description;
gchar *category;
};
#define IBUS_EMOJI_DATA_GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), \
IBUS_TYPE_EMOJI_DATA, \
IBusEmojiDataPrivate))
/* functions prototype */
static void ibus_emoji_data_set_property (IBusEmojiData *emoji,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void ibus_emoji_data_get_property (IBusEmojiData *emoji,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void ibus_emoji_data_destroy (IBusEmojiData *emoji);
static gboolean ibus_emoji_data_serialize (IBusEmojiData *emoji,
GVariantBuilder *builder);
static gint ibus_emoji_data_deserialize (IBusEmojiData *emoji,
GVariant *variant);
static gboolean ibus_emoji_data_copy (IBusEmojiData *emoji,
const IBusEmojiData *src);
G_DEFINE_TYPE (IBusEmojiData, ibus_emoji_data, IBUS_TYPE_SERIALIZABLE)
static void
ibus_emoji_data_class_init (IBusEmojiDataClass *class)
{
IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class);
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
IBusSerializableClass *serializable_class = IBUS_SERIALIZABLE_CLASS (class);
object_class->destroy = (IBusObjectDestroyFunc) ibus_emoji_data_destroy;
gobject_class->set_property =
(GObjectSetPropertyFunc) ibus_emoji_data_set_property;
gobject_class->get_property =
(GObjectGetPropertyFunc) ibus_emoji_data_get_property;
serializable_class->serialize =
(IBusSerializableSerializeFunc) ibus_emoji_data_serialize;
serializable_class->deserialize =
(IBusSerializableDeserializeFunc) ibus_emoji_data_deserialize;
serializable_class->copy =
(IBusSerializableCopyFunc) ibus_emoji_data_copy;
g_type_class_add_private (class, sizeof (IBusEmojiDataPrivate));
/* install properties */
/**
* IBusEmojiData:emoji:
*
* The emoji character
*/
g_object_class_install_property (gobject_class,
PROP_EMOJI,
g_param_spec_string ("emoji",
"emoji character",
"The emoji character UTF-8",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
/**
* IBusEmojiData:annotations: (transfer container) (element-type utf8):
*
* The emoji annotations
*/
g_object_class_install_property (gobject_class,
PROP_ANNOTATIONS,
g_param_spec_pointer ("annotations",
"emoji annotations",
"The emoji annotation list",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
/**
* IBusEmojiData:description:
*
* The emoji description
*/
g_object_class_install_property (gobject_class,
PROP_DESCRIPTION,
g_param_spec_string ("description",
"emoji description",
"The emoji description",
"",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
/**
* IBusEmojiData:category:
*
* The emoji category
*/
g_object_class_install_property (gobject_class,
PROP_CATEGORY,
g_param_spec_string ("category",
"emoji category",
"The emoji category",
"",
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}
static void
ibus_emoji_data_init (IBusEmojiData *emoji)
{
emoji->priv = IBUS_EMOJI_DATA_GET_PRIVATE (emoji);
}
static void
free_dict_words (gpointer list)
{
g_slist_free_full (list, g_free);
}
static void
ibus_emoji_data_destroy (IBusEmojiData *emoji)
{
g_clear_pointer (&emoji->priv->emoji, g_free);
g_clear_pointer (&emoji->priv->annotations, free_dict_words);
g_clear_pointer (&emoji->priv->description, g_free);
g_clear_pointer (&emoji->priv->category, g_free);
IBUS_OBJECT_CLASS (ibus_emoji_data_parent_class)->
destroy (IBUS_OBJECT (emoji));
}
static void
ibus_emoji_data_set_property (IBusEmojiData *emoji,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
switch (prop_id) {
case PROP_EMOJI:
g_assert (emoji->priv->emoji == NULL);
emoji->priv->emoji = g_value_dup_string (value);
break;
case PROP_ANNOTATIONS:
if (emoji->priv->annotations)
g_slist_free_full (emoji->priv->annotations, g_free);
emoji->priv->annotations =
g_slist_copy_deep (g_value_get_pointer (value),
(GCopyFunc) g_strdup, NULL);
break;
case PROP_DESCRIPTION:
g_free (emoji->priv->description);
emoji->priv->description = g_value_dup_string (value);
break;
case PROP_CATEGORY:
g_assert (emoji->priv->category == NULL);
emoji->priv->category = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (emoji, prop_id, pspec);
}
}
static void
ibus_emoji_data_get_property (IBusEmojiData *emoji,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
switch (prop_id) {
case PROP_EMOJI:
g_value_set_string (value, ibus_emoji_data_get_emoji (emoji));
break;
case PROP_ANNOTATIONS:
g_value_set_pointer (
value,
g_slist_copy_deep (ibus_emoji_data_get_annotations (emoji),
(GCopyFunc) g_strdup, NULL));
break;
case PROP_DESCRIPTION:
g_value_set_string (value, ibus_emoji_data_get_description (emoji));
break;
case PROP_CATEGORY:
g_value_set_string (value, ibus_emoji_data_get_category (emoji));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (emoji, prop_id, pspec);
}
}
static gboolean
ibus_emoji_data_serialize (IBusEmojiData *emoji,
GVariantBuilder *builder)
{
GSList *l;
gboolean retval = IBUS_SERIALIZABLE_CLASS (ibus_emoji_data_parent_class)->
serialize ((IBusSerializable *)emoji, builder);
g_return_val_if_fail (retval, FALSE);
#define NOTNULL(s) ((s) != NULL ? (s) : "")
/* If you will add a new property, you can append it at the end and
* you should not change the serialized order of name, longname,
* description, ... because the order is also used in other applications
* likes ibus-qt. */
g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->emoji));
g_variant_builder_add (builder, "u",
g_slist_length (emoji->priv->annotations));
for (l = emoji->priv->annotations; l != NULL; l = l->next) {
g_variant_builder_add (builder, "s", NOTNULL (l->data));
}
g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->description));
g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->category));
#undef NOTNULL
return TRUE;
}
static gint
ibus_emoji_data_deserialize (IBusEmojiData *emoji,
GVariant *variant)
{
guint length, i;
GSList *annotations = NULL;
gint retval = IBUS_SERIALIZABLE_CLASS (ibus_emoji_data_parent_class)->
deserialize ((IBusSerializable *)emoji, variant);
g_return_val_if_fail (retval, 0);
/* If you will add a new property, you can append it at the end and
* you should not change the serialized order of name, longname,
* description, ... because the order is also used in other applications
* likes ibus-qt. */
ibus_g_variant_get_child_string (variant, retval++,
&emoji->priv->emoji);
g_variant_get_child (variant, retval++, "u", &length);
for (i = 0; i < length; i++) {
gchar *s = NULL;
g_variant_get_child (variant, retval++, "s", &s);
annotations = g_slist_append (annotations, s);
}
emoji->priv->annotations = annotations;
ibus_g_variant_get_child_string (variant, retval++,
&emoji->priv->description);
ibus_g_variant_get_child_string (variant, retval++,
&emoji->priv->category);
return retval;
}
static gboolean
ibus_emoji_data_copy (IBusEmojiData *dest,
const IBusEmojiData *src)
{
gboolean retval = IBUS_SERIALIZABLE_CLASS (ibus_emoji_data_parent_class)->
copy ((IBusSerializable *)dest,
(IBusSerializable *)src);
g_return_val_if_fail (retval, FALSE);
dest->priv->emoji = g_strdup (src->priv->emoji);
dest->priv->annotations = g_slist_copy_deep (src->priv->annotations,
(GCopyFunc) g_strdup,
NULL);
dest->priv->description = g_strdup (src->priv->description);
dest->priv->category = g_strdup (src->priv->category);
return TRUE;
}
IBusEmojiData *
ibus_emoji_data_new (const gchar *first_property_name, ...)
{
va_list var_args;
IBusEmojiData *emoji;
g_assert (first_property_name != NULL);
va_start (var_args, first_property_name);
emoji = (IBusEmojiData *) g_object_new_valist (IBUS_TYPE_EMOJI_DATA,
first_property_name,
var_args);
va_end (var_args);
/* emoji is required. Other properties are set in class_init by default. */
g_assert (emoji->priv->emoji != NULL);
g_assert (emoji->priv->description != NULL);
g_assert (emoji->priv->category != NULL);
return emoji;
}
const gchar *
ibus_emoji_data_get_emoji (IBusEmojiData *emoji)
{
g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
return emoji->priv->emoji;
}
GSList *
ibus_emoji_data_get_annotations (IBusEmojiData *emoji)
{
g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
return emoji->priv->annotations;
}
void
ibus_emoji_data_set_annotations (IBusEmojiData *emoji,
GSList *annotations)
{
g_return_if_fail (IBUS_IS_EMOJI_DATA (emoji));
if (emoji->priv->annotations)
g_slist_free_full (emoji->priv->annotations, g_free);
emoji->priv->annotations = annotations;
}
const gchar *
ibus_emoji_data_get_description (IBusEmojiData *emoji)
{
g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
return emoji->priv->description;
}
void
ibus_emoji_data_set_description (IBusEmojiData *emoji,
const gchar *description)
{
g_return_if_fail (IBUS_IS_EMOJI_DATA (emoji));
g_free (emoji->priv->description);
emoji->priv->description = g_strdup (description);
}
const gchar *
ibus_emoji_data_get_category (IBusEmojiData *emoji)
{
g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
return emoji->priv->category;
}
static void
variant_foreach_add_emoji (IBusEmojiData *emoji,
GVariantBuilder *builder)
{
g_variant_builder_add (
builder, "v",
ibus_serializable_serialize (IBUS_SERIALIZABLE (emoji)));
}
static GVariant *
ibus_emoji_data_list_serialize (GSList *list)
{
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
g_slist_foreach (list, (GFunc) variant_foreach_add_emoji, &builder);
return g_variant_builder_end (&builder);
}
static GSList *
ibus_emoji_data_list_deserialize (GVariant *variant)
{
GSList *list = NULL;
GVariantIter iter;
GVariant *emoji_variant = NULL;
g_variant_iter_init (&iter, variant);
while (g_variant_iter_loop (&iter, "v", &emoji_variant)) {
IBusEmojiData *data =
IBUS_EMOJI_DATA (ibus_serializable_deserialize (emoji_variant));
list = g_slist_append (list, data);
g_clear_pointer (&emoji_variant, g_variant_unref);
}
return list;
}
void
ibus_emoji_dict_save (const gchar *path,
GHashTable *dict)
{
GList *values, *v;
GSList *list_for_save = NULL;
g_return_if_fail (path != NULL);
g_return_if_fail (dict != NULL);
values = g_hash_table_get_values (dict);
for (v = values; v; v = v->next) {
IBusEmojiData *data = v->data;
if (!IBUS_IS_EMOJI_DATA (data)) {
g_warning ("Your dict format of { annotation char, emoji GSList "
"} is no longer supported.\n"
"{ emoji char, IBusEmojiData GSList } is expected.");
return;
}
list_for_save = g_slist_append (list_for_save, data);
}
ibus_emoji_data_save (path, list_for_save);
}
GHashTable *
ibus_emoji_dict_load (const gchar *path)
{
GSList *list = ibus_emoji_data_load (path);
GSList *l;
GHashTable *dict = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
g_object_unref);
for (l = list; l; l = l->next) {
IBusEmojiData *data = l->data;
if (!IBUS_IS_EMOJI_DATA (data)) {
g_warning ("Your dict format is no longer supported.\n"
"Need to create the dictionaries again.");
return NULL;
}
g_hash_table_insert (dict,
g_strdup (ibus_emoji_data_get_emoji (data)),
g_object_ref_sink (data));
}
g_slist_free (list);
return dict;
}
IBusEmojiData *
ibus_emoji_dict_lookup (GHashTable *dict,
const gchar *emoji)
{
return (IBusEmojiData *) g_hash_table_lookup (dict, emoji);
}
void
ibus_emoji_data_save (const gchar *path,
GSList *list)
{
GVariant *variant;
const gchar *header = IBUS_EMOJI_DATA_MAGIC;
const guint16 version = IBUS_EMOJI_DATA_VERSION;
const gchar *contents;
gsize length;
gchar *dir;
GStatBuf buf = { 0, };
GError *error = NULL;
g_return_if_fail (path != NULL);
g_return_if_fail (list != NULL);
if (list->data == NULL) {
g_warning ("Failed to save IBus emoji data: Need a list data.");
return;
}
variant = g_variant_new ("(sqv)",
header,
version,
ibus_emoji_data_list_serialize (list));
contents = g_variant_get_data (variant);
length = g_variant_get_size (variant);
dir = g_path_get_dirname (path);
if (g_strcmp0 (dir, ".") != 0 && g_stat (dir, &buf) != 0) {
g_mkdir_with_parents (dir, 0777);
}
g_free (dir);
if (!g_file_set_contents (path, contents, length, &error)) {
g_warning ("Failed to save emoji dict %s: %s", path, error->message);
g_error_free (error);
}
g_variant_unref (variant);
}
GSList *
ibus_emoji_data_load (const gchar *path)
{
gchar *contents = NULL;
gsize length = 0;
GError *error = NULL;
GVariant *variant_table = NULL;
GVariant *variant = NULL;
const gchar *header = NULL;
guint16 version = 0;
GSList *retval = NULL;
if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
g_warning ("Emoji dict does not exist: %s", path);
goto out_load_cache;
}
if (!g_file_get_contents (path, &contents, &length, &error)) {
g_warning ("Failed to get dict content %s: %s", path, error->message);
g_error_free (error);
goto out_load_cache;
}
variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sq)"),
contents,
length,
FALSE,
NULL,
NULL);
if (variant_table == NULL) {
g_warning ("cache table is broken.");
goto out_load_cache;
}
g_variant_get (variant_table, "(&sq)", &header, &version);
if (g_strcmp0 (header, IBUS_EMOJI_DATA_MAGIC) != 0) {
g_warning ("cache is not IBusEmojiData.");
goto out_load_cache;
}
if (version > IBUS_EMOJI_DATA_VERSION) {
g_warning ("cache version is different: %u != %u",
version, IBUS_EMOJI_DATA_VERSION);
goto out_load_cache;
}
version = 0;
header = NULL;
g_variant_unref (variant_table);
variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sqv)"),
contents,
length,
FALSE,
NULL,
NULL);
if (variant_table == NULL) {
g_warning ("cache table is broken.");
goto out_load_cache;
}
g_variant_get (variant_table, "(&sqv)",
NULL,
NULL,
&variant);
if (variant == NULL) {
g_warning ("cache dict is broken.");
goto out_load_cache;
}
retval = ibus_emoji_data_list_deserialize (variant);
out_load_cache:
if (variant)
g_variant_unref (variant);
if (variant_table)
g_variant_unref (variant_table);
g_free (contents);
return retval;
}