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