1 /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 /* vim:set et sts=4: */
3 /* bus - The Input Bus
4  * Copyright (C) 2017-2019 Takao Fujiwara <takao.fujiwara1@gmail.com>
5  * Copyright (C) 2017-2019 Red Hat, Inc.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
20  * USA
21  */
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include <glib.h>
27 #include <glib/gstdio.h>
28 #include "ibusemoji.h"
29 #include "ibusinternal.h"
30 
31 #define IBUS_EMOJI_DATA_MAGIC "IBusEmojiData"
32 #define IBUS_EMOJI_DATA_VERSION (5)
33 
34 enum {
35     PROP_0 = 0,
36     PROP_EMOJI,
37     PROP_ANNOTATIONS,
38     PROP_DESCRIPTION,
39     PROP_CATEGORY,
40 };
41 
42 struct _IBusEmojiDataPrivate {
43     gchar      *emoji;
44     GSList     *annotations;
45     gchar      *description;
46     gchar      *category;
47 };
48 
49 #define IBUS_EMOJI_DATA_GET_PRIVATE(o)  \
50    ((IBusEmojiDataPrivate *)ibus_emoji_data_get_instance_private (o))
51 
52 /* functions prototype */
53 static void      ibus_emoji_data_set_property  (IBusEmojiData       *emoji,
54                                                 guint                prop_id,
55                                                 const GValue        *value,
56                                                 GParamSpec          *pspec);
57 static void      ibus_emoji_data_get_property  (IBusEmojiData       *emoji,
58                                                 guint                prop_id,
59                                                 GValue              *value,
60                                                 GParamSpec          *pspec);
61 static void      ibus_emoji_data_destroy       (IBusEmojiData       *emoji);
62 static gboolean  ibus_emoji_data_serialize     (IBusEmojiData       *emoji,
63                                                 GVariantBuilder     *builder);
64 static gint      ibus_emoji_data_deserialize   (IBusEmojiData       *emoji,
65                                                 GVariant            *variant);
66 static gboolean  ibus_emoji_data_copy          (IBusEmojiData       *emoji,
67                                                 const IBusEmojiData *src);
68 
G_DEFINE_TYPE_WITH_PRIVATE(IBusEmojiData,ibus_emoji_data,IBUS_TYPE_SERIALIZABLE)69 G_DEFINE_TYPE_WITH_PRIVATE (IBusEmojiData,
70                             ibus_emoji_data,
71                             IBUS_TYPE_SERIALIZABLE)
72 
73 static void
74 ibus_emoji_data_class_init (IBusEmojiDataClass *class)
75 {
76     IBusObjectClass *object_class = IBUS_OBJECT_CLASS (class);
77     GObjectClass *gobject_class = G_OBJECT_CLASS (class);
78     IBusSerializableClass *serializable_class = IBUS_SERIALIZABLE_CLASS (class);
79 
80     object_class->destroy = (IBusObjectDestroyFunc) ibus_emoji_data_destroy;
81     gobject_class->set_property =
82             (GObjectSetPropertyFunc) ibus_emoji_data_set_property;
83     gobject_class->get_property =
84             (GObjectGetPropertyFunc) ibus_emoji_data_get_property;
85     serializable_class->serialize   =
86             (IBusSerializableSerializeFunc) ibus_emoji_data_serialize;
87     serializable_class->deserialize =
88             (IBusSerializableDeserializeFunc) ibus_emoji_data_deserialize;
89     serializable_class->copy        =
90             (IBusSerializableCopyFunc) ibus_emoji_data_copy;
91 
92     /* install properties */
93     /**
94      * IBusEmojiData:emoji:
95      *
96      * The emoji character
97      */
98     g_object_class_install_property (gobject_class,
99                     PROP_EMOJI,
100                     g_param_spec_string ("emoji",
101                         "emoji character",
102                         "The emoji character UTF-8",
103                         NULL,
104                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
105 
106     /**
107      * IBusEmojiData:annotations: (transfer container) (element-type utf8):
108      *
109      * The emoji annotations
110      */
111     g_object_class_install_property (gobject_class,
112                     PROP_ANNOTATIONS,
113                     g_param_spec_pointer ("annotations",
114                         "emoji annotations",
115                         "The emoji annotation list",
116                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
117 
118     /**
119      * IBusEmojiData:description:
120      *
121      * The emoji description
122      */
123     g_object_class_install_property (gobject_class,
124                     PROP_DESCRIPTION,
125                     g_param_spec_string ("description",
126                         "emoji description",
127                         "The emoji description",
128                         "",
129                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
130 
131     /**
132      * IBusEmojiData:category:
133      *
134      * The emoji category
135      */
136     g_object_class_install_property (gobject_class,
137                     PROP_CATEGORY,
138                     g_param_spec_string ("category",
139                         "emoji category",
140                         "The emoji category",
141                         "",
142                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
143 }
144 
145 static void
ibus_emoji_data_init(IBusEmojiData * emoji)146 ibus_emoji_data_init (IBusEmojiData *emoji)
147 {
148     emoji->priv = IBUS_EMOJI_DATA_GET_PRIVATE (emoji);
149 }
150 
151 static void
free_dict_words(gpointer list)152 free_dict_words (gpointer list)
153 {
154     g_slist_free_full (list, g_free);
155 }
156 
157 static void
ibus_emoji_data_destroy(IBusEmojiData * emoji)158 ibus_emoji_data_destroy (IBusEmojiData *emoji)
159 {
160     g_clear_pointer (&emoji->priv->emoji, g_free);
161     g_clear_pointer (&emoji->priv->annotations, free_dict_words);
162     g_clear_pointer (&emoji->priv->description, g_free);
163     g_clear_pointer (&emoji->priv->category, g_free);
164 
165     IBUS_OBJECT_CLASS (ibus_emoji_data_parent_class)->
166             destroy (IBUS_OBJECT (emoji));
167 }
168 
169 static void
ibus_emoji_data_set_property(IBusEmojiData * emoji,guint prop_id,const GValue * value,GParamSpec * pspec)170 ibus_emoji_data_set_property (IBusEmojiData *emoji,
171                               guint          prop_id,
172                               const GValue  *value,
173                               GParamSpec    *pspec)
174 {
175     switch (prop_id) {
176     case PROP_EMOJI:
177         g_assert (emoji->priv->emoji == NULL);
178         emoji->priv->emoji = g_value_dup_string (value);
179         break;
180     case PROP_ANNOTATIONS:
181         if (emoji->priv->annotations)
182             g_slist_free_full (emoji->priv->annotations, g_free);
183         emoji->priv->annotations =
184                 g_slist_copy_deep (g_value_get_pointer (value),
185                                    (GCopyFunc) g_strdup, NULL);
186         break;
187     case PROP_DESCRIPTION:
188         g_free (emoji->priv->description);
189         emoji->priv->description = g_value_dup_string (value);
190         break;
191     case PROP_CATEGORY:
192         g_assert (emoji->priv->category == NULL);
193         emoji->priv->category = g_value_dup_string (value);
194         break;
195     default:
196         G_OBJECT_WARN_INVALID_PROPERTY_ID (emoji, prop_id, pspec);
197     }
198 }
199 
200 static void
ibus_emoji_data_get_property(IBusEmojiData * emoji,guint prop_id,GValue * value,GParamSpec * pspec)201 ibus_emoji_data_get_property (IBusEmojiData *emoji,
202                               guint          prop_id,
203                               GValue        *value,
204                               GParamSpec    *pspec)
205 {
206     switch (prop_id) {
207     case PROP_EMOJI:
208         g_value_set_string (value, ibus_emoji_data_get_emoji (emoji));
209         break;
210     case PROP_ANNOTATIONS:
211         g_value_set_pointer (
212                 value,
213                 g_slist_copy_deep (ibus_emoji_data_get_annotations (emoji),
214                                    (GCopyFunc) g_strdup, NULL));
215         break;
216     case PROP_DESCRIPTION:
217         g_value_set_string (value, ibus_emoji_data_get_description (emoji));
218         break;
219     case PROP_CATEGORY:
220         g_value_set_string (value, ibus_emoji_data_get_category (emoji));
221         break;
222     default:
223         G_OBJECT_WARN_INVALID_PROPERTY_ID (emoji, prop_id, pspec);
224     }
225 }
226 
227 static gboolean
ibus_emoji_data_serialize(IBusEmojiData * emoji,GVariantBuilder * builder)228 ibus_emoji_data_serialize (IBusEmojiData   *emoji,
229                            GVariantBuilder *builder)
230 {
231     GSList *l;
232     gboolean retval = IBUS_SERIALIZABLE_CLASS (ibus_emoji_data_parent_class)->
233             serialize ((IBusSerializable *)emoji, builder);
234     g_return_val_if_fail (retval, FALSE);
235 
236 #define NOTNULL(s) ((s) != NULL ? (s) : "")
237     /* If you will add a new property, you can append it at the end and
238      * you should not change the serialized order of name, longname,
239      * description, ... because the order is also used in other applications
240      * likes ibus-qt. */
241     g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->emoji));
242     g_variant_builder_add (builder, "u",
243                            g_slist_length (emoji->priv->annotations));
244     for (l = emoji->priv->annotations; l != NULL; l = l->next) {
245         g_variant_builder_add (builder, "s", NOTNULL (l->data));
246     }
247     g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->description));
248     g_variant_builder_add (builder, "s", NOTNULL (emoji->priv->category));
249 #undef NOTNULL
250     return TRUE;
251 }
252 
253 static gint
ibus_emoji_data_deserialize(IBusEmojiData * emoji,GVariant * variant)254 ibus_emoji_data_deserialize (IBusEmojiData *emoji,
255                              GVariant      *variant)
256 {
257     guint length, i;
258     GSList *annotations = NULL;
259     gint retval = IBUS_SERIALIZABLE_CLASS (ibus_emoji_data_parent_class)->
260             deserialize ((IBusSerializable *)emoji, variant);
261     g_return_val_if_fail (retval, 0);
262 
263     /* If you will add a new property, you can append it at the end and
264      * you should not change the serialized order of name, longname,
265      * description, ... because the order is also used in other applications
266      * likes ibus-qt. */
267     ibus_g_variant_get_child_string (variant, retval++,
268                                      &emoji->priv->emoji);
269     g_variant_get_child (variant, retval++, "u", &length);
270     for (i = 0; i < length; i++) {
271         gchar *s = NULL;
272         g_variant_get_child (variant, retval++, "s", &s);
273         annotations = g_slist_append (annotations, s);
274     }
275     emoji->priv->annotations = annotations;
276     ibus_g_variant_get_child_string (variant, retval++,
277                                      &emoji->priv->description);
278     ibus_g_variant_get_child_string (variant, retval++,
279                                      &emoji->priv->category);
280     return retval;
281 }
282 
283 static gboolean
ibus_emoji_data_copy(IBusEmojiData * dest,const IBusEmojiData * src)284 ibus_emoji_data_copy (IBusEmojiData       *dest,
285                       const IBusEmojiData *src)
286 {
287     gboolean retval = IBUS_SERIALIZABLE_CLASS (ibus_emoji_data_parent_class)->
288             copy ((IBusSerializable *)dest,
289                   (IBusSerializable *)src);
290     g_return_val_if_fail (retval, FALSE);
291 
292     dest->priv->emoji            = g_strdup (src->priv->emoji);
293     dest->priv->annotations      = g_slist_copy_deep (src->priv->annotations,
294                                                       (GCopyFunc) g_strdup,
295                                                       NULL);
296     dest->priv->description      = g_strdup (src->priv->description);
297     dest->priv->category         = g_strdup (src->priv->category);
298     return TRUE;
299 }
300 
301 IBusEmojiData *
ibus_emoji_data_new(const gchar * first_property_name,...)302 ibus_emoji_data_new (const gchar *first_property_name, ...)
303 {
304     va_list var_args;
305     IBusEmojiData *emoji;
306 
307     g_assert (first_property_name != NULL);
308     va_start (var_args, first_property_name);
309     emoji = (IBusEmojiData *) g_object_new_valist (IBUS_TYPE_EMOJI_DATA,
310                                                    first_property_name,
311                                                    var_args);
312     va_end (var_args);
313     /* emoji is required. Other properties are set in class_init by default. */
314     g_assert (emoji->priv->emoji != NULL);
315     g_assert (emoji->priv->description != NULL);
316     g_assert (emoji->priv->category != NULL);
317     return emoji;
318 }
319 
320 const gchar *
ibus_emoji_data_get_emoji(IBusEmojiData * emoji)321 ibus_emoji_data_get_emoji (IBusEmojiData *emoji)
322 {
323     g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
324 
325     return emoji->priv->emoji;
326 }
327 
328 GSList *
ibus_emoji_data_get_annotations(IBusEmojiData * emoji)329 ibus_emoji_data_get_annotations (IBusEmojiData *emoji)
330 {
331     g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
332 
333     return emoji->priv->annotations;
334 }
335 
336 void
ibus_emoji_data_set_annotations(IBusEmojiData * emoji,GSList * annotations)337 ibus_emoji_data_set_annotations (IBusEmojiData *emoji,
338                                  GSList        *annotations)
339 {
340     g_return_if_fail (IBUS_IS_EMOJI_DATA (emoji));
341 
342     if (emoji->priv->annotations)
343         g_slist_free_full (emoji->priv->annotations, g_free);
344     emoji->priv->annotations = annotations;
345 }
346 
347 const gchar *
ibus_emoji_data_get_description(IBusEmojiData * emoji)348 ibus_emoji_data_get_description (IBusEmojiData *emoji)
349 {
350     g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
351 
352     return emoji->priv->description;
353 }
354 
355 void
ibus_emoji_data_set_description(IBusEmojiData * emoji,const gchar * description)356 ibus_emoji_data_set_description (IBusEmojiData *emoji,
357                                  const gchar   *description)
358 {
359     g_return_if_fail (IBUS_IS_EMOJI_DATA (emoji));
360 
361     g_free (emoji->priv->description);
362     emoji->priv->description = g_strdup (description);
363 }
364 
365 const gchar *
ibus_emoji_data_get_category(IBusEmojiData * emoji)366 ibus_emoji_data_get_category (IBusEmojiData *emoji)
367 {
368     g_return_val_if_fail (IBUS_IS_EMOJI_DATA (emoji), NULL);
369 
370     return emoji->priv->category;
371 }
372 
373 static void
variant_foreach_add_emoji(IBusEmojiData * emoji,GVariantBuilder * builder)374 variant_foreach_add_emoji (IBusEmojiData   *emoji,
375                            GVariantBuilder *builder)
376 {
377     g_variant_builder_add (
378             builder, "v",
379             ibus_serializable_serialize (IBUS_SERIALIZABLE (emoji)));
380 }
381 
382 static GVariant *
ibus_emoji_data_list_serialize(GSList * list)383 ibus_emoji_data_list_serialize (GSList *list)
384 {
385     GVariantBuilder builder;
386 
387     g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
388     g_slist_foreach (list,  (GFunc) variant_foreach_add_emoji, &builder);
389     return g_variant_builder_end (&builder);
390 }
391 
392 static GSList *
ibus_emoji_data_list_deserialize(GVariant * variant)393 ibus_emoji_data_list_deserialize (GVariant           *variant)
394 {
395     GSList *list = NULL;
396     GVariantIter iter;
397     GVariant *emoji_variant = NULL;
398 
399     g_variant_iter_init (&iter, variant);
400     while (g_variant_iter_loop (&iter, "v", &emoji_variant)) {
401         IBusEmojiData *data =
402                 IBUS_EMOJI_DATA (ibus_serializable_deserialize (emoji_variant));
403         list = g_slist_append (list, data);
404         g_clear_pointer (&emoji_variant, g_variant_unref);
405     }
406 
407     return list;
408 }
409 
410 void
ibus_emoji_dict_save(const gchar * path,GHashTable * dict)411 ibus_emoji_dict_save (const gchar *path,
412                       GHashTable  *dict)
413 {
414     GList *values, *v;
415     GSList *list_for_save = NULL;
416 
417     g_return_if_fail (path != NULL);
418     g_return_if_fail (dict != NULL);
419 
420     values = g_hash_table_get_values (dict);
421     for (v = values; v; v = v->next) {
422         IBusEmojiData *data = v->data;
423         if (!IBUS_IS_EMOJI_DATA (data)) {
424             g_warning ("Your dict format of { annotation char, emoji GSList "
425                        "} is no longer supported.\n"
426                        "{ emoji char, IBusEmojiData GSList } is expected.");
427             return;
428         }
429         list_for_save = g_slist_append (list_for_save, data);
430     }
431 
432     ibus_emoji_data_save (path, list_for_save);
433 }
434 
435 GHashTable *
ibus_emoji_dict_load(const gchar * path)436 ibus_emoji_dict_load (const gchar *path)
437 {
438     GSList *list = ibus_emoji_data_load (path);
439     GSList *l;
440     GHashTable *dict = g_hash_table_new_full (g_str_hash,
441                                               g_str_equal,
442                                               g_free,
443                                               g_object_unref);
444 
445     for (l = list; l; l = l->next) {
446         IBusEmojiData *data = l->data;
447         if (!IBUS_IS_EMOJI_DATA (data)) {
448             g_warning ("Your dict format is no longer supported.\n"
449                        "Need to create the dictionaries again.");
450             return NULL;
451         }
452         g_hash_table_insert (dict,
453                              g_strdup (ibus_emoji_data_get_emoji (data)),
454                              g_object_ref_sink (data));
455     }
456 
457     g_slist_free (list);
458 
459     return dict;
460 }
461 
462 
463 IBusEmojiData *
ibus_emoji_dict_lookup(GHashTable * dict,const gchar * emoji)464 ibus_emoji_dict_lookup (GHashTable  *dict,
465                         const gchar *emoji)
466 {
467     return (IBusEmojiData *) g_hash_table_lookup (dict, emoji);
468 }
469 
470 void
ibus_emoji_data_save(const gchar * path,GSList * list)471 ibus_emoji_data_save (const gchar *path,
472                       GSList      *list)
473 {
474     GVariant *variant;
475     const gchar *header = IBUS_EMOJI_DATA_MAGIC;
476     const guint16 version = IBUS_EMOJI_DATA_VERSION;
477     const gchar *contents;
478     gsize length;
479     gchar *dir;
480     GStatBuf buf = { 0, };
481     GError *error = NULL;
482 
483     g_return_if_fail (path != NULL);
484     g_return_if_fail (list != NULL);
485     if (list->data == NULL) {
486         g_warning ("Failed to save IBus emoji data: Need a list data.");
487         return;
488     }
489 
490     variant = g_variant_new ("(sqv)",
491                              header,
492                              version,
493                              ibus_emoji_data_list_serialize (list));
494 
495     contents =  g_variant_get_data (variant);
496     length =  g_variant_get_size (variant);
497 
498     dir = g_path_get_dirname (path);
499     if (g_strcmp0 (dir, ".") != 0 && g_stat (dir, &buf) != 0) {
500         g_mkdir_with_parents (dir, 0777);
501     }
502     g_free (dir);
503     if (!g_file_set_contents (path, contents, length, &error)) {
504         g_warning ("Failed to save emoji dict %s: %s", path, error->message);
505         g_error_free (error);
506     }
507 
508     g_variant_unref (variant);
509 }
510 
511 GSList *
ibus_emoji_data_load(const gchar * path)512 ibus_emoji_data_load (const gchar *path)
513 {
514     gchar *contents = NULL;
515     gsize length = 0;
516     GError *error = NULL;
517     GVariant *variant_table = NULL;
518     GVariant *variant = NULL;
519     const gchar *header = NULL;
520     guint16 version = 0;
521     GSList *retval = NULL;
522 
523     if (!g_file_test (path, G_FILE_TEST_EXISTS)) {
524         g_warning ("Emoji dict does not exist: %s", path);
525         goto out_load_cache;
526     }
527 
528     if (!g_file_get_contents (path, &contents, &length, &error)) {
529         g_warning ("Failed to get dict content %s: %s", path, error->message);
530         g_error_free (error);
531         goto out_load_cache;
532     }
533 
534     variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sq)"),
535                                              contents,
536                                              length,
537                                              FALSE,
538                                              NULL,
539                                              NULL);
540 
541     if (variant_table == NULL) {
542         g_warning ("cache table is broken.");
543         goto out_load_cache;
544     }
545 
546     g_variant_get (variant_table, "(&sq)", &header, &version);
547 
548     if (g_strcmp0 (header, IBUS_EMOJI_DATA_MAGIC) != 0) {
549         g_warning ("cache is not IBusEmojiData.");
550         goto out_load_cache;
551     }
552 
553     if (version > IBUS_EMOJI_DATA_VERSION) {
554         g_warning ("cache version is different: %u != %u",
555                    version, IBUS_EMOJI_DATA_VERSION);
556         goto out_load_cache;
557     }
558 
559     version = 0;
560     header = NULL;
561     g_variant_unref (variant_table);
562 
563     variant_table = g_variant_new_from_data (G_VARIANT_TYPE ("(sqv)"),
564                                              contents,
565                                              length,
566                                              FALSE,
567                                              NULL,
568                                              NULL);
569 
570     if (variant_table == NULL) {
571         g_warning ("cache table is broken.");
572         goto out_load_cache;
573     }
574 
575     g_variant_get (variant_table, "(&sqv)",
576                    NULL,
577                    NULL,
578                    &variant);
579 
580     if (variant == NULL) {
581         g_warning ("cache dict is broken.");
582         goto out_load_cache;
583     }
584 
585     retval = ibus_emoji_data_list_deserialize (variant);
586 
587 out_load_cache:
588     if (variant)
589         g_variant_unref (variant);
590     if (variant_table)
591         g_variant_unref (variant_table);
592     g_free (contents);
593 
594     return retval;
595 }
596