Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-sms-store"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <gio/gio.h>
9 : : #include <valent.h>
10 : : #include <sqlite3.h>
11 : :
12 : : #include "valent-message.h"
13 : : #include "valent-message-thread.h"
14 : : #include "valent-sms-store.h"
15 : : #include "valent-sms-store-private.h"
16 : :
17 : : /* Ensure that sqlite3_int64 is the same size as int64_t */
18 : : G_STATIC_ASSERT (sizeof (sqlite3_int64) == sizeof (int64_t));
19 : :
20 : : struct _ValentSmsStore
21 : : {
22 : : ValentContext parent_instance;
23 : :
24 : : GAsyncQueue *queue;
25 : : sqlite3 *connection;
26 : : char *path;
27 : : sqlite3_stmt *stmts[9];
28 : :
29 : : GListStore *summary;
30 : : };
31 : :
32 [ + + + - ]: 166 : G_DEFINE_FINAL_TYPE (ValentSmsStore, valent_sms_store, VALENT_TYPE_CONTEXT)
33 : :
34 : : enum {
35 : : MESSAGE_ADDED,
36 : : MESSAGE_CHANGED,
37 : : MESSAGE_REMOVED,
38 : : N_SIGNALS
39 : : };
40 : :
41 : : static guint signals[N_SIGNALS] = { 0, };
42 : :
43 : : enum {
44 : : STMT_ADD_MESSAGE,
45 : : STMT_REMOVE_MESSAGE,
46 : : STMT_REMOVE_THREAD,
47 : : STMT_GET_MESSAGE,
48 : : STMT_GET_THREAD,
49 : : STMT_GET_THREAD_DATE,
50 : : STMT_GET_THREAD_ITEMS,
51 : : STMT_FIND_MESSAGES,
52 : : STMT_GET_SUMMARY,
53 : : N_STATEMENTS,
54 : : };
55 : :
56 : : static char *statements[N_STATEMENTS] = { NULL, };
57 : :
58 : :
59 : : /*
60 : : * Signal Emission Helpers
61 : : */
62 : : typedef struct
63 : : {
64 : : GRecMutex mutex;
65 : : GWeakRef store;
66 : : ValentMessage *message;
67 : : guint signal_id;
68 : : } ChangeEmission;
69 : :
70 : : static gboolean
71 : 21 : emit_change_main (gpointer data)
72 : : {
73 : 21 : ChangeEmission *emission = data;
74 : 42 : g_autoptr (ValentSmsStore) store = NULL;
75 : :
76 [ + - ]: 21 : g_assert (emission != NULL);
77 : :
78 : 21 : g_rec_mutex_lock (&emission->mutex);
79 [ + - ]: 21 : if ((store = g_weak_ref_get (&emission->store)) != NULL)
80 : : {
81 : 21 : g_signal_emit (G_OBJECT (store),
82 : : emission->signal_id, 0,
83 : : emission->message);
84 : : }
85 : :
86 : 21 : g_weak_ref_clear (&emission->store);
87 [ + - ]: 21 : g_clear_object (&emission->message);
88 : 21 : g_rec_mutex_unlock (&emission->mutex);
89 : 21 : g_rec_mutex_clear (&emission->mutex);
90 : 21 : g_clear_pointer (&emission, g_free);
91 : :
92 [ + - ]: 21 : return G_SOURCE_REMOVE;
93 : : }
94 : :
95 : :
96 : : /*
97 : : * sqlite Threading Helpers
98 : : */
99 : : enum {
100 : : TASK_DEFAULT,
101 : : TASK_CRITICAL,
102 : : TASK_TERMINAL,
103 : : };
104 : :
105 : : typedef struct
106 : : {
107 : : GTask *task;
108 : : GTaskThreadFunc task_func;
109 : : unsigned int task_mode;
110 : : } TaskClosure;
111 : :
112 : : static void
113 : 39 : task_closure_free (gpointer data)
114 : : {
115 : 78 : g_autofree TaskClosure *closure = data;
116 : :
117 [ + - ]: 39 : g_clear_object (&closure->task);
118 : 39 : g_clear_pointer (&closure, g_free);
119 : 39 : }
120 : :
121 : : static void
122 : 0 : task_closure_cancel (gpointer data)
123 : : {
124 : 0 : g_autofree TaskClosure *closure = data;
125 : :
126 [ # # # # : 0 : if (G_IS_TASK (closure->task) && !g_task_get_completed (closure->task))
# # # # #
# ]
127 : : {
128 : 0 : g_task_return_new_error (closure->task,
129 : : G_IO_ERROR,
130 : : G_IO_ERROR_CANCELLED,
131 : : "Operation cancelled");
132 : : }
133 : :
134 : 0 : g_clear_pointer (&closure, task_closure_free);
135 : 0 : }
136 : :
137 : : static gpointer
138 : 7 : valent_sms_store_thread (gpointer data)
139 : : {
140 : 9 : g_autoptr (GAsyncQueue) tasks = data;
141 : 7 : TaskClosure *closure = NULL;
142 : :
143 [ + - ]: 44 : while ((closure = g_async_queue_pop (tasks)))
144 : : {
145 : 44 : unsigned int mode = closure->task_mode;
146 : :
147 [ - + + - : 44 : if (G_IS_TASK (closure->task) && !g_task_get_completed (closure->task))
- + - - -
+ ]
148 : : {
149 : 44 : closure->task_func (closure->task,
150 : : g_task_get_source_object (closure->task),
151 : : g_task_get_task_data (closure->task),
152 : : g_task_get_cancellable (closure->task));
153 : :
154 [ + - - - ]: 39 : if (mode == TASK_CRITICAL && g_task_had_error (closure->task))
155 : : mode = TASK_TERMINAL;
156 : : }
157 : :
158 : 39 : g_clear_pointer (&closure, task_closure_free);
159 : :
160 [ + + ]: 39 : if (mode == TASK_TERMINAL)
161 : : break;
162 : : }
163 : :
164 : : /* Cancel any queued tasks */
165 : 2 : g_async_queue_lock (tasks);
166 : :
167 [ - + ]: 4 : while ((closure = g_async_queue_try_pop_unlocked (tasks)) != NULL)
168 : 2 : g_clear_pointer (&closure, task_closure_cancel);
169 : :
170 : 2 : g_async_queue_unlock (tasks);
171 : :
172 [ + - ]: 2 : return NULL;
173 : : }
174 : :
175 : : /*
176 : : * Step functions
177 : : */
178 : : static inline ValentMessage *
179 : 34 : valent_sms_store_get_message_step (sqlite3_stmt *stmt,
180 : : GError **error)
181 : : {
182 : 68 : g_autoptr (GVariant) metadata = NULL;
183 : 34 : const char *metadata_str;
184 : 34 : int rc;
185 : :
186 [ + - ]: 34 : g_assert (stmt != NULL);
187 [ + - - + ]: 34 : g_assert (error == NULL || *error == NULL);
188 : :
189 [ + + ]: 34 : if ((rc = sqlite3_step (stmt)) == SQLITE_DONE)
190 : : return NULL;
191 : :
192 [ - + ]: 25 : if (rc != SQLITE_ROW)
193 : : {
194 : 0 : g_set_error (error,
195 : : G_IO_ERROR,
196 : : G_IO_ERROR_FAILED,
197 : : "%s: %s", G_STRFUNC, sqlite3_errstr (rc));
198 : 0 : return NULL;
199 : : }
200 : :
201 [ + - ]: 25 : if ((metadata_str = (const char *)sqlite3_column_text (stmt, 3)) != NULL)
202 : 25 : metadata = g_variant_parse (NULL, metadata_str, NULL, NULL, NULL);
203 : :
204 : 25 : return g_object_new (VALENT_TYPE_MESSAGE,
205 : : "box", sqlite3_column_int (stmt, 0),
206 : : "date", sqlite3_column_int64 (stmt, 1),
207 : : "id", sqlite3_column_int64 (stmt, 2),
208 : : "metadata", metadata,
209 : : "read", sqlite3_column_int (stmt, 4),
210 : : "sender", sqlite3_column_text (stmt, 5),
211 : : "text", sqlite3_column_text (stmt, 6),
212 : : "thread_id", sqlite3_column_int64 (stmt, 7),
213 : : NULL);
214 : : }
215 : :
216 : : static inline gboolean
217 : 18 : valent_sms_store_set_message_step (sqlite3_stmt *stmt,
218 : : ValentMessage *message,
219 : : GError **error)
220 : : {
221 : 18 : int rc;
222 : 18 : ValentMessageBox box;
223 : 18 : int64_t date;
224 : 18 : int64_t id;
225 : 18 : GVariant *metadata;
226 : 18 : gboolean read;
227 : 18 : const char *sender;
228 : 18 : const char *text;
229 : 18 : int64_t thread_id;
230 : 36 : g_autofree char *metadata_str = NULL;
231 : :
232 : : /* Extract the message data */
233 : 18 : box = valent_message_get_box (message);
234 : 18 : date = valent_message_get_date (message);
235 : 18 : id = valent_message_get_id (message);
236 : 18 : metadata = valent_message_get_metadata (message);
237 : 18 : read = valent_message_get_read (message);
238 : 18 : sender = valent_message_get_sender (message);
239 : 18 : text = valent_message_get_text (message);
240 : 18 : thread_id = valent_message_get_thread_id (message);
241 : :
242 [ + - ]: 18 : if (metadata != NULL)
243 : 18 : metadata_str = g_variant_print (metadata, TRUE);
244 : :
245 : : /* Bind the message data */
246 : 18 : sqlite3_bind_int (stmt, 1, box);
247 : 18 : sqlite3_bind_int64 (stmt, 2, date);
248 : 18 : sqlite3_bind_int64 (stmt, 3, id);
249 : 18 : sqlite3_bind_text (stmt, 4, metadata_str, -1, NULL);
250 : 18 : sqlite3_bind_int (stmt, 5, read);
251 : 18 : sqlite3_bind_text (stmt, 6, sender, -1, NULL);
252 : 18 : sqlite3_bind_text (stmt, 7, text, -1, NULL);
253 : 18 : sqlite3_bind_int64 (stmt, 8, thread_id);
254 : :
255 : : /* Execute and auto-reset */
256 [ + + ]: 18 : if ((rc = sqlite3_step (stmt)) != SQLITE_DONE)
257 : : {
258 : 1 : g_set_error (error,
259 : : G_IO_ERROR,
260 : : G_IO_ERROR_FAILED,
261 : : "%s: %s", G_STRFUNC, sqlite3_errstr (rc));
262 : 1 : sqlite3_reset (stmt);
263 : 1 : return FALSE;
264 : : }
265 : :
266 : 17 : sqlite3_reset (stmt);
267 : 17 : return TRUE;
268 : : }
269 : :
270 : : static gboolean
271 : 30 : valent_sms_store_return_error_if_closed (GTask *task,
272 : : ValentSmsStore *self)
273 : : {
274 [ + - + - : 30 : g_assert (G_IS_TASK (task));
- + - - ]
275 [ - + ]: 30 : g_assert (VALENT_IS_SMS_STORE (self));
276 : :
277 [ - + ]: 30 : if G_UNLIKELY (self->connection == NULL)
278 : : {
279 : 0 : g_task_return_new_error (task,
280 : : G_IO_ERROR,
281 : : G_IO_ERROR_CONNECTION_CLOSED,
282 : : "Database connection closed");
283 : 0 : return TRUE;
284 : : }
285 : :
286 : : return FALSE;
287 : : }
288 : :
289 : :
290 : : /*
291 : : * Database Hooks
292 : : */
293 : : static void
294 : 21 : update_hook (gpointer user_data,
295 : : int event,
296 : : char const *database,
297 : : char const *table,
298 : : sqlite3_int64 rowid)
299 : : {
300 : 21 : ValentSmsStore *self = VALENT_SMS_STORE (user_data);
301 : 21 : sqlite3_stmt *stmt = self->stmts[STMT_GET_MESSAGE];
302 : 21 : g_autoptr (ValentMessage) message = NULL;
303 [ + - - - ]: 21 : g_autoptr (GError) error = NULL;
304 : :
305 [ + - ]: 21 : g_assert (VALENT_IS_SMS_STORE (self));
306 [ - + ]: 21 : g_assert (!VALENT_IS_MAIN_THREAD ());
307 : :
308 [ + - ]: 21 : if G_UNLIKELY (g_strcmp0 (table, "message") != 0)
309 : : return;
310 : :
311 [ + + ]: 21 : if (event != SQLITE_DELETE)
312 : : {
313 : 18 : sqlite3_bind_int64 (stmt, 1, rowid);
314 : 18 : message = valent_sms_store_get_message_step (stmt, &error);
315 : 18 : sqlite3_reset (stmt);
316 : : }
317 : :
318 [ + - ]: 21 : if G_UNLIKELY (error != NULL)
319 : : {
320 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
321 : 0 : return;
322 : : }
323 : :
324 : : /* Fallback to using a message skeleton */
325 [ + + ]: 21 : if (message == NULL)
326 : : {
327 : 8 : message = g_object_new (VALENT_TYPE_MESSAGE,
328 : : "id", rowid,
329 : : NULL);
330 : : }
331 : :
332 [ + + + - ]: 21 : switch (event)
333 : : {
334 : 16 : case SQLITE_INSERT:
335 : 16 : valent_sms_store_message_added (self, message);
336 : 16 : break;
337 : :
338 : 2 : case SQLITE_UPDATE:
339 : 2 : valent_sms_store_message_changed (self, message);
340 : 2 : break;
341 : :
342 : 3 : case SQLITE_DELETE:
343 : 3 : valent_sms_store_message_removed (self, message);
344 : 3 : break;
345 : : }
346 : : }
347 : :
348 : :
349 : : /*
350 : : * ValentSmsStore Tasks
351 : : */
352 : : static void
353 : 7 : valent_sms_store_open_task (GTask *task,
354 : : gpointer source_object,
355 : : gpointer task_data,
356 : : GCancellable *cancellable)
357 : : {
358 : 7 : ValentSmsStore *self = VALENT_SMS_STORE (source_object);
359 : 7 : const char *path = task_data;
360 : 7 : int rc;
361 : :
362 [ + - ]: 7 : if (g_task_return_error_if_cancelled (task))
363 : : return;
364 : :
365 [ - + ]: 7 : if (self->connection != NULL)
366 : 0 : return g_task_return_boolean (task, TRUE);
367 : :
368 : : /* Pass NOMUTEX since concurrency is managed by the GMutex*/
369 : 7 : rc = sqlite3_open_v2 (path,
370 : : &self->connection,
371 : : (SQLITE_OPEN_READWRITE |
372 : : SQLITE_OPEN_CREATE |
373 : : SQLITE_OPEN_NOMUTEX),
374 : : NULL);
375 : :
376 [ - + ]: 7 : if (rc != SQLITE_OK)
377 : : {
378 : 0 : g_task_return_new_error (task,
379 : : G_IO_ERROR,
380 : : G_IO_ERROR_FAILED,
381 : : "sqlite3_open_v2(): \"%s\": [%i] %s",
382 : : path, rc, sqlite3_errstr (rc));
383 [ # # ]: 0 : g_clear_pointer (&self->connection, sqlite3_close);
384 : 0 : return;
385 : : }
386 : :
387 : : /* Prepare the tables */
388 : 7 : rc = sqlite3_exec (self->connection,
389 : : MESSAGE_TABLE_SQL,
390 : : NULL,
391 : : NULL,
392 : : NULL);
393 : :
394 [ - + ]: 7 : if (rc != SQLITE_OK)
395 : : {
396 : 0 : g_task_return_new_error (task,
397 : : G_IO_ERROR,
398 : : G_IO_ERROR_FAILED,
399 : : "sqlite3_prepare_v2(): [%i] \"message\" Table: %s",
400 : : rc, sqlite3_errstr (rc));
401 [ # # ]: 0 : g_clear_pointer (&self->connection, sqlite3_close);
402 : 0 : return;
403 : : }
404 : :
405 : : /* Prepare the statements */
406 [ + + ]: 70 : for (unsigned int i = 0; i < N_STATEMENTS; i++)
407 : : {
408 : 63 : sqlite3_stmt *stmt = NULL;
409 : 63 : const char *sql = statements[i];
410 : :
411 : 63 : rc = sqlite3_prepare_v2 (self->connection, sql, -1, &stmt, NULL);
412 : :
413 [ - + ]: 63 : if (rc != SQLITE_OK)
414 : : {
415 : 0 : g_task_return_new_error (task,
416 : : G_IO_ERROR,
417 : : G_IO_ERROR_FAILED,
418 : : "sqlite3_prepare_v2(): \"%s\": [%i] %s",
419 : : sql, rc, sqlite3_errstr (rc));
420 [ # # ]: 0 : g_clear_pointer (&self->connection, sqlite3_close);
421 : 0 : return;
422 : : }
423 : :
424 : 63 : self->stmts[i] = g_steal_pointer (&stmt);
425 : : }
426 : :
427 : : /* Connect the hooks */
428 : 7 : sqlite3_update_hook (self->connection, update_hook, self);
429 : :
430 : 7 : g_task_return_boolean (task, TRUE);
431 : : }
432 : :
433 : : static void
434 : 7 : valent_sms_store_close_task (GTask *task,
435 : : gpointer source_object,
436 : : gpointer task_data,
437 : : GCancellable *cancellable)
438 : : {
439 : 7 : ValentSmsStore *self = VALENT_SMS_STORE (source_object);
440 : 7 : int rc;
441 : :
442 [ + - ]: 7 : if (g_task_return_error_if_cancelled (task))
443 : : return;
444 : :
445 [ - + ]: 7 : if (self->connection == NULL)
446 : 0 : return g_task_return_boolean (task, TRUE);
447 : :
448 : : /* Cleanup cached statements */
449 [ + + ]: 70 : for (unsigned int i = 0; i < N_STATEMENTS; i++)
450 [ + - ]: 63 : g_clear_pointer (&self->stmts[i], sqlite3_finalize);
451 : :
452 : : /* Optimize the database before closing.
453 : : *
454 : : * See:
455 : : * https://www.sqlite.org/pragma.html#pragma_optimize
456 : : * https://www.sqlite.org/queryplanner-ng.html#update_2017_a_better_fix
457 : : */
458 : 7 : rc = sqlite3_exec (self->connection, "PRAGMA optimize;", NULL, NULL, NULL);
459 : :
460 [ + + ]: 3 : if (rc != SQLITE_OK)
461 : : {
462 : 2 : g_debug ("sqlite3_exec(): \"%s\": [%i] %s",
463 : : "PRAGMA optimize;", rc, sqlite3_errstr (rc));
464 : : }
465 : :
466 : : /* Close the connection */
467 [ - + ]: 3 : if ((rc = sqlite3_close (self->connection)) != SQLITE_OK)
468 : : {
469 : 0 : g_task_return_new_error (task,
470 : : G_IO_ERROR,
471 : : G_IO_ERROR_FAILED,
472 : : "sqlite3_close(): [%i] %s",
473 : : rc, sqlite3_errstr (rc));
474 : 0 : return;
475 : : }
476 : :
477 : 2 : self->connection = NULL;
478 : 2 : g_task_return_boolean (task, TRUE);
479 : : }
480 : :
481 : : static void
482 : 9 : add_messages_task (GTask *task,
483 : : gpointer source_object,
484 : : gpointer task_data,
485 : : GCancellable *cancellable)
486 : : {
487 : 9 : ValentSmsStore *self = VALENT_SMS_STORE (source_object);
488 : 9 : GPtrArray *messages = task_data;
489 : 9 : sqlite3_stmt *stmt = self->stmts[STMT_ADD_MESSAGE];
490 : 9 : unsigned int n_messages = messages->len;
491 : 9 : GError *error = NULL;
492 : :
493 [ + - ]: 9 : if (g_task_return_error_if_cancelled (task))
494 : 1 : return;
495 : :
496 [ + - ]: 9 : if (valent_sms_store_return_error_if_closed (task, self))
497 : : return;
498 : :
499 [ + + ]: 26 : for (unsigned int i = 0; i < n_messages; i++)
500 : : {
501 : 18 : ValentMessage *message = g_ptr_array_index (messages, i);
502 : :
503 : : /* Iterate the results stopping on error to mark the point of failure */
504 [ + + ]: 18 : if (!valent_sms_store_set_message_step (stmt, message, &error))
505 : : {
506 : : n_messages = i;
507 : : break;
508 : : }
509 : : }
510 : :
511 : : /* Truncate the input on failure, since we'll be emitting signals */
512 [ + + ]: 9 : if (n_messages < messages->len)
513 : 1 : g_ptr_array_remove_range (messages, n_messages, messages->len - n_messages);
514 : :
515 [ + + ]: 9 : if (error != NULL)
516 : 1 : return g_task_return_error (task, error);
517 : :
518 : 8 : g_task_return_boolean (task, TRUE);
519 : : }
520 : :
521 : : static void
522 : 1 : remove_message_task (GTask *task,
523 : : gpointer source_object,
524 : : gpointer task_data,
525 : : GCancellable *cancellable)
526 : : {
527 : 1 : ValentSmsStore *self = VALENT_SMS_STORE (source_object);
528 : 1 : int64_t *message_id = task_data;
529 : 1 : sqlite3_stmt *stmt = self->stmts[STMT_REMOVE_MESSAGE];
530 : 1 : int rc;
531 : :
532 [ + - ]: 1 : if (g_task_return_error_if_cancelled (task))
533 : : return;
534 : :
535 [ + - ]: 1 : if (valent_sms_store_return_error_if_closed (task, self))
536 : : return;
537 : :
538 : 1 : sqlite3_bind_int64 (stmt, 1, *message_id);
539 : 1 : rc = sqlite3_step (stmt);
540 : 1 : sqlite3_reset (stmt);
541 : :
542 [ + - ]: 1 : if (rc == SQLITE_DONE || rc == SQLITE_OK)
543 : 1 : return g_task_return_boolean (task, TRUE);
544 : :
545 : 0 : return g_task_return_new_error (task,
546 : : G_IO_ERROR,
547 : : G_IO_ERROR_FAILED,
548 : : "%s: %s",
549 : : G_STRFUNC, sqlite3_errstr (rc));
550 : : }
551 : :
552 : : static void
553 : 1 : remove_thread_task (GTask *task,
554 : : gpointer source_object,
555 : : gpointer task_data,
556 : : GCancellable *cancellable)
557 : : {
558 : 1 : ValentSmsStore *self = VALENT_SMS_STORE (source_object);
559 : 1 : int64_t *thread_id = task_data;
560 : 1 : sqlite3_stmt *stmt = self->stmts[STMT_REMOVE_THREAD];
561 : 1 : int rc;
562 : :
563 [ + - ]: 1 : if (g_task_return_error_if_cancelled (task))
564 : : return;
565 : :
566 [ + - ]: 1 : if (valent_sms_store_return_error_if_closed (task, self))
567 : : return;
568 : :
569 : 1 : sqlite3_bind_int64 (stmt, 1, *thread_id);
570 : 1 : rc = sqlite3_step (stmt);
571 : 1 : sqlite3_reset (stmt);
572 : :
573 [ + - ]: 1 : if (rc == SQLITE_DONE || rc == SQLITE_OK)
574 : 1 : return g_task_return_boolean (task, TRUE);
575 : :
576 : 0 : return g_task_return_new_error (task,
577 : : G_IO_ERROR,
578 : : G_IO_ERROR_FAILED,
579 : : "%s: %s",
580 : : G_STRFUNC, sqlite3_errstr (rc));
581 : : }
582 : :
583 : : static void
584 : 1 : find_messages_task (GTask *task,
585 : : gpointer source_object,
586 : : gpointer task_data,
587 : : GCancellable *cancellable)
588 : : {
589 : 1 : ValentSmsStore *self = VALENT_SMS_STORE (source_object);
590 : 1 : const char *query = task_data;
591 : 1 : sqlite3_stmt *stmt = self->stmts[STMT_FIND_MESSAGES];
592 : 1 : g_autoptr (GPtrArray) messages = NULL;
593 [ - - ]: 1 : g_autofree char *query_param = NULL;
594 : 1 : ValentMessage *message;
595 : 1 : GError *error = NULL;
596 : :
597 [ + - ]: 1 : if (g_task_return_error_if_cancelled (task))
598 : : return;
599 : :
600 [ + - ]: 1 : if (valent_sms_store_return_error_if_closed (task, self))
601 : : return;
602 : :
603 : : // NOTE: escaped percent signs (%%) are query wildcards (%)
604 : 1 : query_param = g_strdup_printf ("%%%s%%", query);
605 : 1 : sqlite3_bind_text (stmt, 1, query_param, -1, NULL);
606 : :
607 : : /* Collect the results */
608 : 1 : messages = g_ptr_array_new_with_free_func (g_object_unref);
609 : :
610 [ + + ]: 3 : while ((message = valent_sms_store_get_message_step (stmt, &error)))
611 : 2 : g_ptr_array_add (messages, message);
612 : 1 : sqlite3_reset (stmt);
613 : :
614 [ - + ]: 1 : if (error != NULL)
615 : 0 : return g_task_return_error (task, error);
616 : :
617 : 1 : g_task_return_pointer (task,
618 : : g_steal_pointer (&messages),
619 : : (GDestroyNotify)g_ptr_array_unref);
620 : : }
621 : :
622 : : static void
623 : 6 : get_message_task (GTask *task,
624 : : gpointer source_object,
625 : : gpointer task_data,
626 : : GCancellable *cancellable)
627 : : {
628 : 6 : ValentSmsStore *self = VALENT_SMS_STORE (source_object);
629 : 6 : int64_t *message_id = task_data;
630 : 6 : sqlite3_stmt *stmt = self->stmts[STMT_GET_MESSAGE];
631 : 6 : g_autoptr (ValentMessage) message = NULL;
632 : 6 : GError *error = NULL;
633 : :
634 [ + - ]: 6 : if (g_task_return_error_if_cancelled (task))
635 : : return;
636 : :
637 [ + - ]: 6 : if (valent_sms_store_return_error_if_closed (task, self))
638 : : return;
639 : :
640 : 6 : sqlite3_bind_int64 (stmt, 1, *message_id);
641 : 6 : message = valent_sms_store_get_message_step (stmt, &error);
642 : 6 : sqlite3_reset (stmt);
643 : :
644 [ - + ]: 6 : if (error != NULL)
645 : 0 : return g_task_return_error (task, error);
646 : :
647 : 6 : g_task_return_pointer (task, g_steal_pointer (&message), g_object_unref);
648 : : }
649 : :
650 : : static void
651 : 3 : get_summary_cb (ValentSmsStore *self,
652 : : GAsyncResult *result,
653 : : gpointer user_data)
654 : : {
655 : 3 : g_autoptr (GPtrArray) messages = NULL;
656 : 3 : g_autoptr (GError) error = NULL;
657 : :
658 [ - + ]: 3 : if ((messages = g_task_propagate_pointer (G_TASK (result), &error)) == NULL)
659 : : {
660 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
661 [ # # ]: 0 : return;
662 : : }
663 : :
664 [ - + ]: 3 : g_list_store_splice (self->summary, 0, 0, messages->pdata, messages->len);
665 : : }
666 : :
667 : : static void
668 : 3 : get_summary_task (GTask *task,
669 : : gpointer source_object,
670 : : gpointer task_data,
671 : : GCancellable *cancellable)
672 : : {
673 : 3 : ValentSmsStore *self = VALENT_SMS_STORE (source_object);
674 : 3 : sqlite3_stmt *stmt = self->stmts[STMT_GET_SUMMARY];
675 : 3 : g_autoptr (GPtrArray) messages = NULL;
676 : 3 : ValentMessage *message;
677 : 3 : GError *error = NULL;
678 : :
679 [ + - ]: 3 : if (g_task_return_error_if_cancelled (task))
680 : : return;
681 : :
682 [ + - ]: 3 : if (valent_sms_store_return_error_if_closed (task, self))
683 : : return;
684 : :
685 : : /* Collect the results */
686 : 3 : messages = g_ptr_array_new_with_free_func (g_object_unref);
687 : :
688 [ + + ]: 7 : while ((message = valent_sms_store_get_message_step (stmt, &error)))
689 : 4 : g_ptr_array_add (messages, message);
690 : 3 : sqlite3_reset (stmt);
691 : :
692 [ - + ]: 3 : if (error != NULL)
693 : 0 : return g_task_return_error (task, error);
694 : :
695 : 3 : g_task_return_pointer (task,
696 : : g_steal_pointer (&messages),
697 : : (GDestroyNotify)g_ptr_array_unref);
698 : : }
699 : :
700 : : static void
701 : 5 : get_thread_date_task (GTask *task,
702 : : gpointer source_object,
703 : : gpointer task_data,
704 : : GCancellable *cancellable)
705 : : {
706 : 5 : ValentSmsStore *self = VALENT_SMS_STORE (source_object);
707 : 5 : int64_t *thread_id = task_data;
708 : 5 : sqlite3_stmt *stmt = self->stmts[STMT_GET_THREAD_DATE];
709 : 5 : int64_t date = 0;
710 : 5 : int rc;
711 : :
712 [ + - ]: 5 : if (g_task_return_error_if_cancelled (task))
713 : : return;
714 : :
715 [ + - ]: 5 : if (valent_sms_store_return_error_if_closed (task, self))
716 : : return;
717 : :
718 : 5 : sqlite3_bind_int64 (stmt, 1, *thread_id);
719 : :
720 [ + + ]: 5 : if ((rc = sqlite3_step (stmt)) == SQLITE_ROW)
721 : 3 : date = sqlite3_column_int64 (stmt, 0);
722 : :
723 : 5 : sqlite3_reset (stmt);
724 : :
725 [ - + ]: 5 : if (rc != SQLITE_DONE && rc != SQLITE_ROW)
726 : : {
727 : 0 : g_task_return_new_error (task,
728 : : G_IO_ERROR,
729 : : G_IO_ERROR_FAILED,
730 : : "%s: %s",
731 : : G_STRFUNC, sqlite3_errstr (rc));
732 : 0 : return;
733 : : }
734 : :
735 : 5 : g_task_return_int (task, date);
736 : : }
737 : :
738 : : static void
739 : 4 : get_thread_items_task (GTask *task,
740 : : gpointer source_object,
741 : : gpointer task_data,
742 : : GCancellable *cancellable)
743 : : {
744 : 4 : ValentSmsStore *self = VALENT_SMS_STORE (source_object);
745 : 4 : int64_t *thread_id = task_data;
746 : 4 : sqlite3_stmt *stmt = self->stmts[STMT_GET_THREAD_ITEMS];
747 : 4 : g_autoptr (GPtrArray) messages = NULL;
748 : 4 : int rc;
749 : :
750 [ + - ]: 4 : if (g_task_return_error_if_cancelled (task))
751 : : return;
752 : :
753 [ + - ]: 4 : if (valent_sms_store_return_error_if_closed (task, self))
754 : : return;
755 : :
756 : 4 : messages = g_ptr_array_new_with_free_func (g_object_unref);
757 : 4 : sqlite3_bind_int64 (stmt, 1, *thread_id);
758 : :
759 [ + + ]: 12 : while ((rc = sqlite3_step (stmt)) == SQLITE_ROW)
760 : : {
761 : 8 : ValentMessage *message;
762 : :
763 : 8 : message = g_object_new (VALENT_TYPE_MESSAGE,
764 : : "date", sqlite3_column_int64 (stmt, 0),
765 : : "id", sqlite3_column_int64 (stmt, 1),
766 : : "sender", sqlite3_column_text (stmt, 2),
767 : : "thread-id", *thread_id,
768 : : NULL);
769 : 8 : g_ptr_array_add (messages, message);
770 : : }
771 : :
772 : 4 : sqlite3_reset (stmt);
773 : :
774 [ - + ]: 4 : if (rc != SQLITE_DONE && rc != SQLITE_ROW)
775 : : {
776 : 0 : g_task_return_new_error (task,
777 : : G_IO_ERROR,
778 : : G_IO_ERROR_FAILED,
779 : : "%s: %s",
780 : : G_STRFUNC, sqlite3_errstr (rc));
781 [ # # ]: 0 : return;
782 : : }
783 : :
784 : 4 : g_task_return_pointer (task,
785 : : g_steal_pointer (&messages),
786 : : (GDestroyNotify)g_ptr_array_unref);
787 : : }
788 : :
789 : :
790 : : /*
791 : : * Private
792 : : */
793 : : static inline void
794 : 37 : valent_sms_store_push (ValentSmsStore *self,
795 : : GTask *task,
796 : : GTaskThreadFunc task_func)
797 : : {
798 : 37 : TaskClosure *closure = NULL;
799 : :
800 [ - + ]: 37 : if G_UNLIKELY (self->queue == NULL)
801 : : {
802 : 0 : g_task_return_new_error (task,
803 : : G_IO_ERROR,
804 : : G_IO_ERROR_CLOSED,
805 : : "Store is closed");
806 : 0 : return;
807 : : }
808 : :
809 : 37 : closure = g_new0 (TaskClosure, 1);
810 : 37 : closure->task = g_object_ref (task);
811 : 37 : closure->task_func = task_func;
812 : 37 : closure->task_mode = TASK_DEFAULT;
813 : 37 : g_async_queue_push (self->queue, closure);
814 : : }
815 : :
816 : : static void
817 : 7 : valent_sms_store_open (ValentSmsStore *self)
818 : : {
819 : 14 : g_autoptr (GThread) thread = NULL;
820 [ + - ]: 7 : g_autoptr (GError) error = NULL;
821 [ - + ]: 7 : g_autoptr (GTask) task = NULL;
822 [ + - ]: 7 : g_autoptr (GFile) file = NULL;
823 : :
824 : 7 : file = valent_context_get_cache_file (VALENT_CONTEXT (self), "sms.db");
825 : 7 : self->path = g_file_get_path (file);
826 : :
827 : 7 : task = g_task_new (self, NULL, NULL, NULL);
828 [ + - ]: 7 : g_task_set_source_tag (task, valent_sms_store_open);
829 [ - + ]: 14 : g_task_set_task_data (task, g_strdup (self->path), g_free);
830 : 7 : valent_sms_store_push (self, task, valent_sms_store_open_task);
831 : :
832 : : /* Spawn the worker thread, passing in a reference to the queue */
833 : 7 : thread = g_thread_try_new ("valent-task-queue",
834 : : valent_sms_store_thread,
835 : 7 : g_async_queue_ref (self->queue),
836 : : &error);
837 : :
838 : : /* On failure drop the reference passed to the thread, then clear the last
839 : : * reference so the open task is cancelled and new tasks are rejected */
840 [ - + ]: 7 : if (error != NULL)
841 : : {
842 : 0 : g_critical ("%s: Failed to spawn worker thread: %s",
843 : : G_OBJECT_TYPE_NAME (self),
844 : : error->message);
845 : 0 : g_async_queue_unref (self->queue);
846 [ - - + - ]: 7 : g_clear_pointer (&self->queue, g_async_queue_unref);
847 : : }
848 : 7 : }
849 : :
850 : : static void
851 : 7 : valent_sms_store_close (ValentSmsStore *self)
852 : : {
853 : 14 : g_autoptr (GTask) task = NULL;
854 : 7 : TaskClosure *closure = NULL;
855 : :
856 : 7 : task = g_task_new (self, NULL, NULL, NULL);
857 [ + - ]: 7 : g_task_set_source_tag (task, valent_sms_store_close);
858 : :
859 : 7 : closure = g_new0 (TaskClosure, 1);
860 : 7 : closure->task = g_object_ref (task);
861 : 7 : closure->task_func = valent_sms_store_close_task;
862 : 7 : closure->task_mode = TASK_TERMINAL;
863 [ + - ]: 7 : g_async_queue_push (self->queue, closure);
864 : 7 : }
865 : :
866 : : /*
867 : : * ValentObject
868 : : */
869 : : static void
870 : 9 : valent_sms_store_destroy (ValentObject *object)
871 : : {
872 : 9 : ValentSmsStore *self = VALENT_SMS_STORE (object);
873 : :
874 : : /* We will drop our reference to queue once we queue the closing task, then
875 : : * the task itself will end up holding the last reference. */
876 [ + + ]: 9 : if (self->queue != NULL)
877 : : {
878 : 7 : valent_sms_store_close (self);
879 [ + - ]: 7 : g_clear_pointer (&self->queue, g_async_queue_unref);
880 : : }
881 : :
882 : 9 : VALENT_OBJECT_CLASS (valent_sms_store_parent_class)->destroy (object);
883 : 9 : }
884 : :
885 : : /*
886 : : * GObject
887 : : */
888 : : static void
889 : 7 : valent_sms_store_constructed (GObject *object)
890 : : {
891 : 7 : ValentSmsStore *self = VALENT_SMS_STORE (object);
892 : :
893 : : /* Chain-up before queueing the open task to ensure the path is prepared */
894 : 7 : G_OBJECT_CLASS (valent_sms_store_parent_class)->constructed (object);
895 : :
896 : 7 : valent_sms_store_open (self);
897 : 7 : }
898 : :
899 : : static void
900 : 2 : valent_sms_store_finalize (GObject *object)
901 : : {
902 : 2 : ValentSmsStore *self = VALENT_SMS_STORE (object);
903 : :
904 [ - + ]: 2 : g_clear_pointer (&self->queue, g_async_queue_unref);
905 [ + - ]: 2 : g_clear_pointer (&self->path, g_free);
906 : 2 : g_clear_weak_pointer (&self->summary);
907 : :
908 : 2 : G_OBJECT_CLASS (valent_sms_store_parent_class)->finalize (object);
909 : 2 : }
910 : :
911 : : static void
912 : 5 : valent_sms_store_class_init (ValentSmsStoreClass *klass)
913 : : {
914 : 5 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
915 : 5 : ValentObjectClass *vobject_class = VALENT_OBJECT_CLASS (klass);
916 : :
917 : 5 : object_class->constructed = valent_sms_store_constructed;
918 : 5 : object_class->finalize = valent_sms_store_finalize;
919 : :
920 : 5 : vobject_class->destroy = valent_sms_store_destroy;
921 : :
922 : : /**
923 : : * ValentSmsStore::message-added:
924 : : * @store: a `ValentSmsStore`
925 : : * @message: a `ValentMessage`
926 : : *
927 : : * ValentSmsStore::message-added is emitted when a new message is added to
928 : : * @store.
929 : : */
930 : 10 : signals [MESSAGE_ADDED] =
931 : 5 : g_signal_new ("message-added",
932 : : VALENT_TYPE_SMS_STORE,
933 : : G_SIGNAL_RUN_LAST,
934 : : 0,
935 : : NULL, NULL,
936 : : g_cclosure_marshal_VOID__OBJECT,
937 : : G_TYPE_NONE, 1, VALENT_TYPE_MESSAGE);
938 : 5 : g_signal_set_va_marshaller (signals [MESSAGE_ADDED],
939 : : G_TYPE_FROM_CLASS (klass),
940 : : g_cclosure_marshal_VOID__OBJECTv);
941 : :
942 : : /**
943 : : * ValentSmsStore::message-changed:
944 : : * @store: a `ValentSmsStore`
945 : : * @message: a `ValentMessage`
946 : : *
947 : : * ValentSmsStore::message-changed is emitted when a message is updated in
948 : : * @store.
949 : : */
950 : 10 : signals [MESSAGE_CHANGED] =
951 : 5 : g_signal_new ("message-changed",
952 : : VALENT_TYPE_SMS_STORE,
953 : : G_SIGNAL_RUN_LAST,
954 : : 0,
955 : : NULL, NULL,
956 : : g_cclosure_marshal_VOID__OBJECT,
957 : : G_TYPE_NONE, 1, VALENT_TYPE_MESSAGE);
958 : 5 : g_signal_set_va_marshaller (signals [MESSAGE_CHANGED],
959 : : G_TYPE_FROM_CLASS (klass),
960 : : g_cclosure_marshal_VOID__OBJECTv);
961 : :
962 : : /**
963 : : * ValentSmsStore::message-removed:
964 : : * @store: a `ValentSmsStore`
965 : : * @message: a `ValentMessage`
966 : : *
967 : : * ValentSmsStore::message-removed is emitted when a message is removed from
968 : : * @store.
969 : : */
970 : 10 : signals [MESSAGE_REMOVED] =
971 : 5 : g_signal_new ("message-removed",
972 : : VALENT_TYPE_SMS_STORE,
973 : : G_SIGNAL_RUN_FIRST,
974 : : 0,
975 : : NULL, NULL,
976 : : g_cclosure_marshal_VOID__OBJECT,
977 : : G_TYPE_NONE, 1, VALENT_TYPE_MESSAGE);
978 : 5 : g_signal_set_va_marshaller (signals [MESSAGE_REMOVED],
979 : : G_TYPE_FROM_CLASS (klass),
980 : : g_cclosure_marshal_VOID__OBJECTv);
981 : :
982 : : /* SQL Statements */
983 : 5 : statements[STMT_ADD_MESSAGE] = ADD_MESSAGE_SQL;
984 : 5 : statements[STMT_REMOVE_MESSAGE] = REMOVE_MESSAGE_SQL;
985 : 5 : statements[STMT_REMOVE_THREAD] = REMOVE_THREAD_SQL;
986 : 5 : statements[STMT_GET_MESSAGE] = GET_MESSAGE_SQL;
987 : 5 : statements[STMT_GET_THREAD] = GET_THREAD_SQL;
988 : 5 : statements[STMT_GET_THREAD_DATE] = GET_THREAD_DATE_SQL;
989 : 5 : statements[STMT_GET_THREAD_ITEMS] = GET_THREAD_ITEMS_SQL;
990 : 5 : statements[STMT_FIND_MESSAGES] = FIND_MESSAGES_SQL;
991 : 5 : statements[STMT_GET_SUMMARY] = GET_SUMMARY_SQL;
992 : 5 : }
993 : :
994 : : static void
995 : 7 : valent_sms_store_init (ValentSmsStore *self)
996 : : {
997 : 7 : self->queue = g_async_queue_new_full (task_closure_cancel);
998 : 7 : }
999 : :
1000 : : /**
1001 : : * valent_sms_store_new:
1002 : : * @parent: a `ValentContext`
1003 : : *
1004 : : * Create a new `ValentSmsStore`.
1005 : : *
1006 : : * Returns: (transfer full): a new sms store
1007 : : */
1008 : : ValentSmsStore *
1009 : 4 : valent_sms_store_new (ValentContext *parent)
1010 : : {
1011 : 4 : return g_object_new (VALENT_TYPE_SMS_STORE,
1012 : : "domain", "plugin",
1013 : : "id", "sms",
1014 : : "parent", parent,
1015 : : NULL);
1016 : : }
1017 : :
1018 : : /**
1019 : : * valent_sms_store_add_message:
1020 : : * @store: a `ValentSmsStore`
1021 : : * @message: a `ValentMessage`
1022 : : * @cancellable: (nullable): a `GCancellable`
1023 : : * @callback: (scope async): a `GAsyncReadyCallback`
1024 : : * @user_data: (closure): user supplied data
1025 : : *
1026 : : * Add @message to @store.
1027 : : */
1028 : : void
1029 : 1 : valent_sms_store_add_message (ValentSmsStore *store,
1030 : : ValentMessage *message,
1031 : : GCancellable *cancellable,
1032 : : GAsyncReadyCallback callback,
1033 : : gpointer user_data)
1034 : : {
1035 : 2 : g_autoptr (GTask) task = NULL;
1036 [ + - ]: 1 : g_autoptr (GPtrArray) messages = NULL;
1037 : :
1038 [ + - ]: 1 : g_return_if_fail (VALENT_IS_SMS_STORE (store));
1039 [ - + ]: 1 : g_return_if_fail (VALENT_IS_MESSAGE (message));
1040 : :
1041 : 1 : messages = g_ptr_array_new_with_free_func (g_object_unref);
1042 : 1 : g_ptr_array_add (messages, g_object_ref (message));
1043 : :
1044 : 1 : task = g_task_new (store, cancellable, callback, user_data);
1045 [ + - ]: 1 : g_task_set_source_tag (task, valent_sms_store_add_message);
1046 : 1 : g_task_set_task_data (task,
1047 : : g_steal_pointer (&messages),
1048 : : (GDestroyNotify)g_ptr_array_unref);
1049 [ + - ]: 1 : valent_sms_store_push (store, task, add_messages_task);
1050 : : }
1051 : :
1052 : : /**
1053 : : * valent_sms_store_add_messages:
1054 : : * @store: a `ValentSmsStore`
1055 : : * @messages: (element-type Valent.Message): a `ValentMessage`
1056 : : * @cancellable: (nullable): a `GCancellable`
1057 : : * @callback: (scope async): a `GAsyncReadyCallback`
1058 : : * @user_data: (closure): user supplied data
1059 : : *
1060 : : * Add @messages to @store.
1061 : : */
1062 : : void
1063 : 8 : valent_sms_store_add_messages (ValentSmsStore *store,
1064 : : GPtrArray *messages,
1065 : : GCancellable *cancellable,
1066 : : GAsyncReadyCallback callback,
1067 : : gpointer user_data)
1068 : : {
1069 : 16 : g_autoptr (GTask) task = NULL;
1070 : :
1071 [ + - ]: 8 : g_return_if_fail (VALENT_IS_SMS_STORE (store));
1072 [ - + ]: 8 : g_return_if_fail (messages != NULL);
1073 : :
1074 : 8 : task = g_task_new (store, cancellable, callback, user_data);
1075 [ + - ]: 8 : g_task_set_source_tag (task, valent_sms_store_add_message);
1076 : 8 : g_task_set_task_data (task,
1077 : 8 : g_ptr_array_ref (messages),
1078 : : (GDestroyNotify)g_ptr_array_unref);
1079 [ + - ]: 8 : valent_sms_store_push (store, task, add_messages_task);
1080 : : }
1081 : :
1082 : : /**
1083 : : * valent_sms_store_add_messages_finish:
1084 : : * @store: a `ValentSmsStore`
1085 : : * @result: a `GAsyncResult`
1086 : : * @error: (nullable): a `GError`
1087 : : *
1088 : : * Finish an operation started by valent_sms_store_add_messages().
1089 : : *
1090 : : * Returns: %TRUE, or %FALSE with @error set
1091 : : */
1092 : : gboolean
1093 : 5 : valent_sms_store_add_messages_finish (ValentSmsStore *store,
1094 : : GAsyncResult *result,
1095 : : GError **error)
1096 : : {
1097 [ + - ]: 5 : g_return_val_if_fail (VALENT_IS_SMS_STORE (store), FALSE);
1098 [ - + ]: 5 : g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
1099 [ + - - + ]: 5 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1100 : :
1101 : 5 : return g_task_propagate_boolean (G_TASK (result), error);
1102 : : }
1103 : :
1104 : : /**
1105 : : * valent_sms_store_remove_message:
1106 : : * @store: a `ValentSmsStore`
1107 : : * @message_id: a message ID
1108 : : * @cancellable: (nullable): a `GCancellable`
1109 : : * @callback: (scope async): a `GAsyncReadyCallback`
1110 : : * @user_data: (closure): user supplied data
1111 : : *
1112 : : * Remove the message with @message_id from @thread_id.
1113 : : */
1114 : : void
1115 : 1 : valent_sms_store_remove_message (ValentSmsStore *store,
1116 : : int64_t message_id,
1117 : : GCancellable *cancellable,
1118 : : GAsyncReadyCallback callback,
1119 : : gpointer user_data)
1120 : : {
1121 : 0 : g_autoptr (GTask) task = NULL;
1122 : 1 : int64_t *task_data;
1123 : :
1124 [ + - ]: 1 : g_return_if_fail (VALENT_IS_SMS_STORE (store));
1125 : :
1126 : 1 : task_data = g_new0 (int64_t, 1);
1127 : 1 : *task_data = message_id;
1128 : :
1129 : 1 : task = g_task_new (store, cancellable, callback, user_data);
1130 : 1 : g_task_set_task_data (task, task_data, g_free);
1131 [ + - ]: 1 : g_task_set_source_tag (task, valent_sms_store_remove_message);
1132 [ + - ]: 1 : valent_sms_store_push (store, task, remove_message_task);
1133 : : }
1134 : :
1135 : : /**
1136 : : * valent_sms_store_remove_message_finish:
1137 : : * @store: a `ValentSmsStore`
1138 : : * @result: a `GAsyncResult`
1139 : : * @error: (nullable): a `GError`
1140 : : *
1141 : : * Finish an operation started by valent_sms_store_remove_message().
1142 : : *
1143 : : * Returns: %TRUE, or %FALSE with @error set
1144 : : */
1145 : : gboolean
1146 : 1 : valent_sms_store_remove_message_finish (ValentSmsStore *store,
1147 : : GAsyncResult *result,
1148 : : GError **error)
1149 : : {
1150 [ + - ]: 1 : g_return_val_if_fail (VALENT_IS_SMS_STORE (store), FALSE);
1151 [ - + ]: 1 : g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
1152 [ + - - + ]: 1 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1153 : :
1154 : 1 : return g_task_propagate_boolean (G_TASK (result), error);
1155 : : }
1156 : :
1157 : : /**
1158 : : * valent_sms_store_remove_thread:
1159 : : * @store: a `ValentSmsStore`
1160 : : * @thread_id: a thread ID
1161 : : * @cancellable: (nullable): a `GCancellable`
1162 : : * @callback: (scope async): a `GAsyncReadyCallback`
1163 : : * @user_data: (closure): user supplied data
1164 : : *
1165 : : * Remove @thread_id and all it's messages from @store.
1166 : : */
1167 : : void
1168 : 1 : valent_sms_store_remove_thread (ValentSmsStore *store,
1169 : : int64_t thread_id,
1170 : : GCancellable *cancellable,
1171 : : GAsyncReadyCallback callback,
1172 : : gpointer user_data)
1173 : : {
1174 : 2 : g_autoptr (GTask) task = NULL;
1175 : 1 : int64_t *task_data;
1176 : :
1177 [ + - ]: 1 : g_return_if_fail (VALENT_IS_SMS_STORE (store));
1178 [ - + ]: 1 : g_return_if_fail (thread_id >= 0);
1179 : :
1180 : 1 : task_data = g_new0 (int64_t, 1);
1181 : 1 : *task_data = thread_id;
1182 : :
1183 : 1 : task = g_task_new (store, cancellable, callback, user_data);
1184 : 1 : g_task_set_task_data (task, task_data, g_free);
1185 [ + - ]: 1 : g_task_set_source_tag (task, valent_sms_store_remove_thread);
1186 [ + - ]: 1 : valent_sms_store_push (store, task, remove_thread_task);
1187 : : }
1188 : :
1189 : : /**
1190 : : * valent_sms_store_remove_thread_finish:
1191 : : * @store: a `ValentSmsStore`
1192 : : * @result: a `GAsyncResult`
1193 : : * @error: (nullable): a `GError`
1194 : : *
1195 : : * Finish an operation started by valent_sms_store_remove_thread().
1196 : : *
1197 : : * Returns: %TRUE, or %FALSE with @error set
1198 : : */
1199 : : gboolean
1200 : 1 : valent_sms_store_remove_thread_finish (ValentSmsStore *store,
1201 : : GAsyncResult *result,
1202 : : GError **error)
1203 : : {
1204 [ + - ]: 1 : g_return_val_if_fail (VALENT_IS_SMS_STORE (store), FALSE);
1205 [ - + ]: 1 : g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
1206 [ + - - + ]: 1 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1207 : :
1208 : 1 : return g_task_propagate_boolean (G_TASK (result), error);
1209 : : }
1210 : :
1211 : : /**
1212 : : * valent_sms_store_find_messages:
1213 : : * @store: a `ValentSmsStore`
1214 : : * @query: a string to search for
1215 : : * @cancellable: (nullable): a `GCancellable`
1216 : : * @callback: (scope async): a `GAsyncReadyCallback`
1217 : : * @user_data: (closure): user supplied data
1218 : : *
1219 : : * Search through all the messages in @store and return the most recent message
1220 : : * from each thread containing @query.
1221 : : *
1222 : : * Call valent_sms_store_find_messages_finish() to get the result.
1223 : : */
1224 : : void
1225 : 1 : valent_sms_store_find_messages (ValentSmsStore *store,
1226 : : const char *query,
1227 : : GCancellable *cancellable,
1228 : : GAsyncReadyCallback callback,
1229 : : gpointer user_data)
1230 : : {
1231 : 2 : g_autoptr (GTask) task = NULL;
1232 : :
1233 [ + - ]: 1 : g_return_if_fail (VALENT_IS_SMS_STORE (store));
1234 [ - + ]: 1 : g_return_if_fail (query != NULL);
1235 [ - + - - : 1 : g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- - - - ]
1236 : :
1237 : 1 : task = g_task_new (store, cancellable, callback, user_data);
1238 [ + - ]: 1 : g_task_set_source_tag (task, valent_sms_store_find_messages);
1239 [ - + ]: 2 : g_task_set_task_data (task, g_strdup (query), g_free);
1240 [ + - ]: 1 : valent_sms_store_push (store, task, find_messages_task);
1241 : : }
1242 : :
1243 : : /**
1244 : : * valent_sms_store_find_messages_finish:
1245 : : * @store: a `ValentSmsStore`
1246 : : * @result: a `GAsyncResult`
1247 : : * @error: (nullable): a `GError`
1248 : : *
1249 : : * Finish an operation started by valent_sms_store_find_messages().
1250 : : *
1251 : : * Returns: (transfer container) (element-type Valent.Message): an `GPtrArray`
1252 : : */
1253 : : GPtrArray *
1254 : 1 : valent_sms_store_find_messages_finish (ValentSmsStore *store,
1255 : : GAsyncResult *result,
1256 : : GError **error)
1257 : : {
1258 [ + - ]: 1 : g_return_val_if_fail (VALENT_IS_SMS_STORE (store), NULL);
1259 [ - + ]: 1 : g_return_val_if_fail (g_task_is_valid (result, store), NULL);
1260 [ + - - + ]: 1 : g_return_val_if_fail (error == NULL || *error == NULL, NULL);
1261 : :
1262 : 1 : return g_task_propagate_pointer (G_TASK (result), error);
1263 : : }
1264 : :
1265 : : /**
1266 : : * valent_sms_store_get_message:
1267 : : * @store: a `ValentSmsStore`
1268 : : * @message_id: a message ID
1269 : : * @cancellable: (nullable): a `GCancellable`
1270 : : * @callback: (scope async): a `GAsyncReadyCallback`
1271 : : * @user_data: (closure): user supplied data
1272 : : *
1273 : : * Get the `ValentMessage` with @message_id or %NULL if not found.
1274 : : *
1275 : : * Returns: (transfer none) (nullable): a `ValentMessage`
1276 : : */
1277 : : void
1278 : 6 : valent_sms_store_get_message (ValentSmsStore *store,
1279 : : int64_t message_id,
1280 : : GCancellable *cancellable,
1281 : : GAsyncReadyCallback callback,
1282 : : gpointer user_data)
1283 : : {
1284 : 12 : g_autoptr (GTask) task = NULL;
1285 : 6 : int64_t *task_data;
1286 : :
1287 [ + - ]: 6 : g_return_if_fail (VALENT_IS_SMS_STORE (store));
1288 [ + + + - : 6 : g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
- + - - ]
1289 : :
1290 : 6 : task_data = g_new (int64_t, 1);
1291 : 6 : *task_data = message_id;
1292 : :
1293 : 6 : task = g_task_new (store, cancellable, callback, user_data);
1294 : 6 : g_task_set_task_data (task, task_data, g_free);
1295 [ + - ]: 6 : g_task_set_source_tag (task, valent_sms_store_get_message);
1296 [ + - ]: 6 : valent_sms_store_push (store, task, get_message_task);
1297 : : }
1298 : :
1299 : : /**
1300 : : * valent_sms_store_get_message_finish:
1301 : : * @store: a `ValentSmsStore`
1302 : : * @result: a `GAsyncResult`
1303 : : * @error: (nullable): a `GError`
1304 : : *
1305 : : * Finish an operation started by valent_sms_store_get_message().
1306 : : *
1307 : : * Returns: (transfer full) (nullable): a `ValentMessage`
1308 : : */
1309 : : ValentMessage *
1310 : 6 : valent_sms_store_get_message_finish (ValentSmsStore *store,
1311 : : GAsyncResult *result,
1312 : : GError **error)
1313 : : {
1314 [ + - ]: 6 : g_return_val_if_fail (VALENT_IS_SMS_STORE (store), NULL);
1315 [ - + ]: 6 : g_return_val_if_fail (g_task_is_valid (result, store), FALSE);
1316 [ + - - + ]: 6 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
1317 : :
1318 : 6 : return g_task_propagate_pointer (G_TASK (result), error);
1319 : : }
1320 : :
1321 : : /**
1322 : : * valent_sms_store_get_summary:
1323 : : * @store: a `ValentSmsStore`
1324 : : *
1325 : : * Get the latest message of each thread as a `GListModel`.
1326 : : *
1327 : : * Returns: (transfer full) (nullable): a `GListModel`
1328 : : */
1329 : : GListModel *
1330 : 3 : valent_sms_store_get_summary (ValentSmsStore *store)
1331 : : {
1332 : 6 : g_autoptr (GTask) task = NULL;
1333 : :
1334 [ + - ]: 3 : g_return_val_if_fail (VALENT_IS_SMS_STORE (store), NULL);
1335 : :
1336 [ - + ]: 3 : if (store->summary != NULL)
1337 : 0 : return g_object_ref (G_LIST_MODEL (store->summary));
1338 : :
1339 : 3 : store->summary = g_list_store_new (VALENT_TYPE_MESSAGE);
1340 : 3 : g_object_add_weak_pointer (G_OBJECT (store->summary),
1341 : 3 : (gpointer)&store->summary);
1342 : :
1343 : 3 : task = g_task_new (store, NULL, (GAsyncReadyCallback)get_summary_cb, NULL);
1344 [ + - ]: 3 : g_task_set_source_tag (task, valent_sms_store_get_summary);
1345 : 3 : valent_sms_store_push (store, task, get_summary_task);
1346 : :
1347 [ - + ]: 3 : return G_LIST_MODEL (store->summary);
1348 : : }
1349 : :
1350 : : /**
1351 : : * valent_sms_store_get_thread:
1352 : : * @store: a `ValentSmsStore`
1353 : : * @thread_id: a message id
1354 : : *
1355 : : * Get the thread with @thread_id as a `GListModel`.
1356 : : *
1357 : : * Returns: (transfer full): a `GListModel`
1358 : : */
1359 : : GListModel *
1360 : 4 : valent_sms_store_get_thread (ValentSmsStore *store,
1361 : : int64_t thread_id)
1362 : : {
1363 [ + - ]: 4 : g_return_val_if_fail (VALENT_IS_SMS_STORE (store), 0);
1364 [ - + ]: 4 : g_return_val_if_fail (thread_id > 0, 0);
1365 : :
1366 : 4 : return valent_message_thread_new (store, thread_id);
1367 : : }
1368 : :
1369 : : /**
1370 : : * valent_sms_store_get_thread_date:
1371 : : * @store: a `ValentSmsStore`
1372 : : * @thread_id: a thread ID
1373 : : *
1374 : : * Get the date of the last message in @thread_id.
1375 : : *
1376 : : * Returns: a UNIX epoch timestamp.
1377 : : */
1378 : : int64_t
1379 : 5 : valent_sms_store_get_thread_date (ValentSmsStore *store,
1380 : : int64_t thread_id)
1381 : : {
1382 : 10 : g_autoptr (GTask) task = NULL;
1383 [ + - ]: 5 : g_autoptr (GError) error = NULL;
1384 : 5 : int64_t date = 0;
1385 : :
1386 [ + - ]: 5 : g_return_val_if_fail (VALENT_IS_SMS_STORE (store), 0);
1387 [ - + ]: 5 : g_return_val_if_fail (thread_id >= 0, 0);
1388 : :
1389 : 5 : task = g_task_new (store, NULL, NULL, NULL);
1390 [ + - ]: 5 : g_task_set_source_tag (task, valent_sms_store_get_thread_date);
1391 : 5 : g_task_set_task_data (task, &thread_id, NULL);
1392 : 5 : valent_sms_store_push (store, task, get_thread_date_task);
1393 : :
1394 [ + + ]: 950 : while (!g_task_get_completed (task))
1395 : 945 : g_main_context_iteration (NULL, FALSE);
1396 : :
1397 : 5 : date = g_task_propagate_int (task, &error);
1398 : :
1399 [ - + ]: 5 : if (error != NULL)
1400 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
1401 : :
1402 : : return date;
1403 : : }
1404 : :
1405 : : /**
1406 : : * valent_sms_store_get_thread_items:
1407 : : * @store: a `ValentSmsStore`
1408 : : * @thread_id: a thread ID
1409 : : *
1410 : : * Get the `ValentMessage` in @thread_id at @position, when sorted by date in
1411 : : * ascending order.
1412 : : */
1413 : : void
1414 : 4 : valent_sms_store_get_thread_items (ValentSmsStore *store,
1415 : : int64_t thread_id,
1416 : : GCancellable *cancellable,
1417 : : GAsyncReadyCallback callback,
1418 : : gpointer user_data)
1419 : : {
1420 : 8 : g_autoptr (GTask) task = NULL;
1421 : 4 : int64_t *task_data;
1422 : :
1423 [ + - ]: 4 : g_return_if_fail (VALENT_IS_SMS_STORE (store));
1424 [ - + ]: 4 : g_return_if_fail (thread_id >= 0);
1425 : :
1426 : 4 : task_data = g_new0 (int64_t, 1);
1427 : 4 : *task_data = thread_id;
1428 : :
1429 : 4 : task = g_task_new (store, cancellable, callback, user_data);
1430 [ + - ]: 4 : g_task_set_source_tag (task, valent_sms_store_get_thread_items);
1431 : 4 : g_task_set_task_data (task, task_data, g_free);
1432 [ + - ]: 4 : valent_sms_store_push (store, task, get_thread_items_task);
1433 : : }
1434 : :
1435 : : /**
1436 : : * valent_sms_store_message_added:
1437 : : * @store: a `ValentSmsStore`
1438 : : * @message: a `ValentMessage`
1439 : : *
1440 : : * Emits the `ValentSmsStore`::message-added signal on @store.
1441 : : *
1442 : : * This function should only be called by classes implementing
1443 : : * `ValentSmsStore`. It has to be called after the internal representation
1444 : : * of @store has been updated, because handlers connected to this signal
1445 : : * might query the new state of the provider.
1446 : : */
1447 : : void
1448 : 16 : valent_sms_store_message_added (ValentSmsStore *store,
1449 : : ValentMessage *message)
1450 : : {
1451 : 16 : ChangeEmission *emission;
1452 : :
1453 [ + - ]: 16 : g_return_if_fail (VALENT_IS_SMS_STORE (store));
1454 [ - + ]: 16 : g_return_if_fail (VALENT_IS_MESSAGE (message));
1455 : :
1456 [ + - ]: 16 : if G_LIKELY (VALENT_IS_MAIN_THREAD ())
1457 : : {
1458 : 0 : g_signal_emit (G_OBJECT (store), signals [MESSAGE_ADDED], 0, message);
1459 : 0 : return;
1460 : : }
1461 : :
1462 : 16 : emission = g_new0 (ChangeEmission, 1);
1463 : 16 : g_rec_mutex_init (&emission->mutex);
1464 : 16 : g_rec_mutex_lock (&emission->mutex);
1465 : 16 : g_weak_ref_init (&emission->store, store);
1466 : 16 : emission->message = g_object_ref (message);
1467 : 16 : emission->signal_id = signals [MESSAGE_ADDED];
1468 : 16 : g_rec_mutex_unlock (&emission->mutex);
1469 : :
1470 : 16 : g_idle_add_full (G_PRIORITY_DEFAULT,
1471 : : emit_change_main,
1472 : : g_steal_pointer (&emission),
1473 : : NULL);
1474 : : }
1475 : :
1476 : : /**
1477 : : * valent_sms_store_message_removed:
1478 : : * @store: a `ValentSmsStore`
1479 : : * @message: a `ValentMessage`
1480 : : *
1481 : : * Emits the `ValentSmsStore`::message-removed signal on @store.
1482 : : *
1483 : : * This function should only be called by classes implementing
1484 : : * `ValentSmsStore`. It has to be called after the internal representation
1485 : : * of @store has been updated, because handlers connected to this signal
1486 : : * might query the new state of the provider.
1487 : : */
1488 : : void
1489 : 3 : valent_sms_store_message_removed (ValentSmsStore *store,
1490 : : ValentMessage *message)
1491 : : {
1492 : 3 : ChangeEmission *emission;
1493 : :
1494 [ + - ]: 3 : g_return_if_fail (VALENT_IS_SMS_STORE (store));
1495 [ - + ]: 3 : g_return_if_fail (VALENT_IS_MESSAGE (message));
1496 : :
1497 [ + - ]: 3 : if G_LIKELY (VALENT_IS_MAIN_THREAD ())
1498 : : {
1499 : 0 : g_signal_emit (G_OBJECT (store), signals [MESSAGE_REMOVED], 0, message);
1500 : 0 : return;
1501 : : }
1502 : :
1503 : 3 : emission = g_new0 (ChangeEmission, 1);
1504 : 3 : g_rec_mutex_init (&emission->mutex);
1505 : 3 : g_rec_mutex_lock (&emission->mutex);
1506 : 3 : g_weak_ref_init (&emission->store, store);
1507 : 3 : emission->message = g_object_ref (message);
1508 : 3 : emission->signal_id = signals [MESSAGE_REMOVED];
1509 : 3 : g_rec_mutex_unlock (&emission->mutex);
1510 : :
1511 : 3 : g_idle_add_full (G_PRIORITY_DEFAULT,
1512 : : emit_change_main,
1513 : : g_steal_pointer (&emission),
1514 : : NULL);
1515 : : }
1516 : :
1517 : : /**
1518 : : * valent_sms_store_message_changed:
1519 : : * @store: a `ValentSmsStore`
1520 : : * @message: a `ValentMessage`
1521 : : *
1522 : : * Emits the `ValentSmsStore`::message-changed signal on @store.
1523 : : *
1524 : : * This function should only be called by classes implementing
1525 : : * `ValentSmsStore`. It has to be called after the internal representation
1526 : : * of @store has been updated, because handlers connected to this signal
1527 : : * might query the new state of the provider.
1528 : : */
1529 : : void
1530 : 2 : valent_sms_store_message_changed (ValentSmsStore *store,
1531 : : ValentMessage *message)
1532 : : {
1533 : 2 : ChangeEmission *emission;
1534 : :
1535 [ + - ]: 2 : g_return_if_fail (VALENT_IS_SMS_STORE (store));
1536 [ - + ]: 2 : g_return_if_fail (VALENT_IS_MESSAGE (message));
1537 : :
1538 [ + - ]: 2 : if G_LIKELY (VALENT_IS_MAIN_THREAD ())
1539 : : {
1540 : 0 : g_signal_emit (G_OBJECT (store), signals [MESSAGE_CHANGED], 0, message);
1541 : 0 : return;
1542 : : }
1543 : :
1544 : 2 : emission = g_new0 (ChangeEmission, 1);
1545 : 2 : g_rec_mutex_init (&emission->mutex);
1546 : 2 : g_rec_mutex_lock (&emission->mutex);
1547 : 2 : g_weak_ref_init (&emission->store, store);
1548 : 2 : emission->message = g_object_ref (message);
1549 : 2 : emission->signal_id = signals [MESSAGE_CHANGED];
1550 : 2 : g_rec_mutex_unlock (&emission->mutex);
1551 : :
1552 : 2 : g_idle_add_full (G_PRIORITY_DEFAULT,
1553 : : emit_change_main,
1554 : : g_steal_pointer (&emission),
1555 : : NULL);
1556 : : }
1557 : :
|