1 /* font-manager-database.c
2  *
3  * Copyright (C) 2009 - 2021 Jerry Casiano
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.
17  *
18  * If not, see <http://www.gnu.org/licenses/gpl-3.0.txt>.
19 */
20 
21 #include "font-manager-database.h"
22 
23 /**
24  * SECTION: font-manager-database
25  * @short_description: Database related functions
26  * @title: Database
27  * @include: font-manager-database.h
28  * @stability: Unstable
29  *
30  * Database class and related functions.
31  *
32  * The current design uses a three separate database files.
33  * The first holds information required for basic font identification,
34  * the second holds all the metadata extracted from the font file
35  * itself and the third has information related to orthography support.
36  *
37  * These are then attached to the "base" database for access.
38  */
39 
40 #define CREATE_FONTS_TABLE "CREATE TABLE IF NOT EXISTS Fonts ( " \
41 "uid INTEGER PRIMARY KEY, filepath TEXT, findex INTEGER, family TEXT, " \
42 "style TEXT, spacing INTEGER, slant INTEGER, weight INTEGER, " \
43 "width INTEGER, description TEXT );\n"
44 
45 #define CREATE_INFO_TABLE "CREATE TABLE IF NOT EXISTS Metadata ( " \
46 "uid INTEGER PRIMARY KEY, filepath TEXT, findex INTEGER, family TEXT, " \
47 "style TEXT, owner INTEGER, psname TEXT, filetype TEXT, 'n-glyphs' INTEGER, " \
48 "copyright TEXT, version TEXT, description TEXT, 'license-data' TEXT, " \
49 "'license-url' TEXT, vendor TEXT, designer TEXT, 'designer-url' TEXT, " \
50 "'license-type' TEXT, fsType INTEGER, filesize TEXT, checksum TEXT );\n"
51 
52 #define CREATE_PANOSE_TABLE "CREATE TABLE IF NOT EXISTS Panose ( " \
53 "uid INTEGER PRIMARY KEY, P0 INTEGER, P1 INTEGER, P2 INTEGER, P3 INTEGER, " \
54 "P4 INTEGER, P5 INTEGER, P6 INTEGER, P7 INTEGER, P8 INTEGER, P9 INTEGER, " \
55 "filepath TEXT, findex INTEGER );\n"
56 
57 #define CREATE_ORTH_TABLE "CREATE TABLE IF NOT EXISTS Orthography ( " \
58 "uid INTEGER PRIMARY KEY, filepath TEXT, findex INT, support TEXT, sample TEXT );\n"
59 
60 #define CREATE_FONT_MATCH_INDEX "CREATE INDEX IF NOT EXISTS font_match_idx " \
61 "ON Fonts (filepath, findex, family, description);\n"
62 
63 #define CREATE_INFO_MATCH_INDEX "CREATE INDEX IF NOT EXISTS info_match_idx " \
64 "ON Metadata (filepath, findex, owner, filetype, vendor, 'license-type');\n"
65 
66 #define CREATE_PANOSE_MATCH_INDEX "CREATE INDEX IF NOT EXISTS panose_match_idx " \
67 "ON Panose (filepath, findex, P0);\n"
68 
69 #define DROP_FONT_MATCH_INDEX "DROP INDEX IF EXISTS font_match_idx;\n"
70 #define DROP_INFO_MATCH_INDEX "DROP INDEX IF EXISTS info_match_idx;\n"
71 #define DROP_PANOSE_MATCH_INDEX "DROP INDEX IF EXISTS panose_match_idx;\n"
72 
73 #define INSERT_FONT_ROW "INSERT OR REPLACE INTO Fonts VALUES (NULL,?,?,?,?,?,?,?,?,?);"
74 #define INSERT_INFO_ROW "INSERT OR REPLACE INTO Metadata VALUES (NULL,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);"
75 #define INSERT_PANOSE_ROW "INSERT OR REPLACE INTO Panose VALUES (NULL,?,?,?,?,?,?,?,?,?,?,?,?);"
76 #define INSERT_ORTH_ROW "INSERT OR REPLACE INTO Orthography VALUES (NULL, ?, ?, ?, ?);"
77 
78 #define FONT_PROPERTIES FontProperties
79 #define INFO_PROPERTIES InfoProperties
80 
81 typedef struct
82 {
83     gboolean in_transaction;
84     gchar *file;
85 }
86 FontManagerDatabasePrivate;
87 
88 G_DEFINE_TYPE_WITH_PRIVATE(FontManagerDatabase, font_manager_database, G_TYPE_OBJECT)
89 G_DEFINE_QUARK(font-manager-database-error-quark, font_manager_database_error)
90 
91 enum
92 {
93     PROP_RESERVED,
94     PROP_FILE,
95     N_PROPERTIES
96 };
97 
98 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
99 
100 static void
set_error(FontManagerDatabase * self,const gchar * ctx,GError ** error)101 set_error (FontManagerDatabase *self, const gchar *ctx, GError **error)
102 {
103     g_return_if_fail(error == NULL || *error == NULL);
104     const gchar *msg_format = "Database Error : (%s) [%i] - %s";
105     g_debug(msg_format, ctx, sqlite3_errcode(self->db), sqlite3_errmsg(self->db));
106     g_set_error(error,
107                 FONT_MANAGER_DATABASE_ERROR,
108                 (FontManagerDatabaseError) sqlite3_errcode(self->db),
109                 msg_format, ctx, sqlite3_errcode(self->db), sqlite3_errmsg(self->db));
110     return;
111 }
112 
113 static gboolean
sqlite3_open_failed(FontManagerDatabase * self,GError ** error)114 sqlite3_open_failed (FontManagerDatabase *self, GError **error)
115 {
116     g_return_val_if_fail(self != NULL, TRUE);
117     g_return_val_if_fail((error == NULL || *error == NULL), TRUE);
118     g_clear_pointer(&self->stmt, sqlite3_finalize);
119     if (self->db != NULL)
120         return FALSE;
121     GError *err = NULL;
122     font_manager_database_open(self, &err);
123     if (err != NULL) {
124         g_propagate_error(error, err);
125         g_warning("Database Error : Failed to open database.");
126         return TRUE;
127     }
128     return FALSE;
129 }
130 
131 static gboolean
sqlite3_step_succeeded(FontManagerDatabase * db,int expected_result)132 sqlite3_step_succeeded (FontManagerDatabase *db, int expected_result)
133 {
134     int actual_result = sqlite3_step(db->stmt);
135     if (actual_result == expected_result)
136         return TRUE;
137     if (actual_result != SQLITE_OK && actual_result != SQLITE_ROW && actual_result != SQLITE_DONE)
138         g_warning("SQLite Result Code %i : %s", sqlite3_errcode(db->db), sqlite3_errmsg(db->db));
139     return FALSE;
140 }
141 
142 /* font_manager_database_close:
143  * @self:   #FontManagerDatabase
144  * @error: (nullable): #GError or %NULL to ignore errors
145  *
146  * Close database.
147  * It is not necessary to call this function in normal usage.
148  */
149 static void
font_manager_database_close(FontManagerDatabase * self,GError ** error)150 font_manager_database_close (FontManagerDatabase *self, GError **error)
151 {
152     g_return_if_fail(self != NULL);
153     g_return_if_fail(error == NULL || *error == NULL);
154     g_clear_pointer(&self->stmt, sqlite3_finalize);
155     sqlite3_exec(self->db, "PRAGMA optimize;", NULL, NULL, NULL);
156     if (self->db && (sqlite3_close(self->db) != SQLITE_OK))
157         set_error(self, "sqlite3_close", error);
158     self->db = NULL;
159     return;
160 }
161 
162 static void
font_manager_database_dispose(GObject * gobject)163 font_manager_database_dispose (GObject *gobject)
164 {
165     g_return_if_fail(gobject != NULL);
166     FontManagerDatabase *self = FONT_MANAGER_DATABASE(gobject);
167     FontManagerDatabasePrivate *priv = font_manager_database_get_instance_private(self);
168     font_manager_database_close(self, NULL);
169     g_clear_pointer(&priv->file, g_free);
170     G_OBJECT_CLASS(font_manager_database_parent_class)->dispose(gobject);
171     return;
172 }
173 
174 static void
font_manager_database_get_property(GObject * gobject,guint property_id,GValue * value,GParamSpec * pspec)175 font_manager_database_get_property (GObject *gobject,
176                                     guint property_id,
177                                     GValue *value,
178                                     GParamSpec *pspec)
179 {
180     g_return_if_fail(gobject != NULL);
181     FontManagerDatabase *self = FONT_MANAGER_DATABASE(gobject);
182     FontManagerDatabasePrivate *priv = font_manager_database_get_instance_private(self);
183     switch (property_id) {
184         case PROP_FILE:
185             g_value_set_string(value, priv->file);
186             break;
187         default:
188             G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, property_id, pspec);
189             break;
190     }
191     return;
192 }
193 
194 static void
font_manager_database_set_property(GObject * gobject,guint property_id,const GValue * value,GParamSpec * pspec)195 font_manager_database_set_property (GObject *gobject,
196                                     guint property_id,
197                                     const GValue *value,
198                                     GParamSpec *pspec)
199 {
200     g_return_if_fail(gobject != NULL);
201     FontManagerDatabase *self = FONT_MANAGER_DATABASE(gobject);
202     FontManagerDatabasePrivate *priv = font_manager_database_get_instance_private(self);
203     switch (property_id) {
204         case PROP_FILE:
205             font_manager_database_close(self, NULL);
206             g_free(priv->file);
207             priv->file = g_value_dup_string(value);
208             break;
209         default:
210             G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, property_id, pspec);
211             break;
212     }
213     return;
214 }
215 
216 static void
font_manager_database_class_init(FontManagerDatabaseClass * klass)217 font_manager_database_class_init (FontManagerDatabaseClass *klass)
218 {
219 
220     GObjectClass *object_class = G_OBJECT_CLASS(klass);
221     object_class->dispose = font_manager_database_dispose;
222     object_class->get_property = font_manager_database_get_property;
223     object_class->set_property = font_manager_database_set_property;
224 
225     /**
226      * FontManagerDatabase:file:
227      *
228      * Filepath to database.
229      */
230     obj_properties[PROP_FILE] = g_param_spec_string("file",
231                                                     NULL,
232                                                     "Database file",
233                                                     NULL,
234                                                     G_PARAM_READWRITE |
235                                                     G_PARAM_STATIC_STRINGS);
236 
237     g_object_class_install_properties(object_class, N_PROPERTIES, obj_properties);
238     return;
239 }
240 
241 static void
font_manager_database_init(FontManagerDatabase * self)242 font_manager_database_init (FontManagerDatabase *self)
243 {
244     g_return_if_fail(self != NULL);
245     FontManagerDatabasePrivate *priv = font_manager_database_get_instance_private(self);
246     priv->file = g_strdup(":memory:");
247     return;
248 }
249 
250 /**
251  * font_manager_database_get_type_name:
252  * @type: #FontManagerDatabaseType
253  *
254  * Returns: Database type name
255  */
256 const gchar *
font_manager_database_get_type_name(FontManagerDatabaseType type)257 font_manager_database_get_type_name (FontManagerDatabaseType type)
258 {
259     switch (type) {
260         case FONT_MANAGER_DATABASE_TYPE_FONT:
261             return "Fonts";
262         case FONT_MANAGER_DATABASE_TYPE_METADATA:
263             return "Metadata";
264         case FONT_MANAGER_DATABASE_TYPE_ORTHOGRAPHY:
265             return "Orthography";
266         default:
267             return "";
268     }
269 }
270 
271 /**
272  * font_manager_database_get_file:
273  * @type: #FontManagerDatabaseType
274  *
275  * Returns: (nullable): A newly allocated string or %NULL
276  */
277 gchar *
font_manager_database_get_file(FontManagerDatabaseType type)278 font_manager_database_get_file (FontManagerDatabaseType type)
279 {
280     g_autofree gchar *cache_dir = font_manager_get_package_cache_directory();
281     g_autofree gchar *filename = g_strdup_printf("%s.sqlite", font_manager_database_get_type_name(type));
282     return g_build_filename(cache_dir, filename, NULL);
283 }
284 
285 /**
286  * font_manager_database_open:
287  * @self:   #FontManagerDatabase
288  * @error: (nullable): #GError or %NULL to ignore errors
289  *
290  * Open database.
291  *
292  * Note: It is not necessary to call this function in normal usage.
293  * The methods provided by this class will open the database if needed.
294  */
295 void
font_manager_database_open(FontManagerDatabase * self,GError ** error)296 font_manager_database_open (FontManagerDatabase *self, GError **error)
297 {
298     g_return_if_fail(self != NULL);
299     g_return_if_fail(error == NULL || *error == NULL);
300     if (self->db != NULL)
301         return;
302     FontManagerDatabasePrivate *priv = font_manager_database_get_instance_private(self);
303     if (sqlite3_open(priv->file, &self->db) != SQLITE_OK)
304         set_error(self, "sqlite3_open", error);
305     return;
306 }
307 
308 /**
309  * font_manager_database_begin_transaction:
310  * @self:   #FontManagerDatabase
311  * @error: (nullable): #GError or %NULL to ignore errors
312  *
313  * Begin a transaction, this should be paired with
314  * #font_manager_database_commit_transaction().
315  */
316 void
font_manager_database_begin_transaction(FontManagerDatabase * self,GError ** error)317 font_manager_database_begin_transaction (FontManagerDatabase *self, GError **error)
318 {
319     g_return_if_fail(self != NULL);
320     g_return_if_fail(error == NULL || *error == NULL);
321     FontManagerDatabasePrivate *priv = font_manager_database_get_instance_private(self);
322     if (priv->in_transaction)
323         return;
324     if (sqlite3_open_failed(self, error))
325         return;
326     if (sqlite3_exec(self->db, "BEGIN;", NULL, NULL, NULL) != SQLITE_OK)
327         set_error(self, "sqlite3_exec", error);
328     priv->in_transaction = TRUE;
329     return;
330 }
331 
332 /**
333  * font_manager_database_commit_transaction:
334  * @self:   #FontManagerDatabase
335  * @error: (nullable): #GError or %NULL to ignore errors
336  *
337  * End a transaction. It is an error to call this function without having
338  * previously called #font_manager_database_begin_transaction().
339  */
340 void
font_manager_database_commit_transaction(FontManagerDatabase * self,GError ** error)341 font_manager_database_commit_transaction (FontManagerDatabase *self, GError **error)
342 {
343     g_return_if_fail(self != NULL);
344     g_return_if_fail(error == NULL || *error == NULL);
345     FontManagerDatabasePrivate *priv = font_manager_database_get_instance_private(self);
346     if (!priv->in_transaction) {
347         g_set_error(error, FONT_MANAGER_DATABASE_ERROR, FONT_MANAGER_DATABASE_ERROR_MISUSE,
348                     G_STRLOC" : Not in transaction. Nothing to commit.");
349         g_return_if_reached();
350     }
351     if (sqlite3_exec(self->db, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK)
352         set_error(self, "sqlite3_exec", error);
353     priv->in_transaction = FALSE;
354     return;
355 }
356 
357 /**
358  * font_manager_database_execute_query:
359  * @self:   #FontManagerDatabase
360  * @sql:    Valid SQL query
361  * @error: (nullable): #GError or %NULL to ignore errors
362  */
363 void
font_manager_database_execute_query(FontManagerDatabase * self,const gchar * sql,GError ** error)364 font_manager_database_execute_query (FontManagerDatabase *self, const gchar *sql, GError **error)
365 {
366     g_return_if_fail(self != NULL);
367     g_return_if_fail(sql != NULL);
368     g_return_if_fail(error == NULL || *error == NULL);
369     if (sqlite3_open_failed(self, error))
370         return;
371     if (sqlite3_prepare_v2(self->db, sql, -1, &self->stmt, NULL) != SQLITE_OK)
372         set_error(self, sql, error);
373     return;
374 }
375 
376 /**
377  * font_manager_database_get_version:
378  * @self:   #FontManagerDatabase
379  * @error: (nullable): #GError or %NULL to ignore errors
380  *
381  * Returns: Database schema version or -1 on error.
382  */
383 gint
font_manager_database_get_version(FontManagerDatabase * self,GError ** error)384 font_manager_database_get_version (FontManagerDatabase *self, GError **error)
385 {
386     int result = -1;
387     g_return_val_if_fail(self != NULL, result);
388     g_return_val_if_fail((error == NULL || *error == NULL), result);
389     if (sqlite3_open_failed(self, error))
390         return result;
391     font_manager_database_execute_query(self, "PRAGMA user_version", error);
392     g_return_val_if_fail(error == NULL || *error == NULL, result);
393     if (sqlite3_step(self->stmt) == SQLITE_ROW)
394         result = sqlite3_column_int(self->stmt, 0);
395     return result;
396 }
397 
398 /**
399  * font_manager_database_set_version:
400  * @self:       #FontManagerDatabase
401  * @version:    version number
402  * @error: (nullable): #GError or %NULL to ignore errors
403  *
404  * Set database schema version.
405  */
406 void
font_manager_database_set_version(FontManagerDatabase * self,int version,GError ** error)407 font_manager_database_set_version (FontManagerDatabase *self, int version, GError **error)
408 {
409     g_return_if_fail(self != NULL);
410     g_return_if_fail(error == NULL || *error == NULL);
411     if (sqlite3_open_failed(self, error))
412         return;
413     g_autofree gchar *sql = g_strdup_printf("PRAGMA user_version = %i", version);
414     font_manager_database_execute_query(self, sql, error);
415     g_return_if_fail(error == NULL || *error == NULL);
416     if (!sqlite3_step_succeeded(self, SQLITE_DONE))
417         set_error(self, "sqlite3_step", error);
418     return;
419 }
420 
421 /**
422  * font_manager_database_vacuum:
423  * @self:   #FontManagerDatabase
424  * @error: (nullable): #GError or %NULL to ignore errors
425  *
426  * Run sqlite3 VACUUM command on currently selected database.
427  */
428 void
font_manager_database_vacuum(FontManagerDatabase * self,GError ** error)429 font_manager_database_vacuum (FontManagerDatabase *self, GError **error)
430 {
431     g_return_if_fail(self != NULL);
432     g_return_if_fail(error == NULL || *error == NULL);
433     if (sqlite3_open_failed(self, error))
434         return;
435     if (sqlite3_exec(self->db, "VACUUM", NULL, NULL, NULL) != SQLITE_OK)
436         set_error(self, "sqlite3_exec", error);
437     return;
438 }
439 
440 /**
441  * font_manager_database_detach:
442  * @self:   #FontManagerDatabase instance
443  * @type:   #FontManagerDatabaseType
444  * @error: (nullable): #GError or %NULL to ignore errors
445  *
446  * Detaches speficied database.
447  */
448 void
font_manager_database_detach(FontManagerDatabase * self,FontManagerDatabaseType type,GError ** error)449 font_manager_database_detach (FontManagerDatabase *self,
450                               FontManagerDatabaseType type,
451                               GError **error)
452 {
453     g_return_if_fail(self != NULL);
454     g_return_if_fail(error == NULL || *error == NULL);
455     if (sqlite3_open_failed(self, error))
456         return;
457     const gchar *sql = "DETACH DATABASE %s;";
458     const gchar *type_name = font_manager_database_get_type_name(type);
459     g_autofree gchar *query = g_strdup_printf(sql, type_name);
460     int result = sqlite3_exec(self->db, query, NULL, NULL, NULL);
461     /* Ignore most errors here, more than likely means db is not attached */
462     if (result != SQLITE_OK && result != SQLITE_ERROR)
463         set_error(self, "sqlite3_exec", error);
464     return;
465 }
466 
467 /**
468  * font_manager_database_attach:
469  * @self:   #FontManagerDatabase instance
470  * @type:   #FontManagerDatabaseType
471  * @error: (nullable): #GError or %NULL to ignore errors
472  *
473  * Attaches speficied database.
474  */
475 void
font_manager_database_attach(FontManagerDatabase * self,FontManagerDatabaseType type,GError ** error)476 font_manager_database_attach (FontManagerDatabase *self,
477                               FontManagerDatabaseType type,
478                               GError **error)
479 {
480     g_return_if_fail(self != NULL);
481     g_return_if_fail(error == NULL || *error == NULL);
482     if (sqlite3_open_failed(self, error))
483         return;
484     const gchar *sql = "ATTACH DATABASE '%s' AS %s;";
485     const gchar *type_name = font_manager_database_get_type_name(type);
486     g_autofree gchar *filepath = font_manager_database_get_file(type);
487     g_autofree gchar *query = g_strdup_printf(sql, filepath, type_name);
488     if (sqlite3_exec(self->db, query, NULL, NULL, NULL) != SQLITE_OK)
489         set_error(self, "sqlite3_exec", error);
490     return;
491 }
492 
493 /**
494  * font_manager_database_initialize:
495  * @self:   #FontManagerDatabase instance
496  * @type:   #FontManagerDatabaseType
497  * @error: (nullable): #GError or %NULL to ignore errors
498  *
499  * Ensures database is at latest schema version.
500  * Creates required tables if needed.
501  */
502 void
font_manager_database_initialize(FontManagerDatabase * self,FontManagerDatabaseType type,GError ** error)503 font_manager_database_initialize (FontManagerDatabase *self,
504                                   FontManagerDatabaseType type,
505                                   GError **error)
506 {
507     g_return_if_fail(FONT_MANAGER_IS_DATABASE(self));
508     g_return_if_fail(error == NULL || *error == NULL);
509 
510     if (font_manager_database_get_version(self, NULL) == FONT_MANAGER_CURRENT_DATABASE_VERSION)
511         return;
512 
513     font_manager_database_close(self, error);
514     g_return_if_fail(error == NULL || *error == NULL);
515 
516     g_autofree gchar *db_file = NULL;
517     g_object_get(self, "file", &db_file, NULL);
518     if (db_file != NULL && g_file_test(db_file, G_FILE_TEST_EXISTS))
519         if (g_remove(db_file) == -1)
520             g_critical("Failed to remove outdated database file : %s", db_file);
521 
522     if (type != FONT_MANAGER_DATABASE_TYPE_BASE) {
523         font_manager_database_execute_query(self, "PRAGMA journal_mode=WAL;\n", NULL);
524         g_assert(sqlite3_step_succeeded(self, SQLITE_ROW));
525         g_assert(sqlite3_strnicmp((const char *) sqlite3_column_text(self->stmt, 0), "wal", 3) == 0);
526     }
527 
528     if (type == FONT_MANAGER_DATABASE_TYPE_FONT) {
529 
530         font_manager_database_execute_query(self, CREATE_FONTS_TABLE, error);
531         g_return_if_fail(error == NULL || *error == NULL);
532         if (!sqlite3_step_succeeded(self, SQLITE_DONE))
533             set_error(self, "sqlite3_step", error);
534         g_return_if_fail(error == NULL || *error == NULL);
535 
536     } else if (type == FONT_MANAGER_DATABASE_TYPE_METADATA) {
537 
538         font_manager_database_execute_query(self, CREATE_INFO_TABLE, error);
539         g_return_if_fail(error == NULL || *error == NULL);
540         if (!sqlite3_step_succeeded(self, SQLITE_DONE))
541             set_error(self, "sqlite3_step", error);
542         g_return_if_fail(error == NULL || *error == NULL);
543 
544         font_manager_database_execute_query(self, CREATE_PANOSE_TABLE, error);
545         g_return_if_fail(error == NULL || *error == NULL);
546         if (!sqlite3_step_succeeded(self, SQLITE_DONE))
547             set_error(self, "sqlite3_step", error);
548         g_return_if_fail(error == NULL || *error == NULL);
549 
550     } else if (type == FONT_MANAGER_DATABASE_TYPE_ORTHOGRAPHY) {
551 
552         font_manager_database_execute_query(self, CREATE_ORTH_TABLE, error);
553         g_return_if_fail(error == NULL || *error == NULL);
554         if (!sqlite3_step_succeeded(self, SQLITE_DONE))
555             set_error(self, "sqlite3_step", error);
556         g_return_if_fail(error == NULL || *error == NULL);
557 
558     }
559 
560     font_manager_database_set_version(self, FONT_MANAGER_CURRENT_DATABASE_VERSION, NULL);
561     return;
562 }
563 
564 /**
565  * font_manager_database_get_object:
566  * @self: #FontManagerDatabase
567  * @sql: SQL query
568  * @error: #GError or %NULL to ignore errors
569  *
570  * Returns: (transfer full) (nullable):
571  * #JsonObject representation of first result,
572  * %NULL if there were no results or there was an error.
573  */
574 JsonObject *
font_manager_database_get_object(FontManagerDatabase * self,const gchar * sql,GError ** error)575 font_manager_database_get_object (FontManagerDatabase *self, const gchar *sql, GError **error)
576 {
577     g_return_val_if_fail(FONT_MANAGER_IS_DATABASE(self), NULL);
578     g_return_val_if_fail(sql != NULL, NULL);
579     g_return_val_if_fail((error == NULL || *error == NULL), NULL);
580 
581     font_manager_database_execute_query(self, sql, error);
582 
583     if (error != NULL && *error != NULL)
584         return NULL;
585 
586     if (!sqlite3_step_succeeded(self, SQLITE_ROW))
587         return NULL;
588 
589     JsonObject *obj = json_object_new();
590 
591     for (gint i = 0; i < sqlite3_column_count(self->stmt); i++) {
592         const gchar *name = sqlite3_column_origin_name(self->stmt, i);
593         gint int_column = -1;
594         const unsigned char *text_column = NULL;
595         switch (sqlite3_column_type(self->stmt, i)) {
596             case SQLITE_INTEGER:
597                 int_column = sqlite3_column_int(self->stmt, i);
598                 json_object_set_int_member(obj, name, int_column);
599                 break;
600             case SQLITE_TEXT:
601                 text_column = sqlite3_column_text(self->stmt, i);
602                 json_object_set_string_member(obj, name, (const gchar *) text_column);
603                 break;
604             case SQLITE_NULL:
605                 json_object_set_null_member(obj, name);
606                 break;
607             default:
608                 break;
609         }
610     }
611 
612     if (json_object_get_size(obj) < 1)
613         g_clear_pointer(&obj, json_object_unref);
614     return obj;
615 }
616 
617 /**
618  * font_manager_database_new:
619  *
620  * Returns: (transfer full): #FontManagerDatabase
621  */
622 FontManagerDatabase *
font_manager_database_new(void)623 font_manager_database_new (void)
624 {
625     return g_object_new(FONT_MANAGER_TYPE_DATABASE, NULL);
626 }
627 
628 /**
629  * font_manager_database_iterator:
630  * @self:   #FontManagerDatabase
631  *
632  * Returns: (transfer full):   #FontManagerDatabaseIterator.
633  * Free the return object using g_object_unref().
634  */
635 FontManagerDatabaseIterator *
font_manager_database_iterator(FontManagerDatabase * self)636 font_manager_database_iterator (FontManagerDatabase *self)
637 {
638     return font_manager_database_iterator_new(self);
639 }
640 
641 struct _FontManagerDatabaseIterator
642 {
643     GObjectClass parent_class;
644 
645     FontManagerDatabase *db;
646 };
647 
G_DEFINE_TYPE(FontManagerDatabaseIterator,font_manager_database_iterator,G_TYPE_OBJECT)648 G_DEFINE_TYPE(FontManagerDatabaseIterator, font_manager_database_iterator, G_TYPE_OBJECT)
649 
650 static void
651 font_manager_database_iterator_dispose (GObject *gobject)
652 {
653     g_return_if_fail(gobject != NULL);
654     FontManagerDatabaseIterator *self = FONT_MANAGER_DATABASE_ITERATOR(gobject);
655     g_clear_pointer(&self->db->stmt, sqlite3_finalize);
656     g_clear_object(&self->db);
657     G_OBJECT_CLASS(font_manager_database_iterator_parent_class)->dispose(gobject);
658     return;
659 }
660 
661 static void
font_manager_database_iterator_class_init(FontManagerDatabaseIteratorClass * klass)662 font_manager_database_iterator_class_init (FontManagerDatabaseIteratorClass *klass)
663 {
664     G_OBJECT_CLASS(klass)->dispose = font_manager_database_iterator_dispose;
665     return;
666 }
667 
668 static void
font_manager_database_iterator_init(G_GNUC_UNUSED FontManagerDatabaseIterator * self)669 font_manager_database_iterator_init (G_GNUC_UNUSED FontManagerDatabaseIterator *self)
670 {
671     return;
672 }
673 
674 /**
675  * font_manager_database_next:
676  * @self:   #FontManagerDatabase
677  *
678  * Returns: %TRUE if there are more results in set
679  */
680 gboolean
font_manager_database_iterator_next(FontManagerDatabaseIterator * self)681 font_manager_database_iterator_next (FontManagerDatabaseIterator *self)
682 {
683     g_return_val_if_fail(self != NULL, FALSE);
684     g_return_val_if_fail(self->db->stmt != NULL, FALSE);
685     return sqlite3_step_succeeded(self->db, SQLITE_ROW);
686 }
687 
688 /**
689  * font_manager_database_iterator_get: (skip)
690  * @self:   #FontManagerDatabase
691  *
692  * Returns: (transfer none): #sqlite3_stmt
693  */
694 sqlite3_stmt *
font_manager_database_iterator_get(FontManagerDatabaseIterator * self)695 font_manager_database_iterator_get (FontManagerDatabaseIterator *self)
696 {
697     g_return_val_if_fail(self != NULL, NULL);
698     return self->db->stmt;
699 }
700 
701 /**
702  * font_manager_database_iterator_new:
703  * @db: #FontManagerDatabase
704  *
705  * Returns: (transfer full): A newly created #FontManagerDatabaseIterator.
706  * Free the returned object using g_object_unref().
707  */
708 FontManagerDatabaseIterator *
font_manager_database_iterator_new(FontManagerDatabase * db)709 font_manager_database_iterator_new (FontManagerDatabase *db)
710 {
711     g_return_val_if_fail(db != NULL, NULL);
712     g_return_val_if_fail(db->stmt != NULL, NULL);
713     GObject *gobject = g_object_new(FONT_MANAGER_TYPE_DATABASE_ITERATOR, NULL);
714     FontManagerDatabaseIterator *self = FONT_MANAGER_DATABASE_ITERATOR(gobject);
715     self->db = g_object_ref(db);
716     return self;
717 }
718 
719 /* Related functions */
720 
721 typedef void (*InsertCallback) (FontManagerDatabase *db, JsonObject *face, gpointer data);
722 
723 typedef struct
724 {
725     gchar *table;
726     gchar *sql;
727     JsonObject *available_fonts;
728     FontManagerStringSet *available_files;
729     InsertCallback callback;
730     FontManagerProgressCallback progress;
731     gpointer data;
732 }
733 InsertData;
734 
735 static InsertData *
get_insert_data(const gchar * table,const gchar * sql,JsonObject * available_fonts,FontManagerStringSet * available_files,InsertCallback callback,FontManagerProgressCallback progress,gpointer data)736 get_insert_data (const gchar *table, const gchar *sql,
737                  JsonObject *available_fonts, FontManagerStringSet *available_files,
738                  InsertCallback callback, FontManagerProgressCallback progress,
739                  gpointer data)
740 {
741     InsertData *res = g_new0(InsertData, 1);
742     res->table = g_strdup(table);
743     res->sql = g_strdup(sql);
744     res->available_fonts = json_object_ref(available_fonts);
745     res->available_files = g_object_ref(available_files);
746     res->callback = callback;
747     res->progress = progress;
748     res->data = data;
749     return res;
750 }
751 
752 static void
free_insert_data(InsertData * data)753 free_insert_data (InsertData *data)
754 {
755     g_clear_pointer(&data->table, g_free);
756     g_clear_pointer(&data->sql, g_free);
757     g_clear_pointer(&data->available_fonts, json_object_unref);
758     g_clear_object(&data->available_files);
759     g_clear_pointer(&data, g_free);
760     return;
761 }
762 
763 G_DEFINE_AUTOPTR_CLEANUP_FUNC(InsertData, free_insert_data);
764 
765 typedef struct
766 {
767     FontManagerDatabase *db;
768     FontManagerDatabaseType type;
769     JsonObject *available_fonts;
770     FontManagerStringSet *available_files;
771     FontManagerProgressCallback progress;
772 }
773 DatabaseSyncData;
774 
775 static DatabaseSyncData *
get_sync_data(FontManagerDatabase * db,FontManagerDatabaseType type,JsonObject * available_fonts,FontManagerStringSet * available_files,FontManagerProgressCallback progress)776 get_sync_data (FontManagerDatabase *db,
777                FontManagerDatabaseType type,
778                JsonObject *available_fonts,
779                FontManagerStringSet *available_files,
780                FontManagerProgressCallback progress)
781 {
782     DatabaseSyncData *sync_data = g_new0(DatabaseSyncData, 1);
783     sync_data->db = g_object_ref(db);
784     sync_data->type = type;
785     sync_data->available_fonts = json_object_ref(available_fonts);
786     sync_data->available_files = g_object_ref(available_files);
787     sync_data->progress = progress;
788     return sync_data;
789 }
790 
791 static void
free_sync_data(DatabaseSyncData * data)792 free_sync_data (DatabaseSyncData *data)
793 {
794     g_clear_object(&data->db);
795     g_clear_pointer(&data->available_fonts, json_object_unref);
796     g_clear_object(&data->available_files);
797     g_clear_pointer(&data, g_free);
798     return;
799 }
800 
801 static void
bind_from_properties(sqlite3_stmt * stmt,JsonObject * json,const FontManagerJsonProxyProperties * properties,gint n_properties)802 bind_from_properties (sqlite3_stmt *stmt,
803                       JsonObject *json,
804                       const FontManagerJsonProxyProperties *properties,
805                       gint n_properties)
806 {
807     for (gint i = 0; i < n_properties; i++) {
808         const gchar *str = NULL;
809         switch (properties[i].type) {
810             case G_TYPE_INT:
811                 g_assert(json_object_has_member(json, properties[i].name));
812                 gint val = json_object_get_int_member(json, properties[i].name);
813                 g_assert(val >= -1 && sqlite3_bind_int(stmt, i, val) == SQLITE_OK);
814                 break;
815             case G_TYPE_STRING:
816                 if (json_object_has_member(json, properties[i].name))
817                     str = json_object_get_string_member(json, properties[i].name);
818                 g_assert(sqlite3_bind_text(stmt, i, str, -1, SQLITE_STATIC) == SQLITE_OK);
819                 break;
820             default:
821                 break;
822         }
823     }
824     return;
825 }
826 
827 static FontManagerStringSet *
get_known_files(FontManagerDatabase * db,const gchar * table)828 get_known_files (FontManagerDatabase *db, const gchar *table)
829 {
830     FontManagerStringSet *result = font_manager_string_set_new();
831     g_return_val_if_fail(FONT_MANAGER_IS_DATABASE(db), result);
832     g_return_val_if_fail(table != NULL, result);
833     g_autofree gchar *sql = g_strdup_printf("SELECT DISTINCT filepath FROM %s", table);
834     g_autoptr(GError) error = NULL;
835     font_manager_database_execute_query(db, sql, &error);
836     if (error != NULL) {
837         g_critical("%s", error->message);
838         return result;
839     }
840     g_autoptr(FontManagerDatabaseIterator) iter = font_manager_database_iterator(db);
841     while (font_manager_database_iterator_next(iter)) {
842         sqlite3_stmt *stmt = font_manager_database_iterator_get(iter);
843         const gchar *val = (const gchar *) sqlite3_column_text(stmt, 0);
844         if (val)
845             font_manager_string_set_add(result, val);
846     }
847     return result;
848 }
849 
850 static void
sync_fonts_table(FontManagerDatabase * db,JsonObject * face,G_GNUC_UNUSED gpointer data)851 sync_fonts_table (FontManagerDatabase *db, JsonObject *face, G_GNUC_UNUSED gpointer data)
852 {
853     bind_from_properties(db->stmt, face, FONT_PROPERTIES, G_N_ELEMENTS(FONT_PROPERTIES));
854     g_assert(sqlite3_step_succeeded(db, SQLITE_DONE));
855     sqlite3_clear_bindings(db->stmt);
856     sqlite3_reset(db->stmt);
857     return;
858 }
859 
860 static void
sync_metadata_table(FontManagerDatabase * db,JsonObject * face,gpointer data)861 sync_metadata_table (FontManagerDatabase *db, JsonObject *face, gpointer data)
862 {
863     JsonArray *panose_info = data;
864     int index = json_object_get_int_member(face, "findex");
865     const gchar *filepath = json_object_get_string_member(face, "filepath");
866     GError *error = NULL;
867     g_autoptr(JsonObject) _face = font_manager_get_metadata(filepath, index, &error);
868     if (error != NULL) {
869         g_critical("Failed to get metadata for %s::%i - %s", filepath, index, error->message);
870         return;
871     }
872     bind_from_properties(db->stmt, _face, INFO_PROPERTIES, G_N_ELEMENTS(INFO_PROPERTIES));
873     g_assert(sqlite3_step_succeeded(db, SQLITE_DONE));
874     sqlite3_clear_bindings(db->stmt);
875     sqlite3_reset(db->stmt);
876     JsonNode *_panose = json_object_dup_member(_face, "panose");
877     if (_panose) {
878         JsonObject *panose = json_object_new();
879         json_object_set_string_member(panose, "filepath", filepath);
880         json_object_set_int_member(panose, "findex", index);
881         json_object_set_member(panose, "panose", _panose);
882         json_array_add_object_element(panose_info, panose);
883     }
884     return;
885 }
886 
887 static void
sync_panose_table(FontManagerDatabase * db,JsonArray * panose,GCancellable * cancellable,GError ** error)888 sync_panose_table (FontManagerDatabase *db,
889                    JsonArray *panose,
890                    GCancellable *cancellable,
891                    GError **error)
892 {
893     g_return_if_fail(FONT_MANAGER_IS_DATABASE(db));
894     g_return_if_fail(panose != NULL);
895     g_return_if_fail(error == NULL || *error == NULL);
896 
897     guint total = json_array_get_length(panose);
898     if (total == 0)
899         return;
900     font_manager_database_begin_transaction(db, error);
901     g_return_if_fail(error == NULL || *error == NULL);
902     font_manager_database_execute_query(db, INSERT_PANOSE_ROW, error);
903     g_return_if_fail(error == NULL || *error == NULL);
904     for (guint processed = 0; processed < total; processed++) {
905         if (g_cancellable_is_cancelled(cancellable))
906             break;
907         int val;
908         JsonObject *obj = json_array_get_object_element(panose, processed);
909         JsonArray *_panose = json_object_get_array_member(obj, "panose");
910         for (int i = 0; i < 10; i++) {
911             int index = i + 1;
912             val = (int) json_array_get_int_element(_panose, i);
913             g_assert(sqlite3_bind_int(db->stmt, index, val) == SQLITE_OK);
914         }
915         const gchar *filepath = json_object_get_string_member(obj, "filepath");
916         g_assert(sqlite3_bind_text(db->stmt, 11, filepath, -1, SQLITE_STATIC) == SQLITE_OK);
917         val = json_object_get_int_member(obj, "findex");
918         g_assert(sqlite3_bind_int(db->stmt, 12, val) == SQLITE_OK);
919         g_assert(sqlite3_step_succeeded(db, SQLITE_DONE));
920         sqlite3_clear_bindings(db->stmt);
921         sqlite3_reset(db->stmt);
922     }
923     font_manager_database_commit_transaction(db, error);
924     return;
925 }
926 
927 static const gchar *FONT_MANAGER_SKIP_ORTH_SCAN[] = {
928      /* Adobe Blank can take several minutes to process due to number of codepoints. */
929     "Adobe Blank",
930     NULL
931 };
932 
933 static void
sync_orth_table(FontManagerDatabase * db,JsonObject * face,G_GNUC_UNUSED gpointer data)934 sync_orth_table (FontManagerDatabase *db, JsonObject *face, G_GNUC_UNUSED gpointer data)
935 {
936     int index = json_object_get_int_member(face, "findex");
937     const gchar *filepath = json_object_get_string_member(face, "filepath");
938     const gchar *family = json_object_get_string_member(face, "family");
939     gboolean blank_font = FALSE;
940     if (g_strv_contains(FONT_MANAGER_SKIP_ORTH_SCAN, family))
941         blank_font = TRUE;
942     g_autoptr(JsonObject) orth = font_manager_get_orthography_results(blank_font ? NULL : face);
943     g_autofree gchar *json_obj = font_manager_print_json_object(orth, FALSE);
944     const gchar *sample = json_object_get_string_member(orth, "sample");
945     g_assert(sqlite3_bind_text(db->stmt, 1, filepath, -1, SQLITE_STATIC) == SQLITE_OK);
946     g_assert(sqlite3_bind_int(db->stmt, 2, index) == SQLITE_OK);
947     g_assert(sqlite3_bind_text(db->stmt, 3, json_obj, -1, SQLITE_STATIC) == SQLITE_OK);
948     g_assert(sqlite3_bind_text(db->stmt, 4, sample, -1, SQLITE_STATIC) == SQLITE_OK);
949     g_assert(sqlite3_step_succeeded(db, SQLITE_DONE));
950     sqlite3_clear_bindings(db->stmt);
951     sqlite3_reset(db->stmt);
952     return;
953 }
954 
955 static void
update_available_fonts(FontManagerDatabase * db,InsertData * insert,GCancellable * cancellable,GError ** error)956 update_available_fonts (FontManagerDatabase *db,
957                         InsertData *insert,
958                         GCancellable *cancellable,
959                         GError **error)
960 {
961     g_return_if_fail(FONT_MANAGER_IS_DATABASE(db));
962     g_return_if_fail(error == NULL || *error == NULL);
963 
964     g_autoptr(FontManagerProgressData) progress = NULL;
965     g_autoptr(FontManagerStringSet) known_files = get_known_files(db, insert->table);
966 
967     if (font_manager_string_set_contains_all(known_files, insert->available_files))
968         return;
969 
970     guint processed = 0, total = json_object_get_size(insert->available_fonts);
971 
972     font_manager_database_begin_transaction(db, error);
973     g_return_if_fail(error == NULL || *error == NULL);
974     font_manager_database_execute_query(db, insert->sql, error);
975     g_return_if_fail(error == NULL || *error == NULL);
976 
977     JsonObjectIter f_iter;
978     const gchar *f_name;
979     JsonNode *f_node;
980     json_object_iter_init(&f_iter, insert->available_fonts);
981     while (json_object_iter_next(&f_iter, &f_name, &f_node)) {
982         if (g_cancellable_is_cancelled(cancellable))
983             break;
984         /* Stash results periodically so we don't lose everything if closed */
985         if (processed > 0 && processed % 500 == 0) {
986             font_manager_database_commit_transaction(db, error);
987             g_return_if_fail(error == NULL || *error == NULL);
988             font_manager_database_begin_transaction(db, error);
989             g_return_if_fail(error == NULL || *error == NULL);
990             /* Previous call frees the prepared statement we were using */
991             font_manager_database_execute_query(db, insert->sql, error);
992             g_return_if_fail(error == NULL || *error == NULL);
993         }
994         if (insert->progress) {
995 
996             if (!progress)
997                 progress = font_manager_progress_data_new(insert->table, processed, total);
998 
999             g_object_ref(progress);
1000             g_object_set(progress, "message", insert->table, "processed", processed, "total", total, NULL);
1001 
1002             g_main_context_invoke_full(g_main_context_get_thread_default(),
1003                                        G_PRIORITY_HIGH_IDLE,
1004                                        (GSourceFunc) insert->progress,
1005                                        progress,
1006                                        (GDestroyNotify) g_object_unref);
1007 
1008         }
1009         JsonObject *family = json_node_get_object(f_node);
1010         JsonObjectIter s_iter;
1011         const gchar *s_name;
1012         JsonNode *s_node;
1013         json_object_iter_init(&s_iter, family);
1014         while (json_object_iter_next(&s_iter, &s_name, &s_node)) {
1015             JsonObject *face = json_node_get_object(s_node);
1016             const gchar *filepath = json_object_get_string_member(face, "filepath");
1017             if (font_manager_string_set_contains(known_files, filepath))
1018                 continue;
1019             else
1020                 insert->callback(db, face, insert->data);
1021         }
1022         processed++;
1023     }
1024     font_manager_database_commit_transaction(db, error);
1025     return;
1026 }
1027 
1028 /**
1029  * font_manager_update_database_sync:
1030  * @db: #FontManagerDatabase instance
1031  * @type: #FontManagerDatabaseType
1032  * @available_fonts: #JsonObject returned by #font_manager_list_available_fonts
1033  * @available_files: #FontManagerStringSet containing filepaths for all available font files
1034  * @progress: (scope call) (nullable): #FontManagerProgressCallback
1035  * @cancellable: (nullable): #GCancellable or %NULL
1036  * @error: (nullable): #GError or %NULL to ignore errors
1037  *
1038  * Update application database as needed.
1039  *
1040  * Returns: %TRUE on success
1041  */
1042 gboolean
font_manager_update_database_sync(FontManagerDatabase * db,FontManagerDatabaseType type,JsonObject * available_fonts,FontManagerStringSet * available_files,FontManagerProgressCallback progress,GCancellable * cancellable,GError ** error)1043 font_manager_update_database_sync (FontManagerDatabase *db,
1044                                     FontManagerDatabaseType type,
1045                                     JsonObject *available_fonts,
1046                                     FontManagerStringSet *available_files,
1047                                     FontManagerProgressCallback progress,
1048                                     GCancellable *cancellable,
1049                                     GError **error)
1050 {
1051     g_return_val_if_fail(FONT_MANAGER_IS_DATABASE(db), FALSE);
1052     g_return_val_if_fail(type != FONT_MANAGER_DATABASE_TYPE_BASE, FALSE);
1053     g_return_val_if_fail((error == NULL || *error == NULL), FALSE);
1054 
1055     g_autoptr(InsertData) data = NULL;
1056     g_autoptr(JsonArray) panose = NULL;
1057     const gchar *table = font_manager_database_get_type_name(type);
1058 
1059     if (g_cancellable_is_cancelled(cancellable))
1060         return FALSE;
1061 
1062     if (type == FONT_MANAGER_DATABASE_TYPE_FONT) {
1063 
1064         font_manager_database_execute_query(db, DROP_FONT_MATCH_INDEX, NULL);
1065         g_assert(sqlite3_step_succeeded(db, SQLITE_DONE));
1066         data = get_insert_data(table, INSERT_FONT_ROW, available_fonts, available_files,
1067                                       (InsertCallback) sync_fonts_table, progress, NULL);
1068         update_available_fonts(db, data, cancellable, error);
1069         font_manager_database_execute_query(db, CREATE_FONT_MATCH_INDEX, NULL);
1070         g_assert(sqlite3_step_succeeded(db, SQLITE_DONE));
1071 
1072     } else if (type == FONT_MANAGER_DATABASE_TYPE_METADATA) {
1073 
1074         font_manager_database_execute_query(db, DROP_INFO_MATCH_INDEX, NULL);
1075         g_assert(sqlite3_step_succeeded(db, SQLITE_DONE));
1076         font_manager_database_execute_query(db, DROP_PANOSE_MATCH_INDEX, NULL);
1077         g_assert(sqlite3_step_succeeded(db, SQLITE_DONE));
1078         panose = json_array_new();
1079         data = get_insert_data(table, INSERT_INFO_ROW, available_fonts, available_files,
1080                                 (InsertCallback) sync_metadata_table, progress, panose);
1081         update_available_fonts(db, data, cancellable, error);
1082         g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1083         sync_panose_table(db, panose, cancellable, error);
1084         font_manager_database_execute_query(db, CREATE_INFO_MATCH_INDEX, NULL);
1085         g_assert(sqlite3_step_succeeded(db, SQLITE_DONE));
1086         font_manager_database_execute_query(db, CREATE_PANOSE_MATCH_INDEX, NULL);
1087         g_assert(sqlite3_step_succeeded(db, SQLITE_DONE));
1088 
1089     } else if (type == FONT_MANAGER_DATABASE_TYPE_ORTHOGRAPHY) {
1090 
1091         data = get_insert_data(table, INSERT_ORTH_ROW, available_fonts, available_files,
1092                                        (InsertCallback) sync_orth_table, progress, NULL);
1093         update_available_fonts(db, data, cancellable, error);
1094 
1095     }
1096 
1097     g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1098     return TRUE;
1099 }
1100 
1101 
1102 static void
sync_database_thread(GTask * task,G_GNUC_UNUSED gpointer source,gpointer task_data,GCancellable * cancellable)1103 sync_database_thread (GTask *task,
1104                       G_GNUC_UNUSED gpointer source,
1105                       gpointer task_data,
1106                       GCancellable *cancellable)
1107 {
1108     GError *error = NULL;
1109     gboolean result = FALSE;
1110     DatabaseSyncData *data = task_data;
1111 
1112     result = font_manager_update_database_sync(data->db, data->type, data->available_fonts,
1113                                                data->available_files, data->progress, cancellable,
1114                                                &error);
1115 
1116     if (error == NULL)
1117         g_task_return_boolean(task, result);
1118     else
1119         g_task_return_error(task, error);
1120 }
1121 
1122 /**
1123  * font_manager_update_database:
1124  * @db: #FontManagerDatabase instance
1125  * @type: #FontManagerDatabaseType
1126  * @available_fonts: #JsonObject returned by #font_manager_list_available_fonts
1127  * @available_files: #FontManagerStringSet containing filepaths for all available font files
1128  * @progress: (scope call) (nullable): #FontManagerProgressCallback
1129  * @cancellable: (nullable): #GCancellable or %NULL
1130  * @callback: (nullable) (scope async): #GAsyncReadyCallback or %NULL
1131  * @user_data: (nullable): user data passed to callback or %NULL
1132  *
1133  * Update application database as needed.
1134  */
1135 void
font_manager_update_database(FontManagerDatabase * db,FontManagerDatabaseType type,JsonObject * available_fonts,FontManagerStringSet * available_files,FontManagerProgressCallback progress,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1136 font_manager_update_database (FontManagerDatabase *db,
1137                               FontManagerDatabaseType type,
1138                               JsonObject *available_fonts,
1139                               FontManagerStringSet *available_files,
1140                               FontManagerProgressCallback progress,
1141                               GCancellable *cancellable,
1142                               GAsyncReadyCallback callback,
1143                               gpointer user_data)
1144 {
1145     g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE (cancellable));
1146     DatabaseSyncData *sync_data = get_sync_data(db, type, available_fonts, available_files, progress);
1147     g_autoptr(GTask) task = g_task_new(NULL, cancellable, callback, user_data);
1148     g_task_set_priority(task, G_PRIORITY_DEFAULT);
1149     g_task_set_return_on_cancel(task, FALSE);
1150     g_task_set_task_data(task, (gpointer) sync_data, (GDestroyNotify) free_sync_data);
1151     g_task_run_in_thread(task, sync_database_thread);
1152     return;
1153 }
1154 
1155 /**
1156  * font_manager_update_database_finish:
1157  * @result: #GAsyncResult
1158  * @error: (nullable): #GError or %NULL to ignore errors
1159  *
1160  * Returns: %TRUE on success
1161  */
1162 gboolean
font_manager_update_database_finish(GAsyncResult * result,GError ** error)1163 font_manager_update_database_finish (GAsyncResult *result, GError **error)
1164 {
1165     g_return_val_if_fail(g_task_is_valid(result, NULL), FALSE);
1166     g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
1167     return g_task_propagate_boolean(G_TASK(result), error);
1168 }
1169 
1170 /**
1171  * font_manager_get_matching_families_and_fonts:
1172  * @db: #FontManagerDatabase
1173  * @families: #FontManagerStringSet
1174  * @fonts: #FontManagerStringSet
1175  * @sql: SQL query to execute
1176  * @error: #GError or %NULL to ignore errors
1177  *
1178  * Query MUST return two result columns. The first containing the family name
1179  * and the second containing the font description.
1180  */
1181 void
font_manager_get_matching_families_and_fonts(FontManagerDatabase * db,FontManagerStringSet * families,FontManagerStringSet * fonts,const gchar * sql,GError ** error)1182 font_manager_get_matching_families_and_fonts (FontManagerDatabase *db,
1183                                               FontManagerStringSet *families,
1184                                               FontManagerStringSet *fonts,
1185                                               const gchar *sql,
1186                                               GError **error)
1187 {
1188     g_return_if_fail(FONT_MANAGER_IS_DATABASE(db));
1189     g_return_if_fail(FONT_MANAGER_IS_STRING_SET(families));
1190     g_return_if_fail(FONT_MANAGER_IS_STRING_SET(fonts));
1191     g_return_if_fail(sql != NULL);
1192     g_return_if_fail(error == NULL || *error == NULL);
1193     font_manager_database_execute_query(db, sql, error);
1194     g_return_if_fail(error == NULL || *error == NULL);
1195     g_autoptr(FontManagerDatabaseIterator) iter = font_manager_database_iterator(db);
1196     while (font_manager_database_iterator_next(iter)) {
1197         sqlite3_stmt *stmt = font_manager_database_iterator_get(iter);
1198         g_assert(sqlite3_column_count(stmt) >= 2);
1199         const gchar *family = (const gchar *) sqlite3_column_text(stmt, 0);
1200         const gchar *font = (const gchar *) sqlite3_column_text(stmt, 1);
1201         if (family == NULL || font == NULL)
1202             continue;
1203         font_manager_string_set_add(families, family);
1204         font_manager_string_set_add(fonts, font);
1205     }
1206     return;
1207 }
1208 
1209 static FontManagerDatabase *main_database = NULL;
1210 
1211 /**
1212  * font_manager_get_database:
1213  * @type:   #FontManagerDatabaseType
1214  * @error: (nullable): #GError or %NULL to ignore errors
1215  *
1216  * Convenience function which initializes the database and sets default options.
1217  *
1218  * Returns: (transfer full) (nullable): The requested #FontManagerDatabase or %NULL on error.
1219  * Free the returned object using #g_object_unref().
1220  */
1221 FontManagerDatabase *
font_manager_get_database(FontManagerDatabaseType type,GError ** error)1222 font_manager_get_database (FontManagerDatabaseType type, GError **error)
1223 {
1224     g_return_val_if_fail((error == NULL || *error == NULL), NULL);
1225     if (type == FONT_MANAGER_DATABASE_TYPE_BASE && main_database != NULL)
1226         return g_object_ref(main_database);
1227     FontManagerDatabase *db = font_manager_database_new();
1228     g_autofree gchar *db_file = font_manager_database_get_file(type);
1229     g_object_set(db, "file", db_file, NULL);
1230     font_manager_database_initialize(db, type, error);
1231     if (type == FONT_MANAGER_DATABASE_TYPE_BASE && main_database == NULL)
1232         main_database = g_object_ref(db);
1233     return db;
1234 }
1235 
1236 GType
font_manager_database_error_get_type(void)1237 font_manager_database_error_get_type (void)
1238 {
1239   static volatile gsize g_define_type_id__volatile = 0;
1240 
1241   if (g_once_init_enter (&g_define_type_id__volatile))
1242     {
1243       static const GEnumValue values[] = {
1244         { FONT_MANAGER_DATABASE_ERROR_OK, "FONT_MANAGER_DATABASE_ERROR_OK", "ok" },
1245         { FONT_MANAGER_DATABASE_ERROR_ERROR, "FONT_MANAGER_DATABASE_ERROR_ERROR", "error" },
1246         { FONT_MANAGER_DATABASE_ERROR_INTERNAL, "FONT_MANAGER_DATABASE_ERROR_INTERNAL", "internal" },
1247         { FONT_MANAGER_DATABASE_ERROR_PERM, "FONT_MANAGER_DATABASE_ERROR_PERM", "perm" },
1248         { FONT_MANAGER_DATABASE_ERROR_ABORT, "FONT_MANAGER_DATABASE_ERROR_ABORT", "abort" },
1249         { FONT_MANAGER_DATABASE_ERROR_BUSY, "FONT_MANAGER_DATABASE_ERROR_BUSY", "busy" },
1250         { FONT_MANAGER_DATABASE_ERROR_LOCKED, "FONT_MANAGER_DATABASE_ERROR_LOCKED", "locked" },
1251         { FONT_MANAGER_DATABASE_ERROR_NOMEM, "FONT_MANAGER_DATABASE_ERROR_NOMEM", "nomem" },
1252         { FONT_MANAGER_DATABASE_ERROR_READONLY, "FONT_MANAGER_DATABASE_ERROR_READONLY", "readonly" },
1253         { FONT_MANAGER_DATABASE_ERROR_INTERRUPT, "FONT_MANAGER_DATABASE_ERROR_INTERRUPT", "interrupt" },
1254         { FONT_MANAGER_DATABASE_ERROR_IOERR, "FONT_MANAGER_DATABASE_ERROR_IOERR", "ioerr" },
1255         { FONT_MANAGER_DATABASE_ERROR_CORRUPT, "FONT_MANAGER_DATABASE_ERROR_CORRUPT", "corrupt" },
1256         { FONT_MANAGER_DATABASE_ERROR_NOTFOUND, "FONT_MANAGER_DATABASE_ERROR_NOTFOUND", "notfound" },
1257         { FONT_MANAGER_DATABASE_ERROR_FULL, "FONT_MANAGER_DATABASE_ERROR_FULL", "full" },
1258         { FONT_MANAGER_DATABASE_ERROR_CANTOPEN, "FONT_MANAGER_DATABASE_ERROR_CANTOPEN", "cantopen" },
1259         { FONT_MANAGER_DATABASE_ERROR_PROTOCOL, "FONT_MANAGER_DATABASE_ERROR_PROTOCOL", "protocol" },
1260         { FONT_MANAGER_DATABASE_ERROR_EMPTY, "FONT_MANAGER_DATABASE_ERROR_EMPTY", "empty" },
1261         { FONT_MANAGER_DATABASE_ERROR_SCHEMA, "FONT_MANAGER_DATABASE_ERROR_SCHEMA", "schema" },
1262         { FONT_MANAGER_DATABASE_ERROR_TOOBIG, "FONT_MANAGER_DATABASE_ERROR_TOOBIG", "toobig" },
1263         { FONT_MANAGER_DATABASE_ERROR_CONSTRAINT, "FONT_MANAGER_DATABASE_ERROR_CONSTRAINT", "constraint" },
1264         { FONT_MANAGER_DATABASE_ERROR_MISMATCH, "FONT_MANAGER_DATABASE_ERROR_MISMATCH", "mismatch" },
1265         { FONT_MANAGER_DATABASE_ERROR_MISUSE, "FONT_MANAGER_DATABASE_ERROR_MISUSE", "misuse" },
1266         { FONT_MANAGER_DATABASE_ERROR_NOLFS, "FONT_MANAGER_DATABASE_ERROR_NOLFS", "nolfs" },
1267         { FONT_MANAGER_DATABASE_ERROR_AUTH, "FONT_MANAGER_DATABASE_ERROR_AUTH", "auth" },
1268         { FONT_MANAGER_DATABASE_ERROR_FORMAT, "FONT_MANAGER_DATABASE_ERROR_FORMAT", "format" },
1269         { FONT_MANAGER_DATABASE_ERROR_RANGE, "FONT_MANAGER_DATABASE_ERROR_RANGE", "range" },
1270         { FONT_MANAGER_DATABASE_ERROR_NOTADB, "FONT_MANAGER_DATABASE_ERROR_NOTADB", "notadb" },
1271         { FONT_MANAGER_DATABASE_ERROR_NOTICE, "FONT_MANAGER_DATABASE_ERROR_NOTICE", "notice" },
1272         { FONT_MANAGER_DATABASE_ERROR_WARNING, "FONT_MANAGER_DATABASE_ERROR_WARNING", "warning" },
1273         { FONT_MANAGER_DATABASE_ERROR_ROW, "FONT_MANAGER_DATABASE_ERROR_ROW", "row" },
1274         { FONT_MANAGER_DATABASE_ERROR_DONE, "FONT_MANAGER_DATABASE_ERROR_DONE", "done" },
1275         { 0, NULL, NULL }
1276       };
1277       GType g_define_type_id =
1278         g_enum_register_static (g_intern_static_string ("FontManagerDatabaseError"), values);
1279       g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
1280     }
1281 
1282   return g_define_type_id__volatile;
1283 }
1284 
1285 GType
font_manager_database_type_get_type(void)1286 font_manager_database_type_get_type (void)
1287 {
1288   static volatile gsize g_define_type_id__volatile = 0;
1289 
1290   if (g_once_init_enter (&g_define_type_id__volatile))
1291     {
1292       static const GEnumValue values[] = {
1293         { FONT_MANAGER_DATABASE_TYPE_BASE, "FONT_MANAGER_DATABASE_TYPE_BASE", "base" },
1294         { FONT_MANAGER_DATABASE_TYPE_FONT, "FONT_MANAGER_DATABASE_TYPE_FONT", "font" },
1295         { FONT_MANAGER_DATABASE_TYPE_METADATA, "FONT_MANAGER_DATABASE_TYPE_METADATA", "metadata" },
1296         { FONT_MANAGER_DATABASE_TYPE_ORTHOGRAPHY, "FONT_MANAGER_DATABASE_TYPE_ORTHOGRAPHY", "orthography" },
1297         { 0, NULL, NULL }
1298       };
1299       GType g_define_type_id =
1300         g_enum_register_static (g_intern_static_string ("FontManagerDatabaseType"), values);
1301       g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
1302     }
1303 
1304   return g_define_type_id__volatile;
1305 }
1306