libpurple/purpleconversationmanager.c

changeset 43283
01eb1bbf4186
parent 43132
b8a7d50eb1ae
child 43284
50c1bcc45576
equal deleted inserted replaced
43282:4968ebe46578 43283:01eb1bbf4186
18 * 18 *
19 * You should have received a copy of the GNU General Public License along with 19 * You should have received a copy of the GNU General Public License along with
20 * this library; if not, see <https://www.gnu.org/licenses/>. 20 * this library; if not, see <https://www.gnu.org/licenses/>.
21 */ 21 */
22 22
23 #include <seagull.h>
24
23 #include "purpleconversationmanager.h" 25 #include "purpleconversationmanager.h"
24 #include "purpleconversationmanagerprivate.h" 26 #include "purpleconversationmanagerprivate.h"
25 27
26 #include "core.h" 28 #include "core.h"
27 #include "purplecontact.h" 29 #include "purplecontact.h"
48 50
49 struct _PurpleConversationManager { 51 struct _PurpleConversationManager {
50 GObject parent; 52 GObject parent;
51 53
52 char *filename; 54 char *filename;
55 SeagullSqlite3 *db;
56 gboolean database_initialized;
57
58 SeagullStatement *delete_tags;
59 SeagullStatement *insert_properties;
60 SeagullStatement *insert_tags;
53 61
54 GPtrArray *conversations; 62 GPtrArray *conversations;
55 }; 63 };
56 64
57 static PurpleConversationManager *default_manager = NULL; 65 static PurpleConversationManager *default_manager = NULL;
58 66
59 typedef gboolean (*PurpleConversationManagerCompareFunc)(PurpleConversation *conversation, gpointer userdata); 67 typedef gboolean (*PurpleConversationManagerCompareFunc)(PurpleConversation *conversation, gpointer userdata);
68
69 #define RESOURCE_PATH "/im/pidgin/libpurple/conversationmanager"
60 70
61 /****************************************************************************** 71 /******************************************************************************
62 * Helpers 72 * Helpers
63 *****************************************************************************/ 73 *****************************************************************************/
64 static void 74 static void
75 purple_conversation_manager_initialize_database(PurpleConversationManager *manager)
76 {
77 SeagullStatement *stmt = NULL;
78 GError *error = NULL;
79 gboolean result = FALSE;
80 const char *migrations[] = {
81 "01-initial.sql",
82 NULL
83 };
84
85
86 if(!purple_strempty(manager->filename)) {
87 manager->db = seagull_db_new_from_file(manager->filename, &error);
88 } else {
89 manager->db = seagull_db_new_in_memory(&error);
90 }
91
92 if(error != NULL) {
93 g_warning("failed to create database: %s", error->message);
94 g_clear_error(&error);
95
96 return;
97 }
98
99 /* Run the migrations. */
100 result = seagull_migrations_run_from_resources(manager->db,
101 RESOURCE_PATH "/migrations",
102 migrations,
103 &error);
104
105 if(!result) {
106 g_warning("failed to run migrations: %s", error->message);
107 g_clear_error(&error);
108
109 return;
110 }
111
112 stmt = seagull_statement_new_from_resource(manager->db,
113 RESOURCE_PATH "/statements/delete-tags.sql",
114 &error);
115 if(error != NULL) {
116 g_warning("failed to loaded delete-tags statement: %s",
117 error->message);
118 g_clear_error(&error);
119
120 return;
121 }
122 manager->delete_tags = stmt;
123
124 stmt = seagull_statement_new_from_resource(manager->db,
125 RESOURCE_PATH "/statements/insert-properties.sql",
126 &error);
127 if(error != NULL) {
128 g_warning("failed to loaded insert-properties statement: %s",
129 error->message);
130 g_clear_error(&error);
131
132 return;
133 }
134 manager->insert_properties = stmt;
135
136 stmt = seagull_statement_new_from_resource(manager->db,
137 RESOURCE_PATH "/statements/insert-tags.sql",
138 &error);
139 if(error != NULL) {
140 g_warning("failed to loaded insert-tags statement: %s",
141 error->message);
142 g_clear_error(&error);
143
144 return;
145 }
146 manager->insert_tags = stmt;
147
148 manager->database_initialized = TRUE;
149 }
150
151 static void
65 purple_conversation_manager_set_filename(PurpleConversationManager *manager, 152 purple_conversation_manager_set_filename(PurpleConversationManager *manager,
66 const char *filename) 153 const char *filename)
67 { 154 {
68 g_return_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager)); 155 g_return_if_fail(PURPLE_IS_CONVERSATION_MANAGER(manager));
69 156
70 if(g_set_str(&manager->filename, filename)) { 157 if(g_set_str(&manager->filename, filename)) {
158 purple_conversation_manager_initialize_database(manager);
159
71 g_object_notify_by_pspec(G_OBJECT(manager), properties[PROP_FILENAME]); 160 g_object_notify_by_pspec(G_OBJECT(manager), properties[PROP_FILENAME]);
161 }
162 }
163
164 static gboolean
165 purple_conversation_manager_save_conversation_properties(PurpleConversationManager *manager,
166 PurpleConversation *conversation)
167 {
168 PurpleAccount *account = NULL;
169 PurpleContactInfo *contact = NULL;
170 GError *error = NULL;
171 gboolean success = FALSE;
172
173 /* Bind the properties for the conversation. */
174 success = seagull_statement_bind_object(manager->insert_properties,
175 "conv_", G_OBJECT(conversation),
176 &error);
177 if(!success) {
178 g_warning("failed to bind conversation to insert properties: %s",
179 error->message);
180 g_clear_error(&error);
181
182 return FALSE;
183 }
184
185 /* Bind the account */
186 account = purple_conversation_get_account(conversation);
187 success = seagull_statement_bind_text(manager->insert_properties,
188 ":account_id",
189 purple_account_get_id(account),
190 -1,
191 NULL,
192 &error);
193 if(!success) {
194 g_warning("failed to bind account_id to insert properties: %s",
195 error->message);
196 g_clear_error(&error);
197
198 return FALSE;
199 }
200
201 /* If we have a creator, bind it. */
202 contact = purple_conversation_get_creator(conversation);
203 if(PURPLE_IS_CONTACT_INFO(contact)) {
204 success = seagull_statement_bind_text(manager->insert_properties,
205 ":creator_id",
206 purple_contact_info_get_id(contact),
207 -1,
208 NULL,
209 &error);
210 } else {
211 success = seagull_statement_bind_null(manager->insert_properties,
212 ":creator_id",
213 &error);
214 }
215
216 if(!success) {
217 g_warning("failed to bind creator_id to insert properties: %s",
218 error->message);
219 g_clear_error(&error);
220
221 return FALSE;
222 }
223
224 /* If we have a topic author, bind it. */
225 contact = purple_conversation_get_topic_author(conversation);
226 if(PURPLE_IS_CONTACT_INFO(contact)) {
227 success = seagull_statement_bind_text(manager->insert_properties,
228 ":topic_author_id",
229 purple_contact_info_get_id(contact),
230 -1,
231 NULL,
232 &error);
233 } else {
234 success = seagull_statement_bind_null(manager->insert_properties,
235 ":topic_author_id",
236 &error);
237 }
238
239 if(!success) {
240 g_warning("failed to bind creator_id to insert properties: %s",
241 error->message);
242 g_clear_error(&error);
243
244 return FALSE;
245 }
246
247 /* The return value of step is whether or not there is data to read. Since
248 * this is an insert statement, we can ignore it.
249 */
250 seagull_statement_step(manager->insert_properties, &error);
251 if(error != NULL) {
252 g_warning("failed to step insert statement: %s", error->message);
253 g_clear_error(&error);
254
255 return FALSE;
256 }
257
258 return TRUE;
259 }
260
261 static gboolean
262 purple_conversation_manager_save_conversation_tags(PurpleConversationManager *manager,
263 PurpleConversation *conversation)
264 {
265 PurpleAccount *account = NULL;
266 PurpleTags *tags = NULL;
267 GError *error = NULL;
268 const char *conversation_id = NULL;
269 const char *account_id = NULL;
270 gboolean success = FALSE;
271
272 /* Grab the conversation and account ids as we need them for everything. */
273 conversation_id = purple_conversation_get_id(conversation);
274 account = purple_conversation_get_account(conversation);
275 account_id = purple_account_get_id(account);
276
277 /* Bind the conversation id to the delete statement. */
278 success = seagull_statement_bind_text(manager->delete_tags,
279 ":conversation_id",
280 conversation_id,
281 -1,
282 NULL,
283 &error);
284 if(!success) {
285 g_warning("failed to bind conversation_id to delete tags: %s",
286 error->message);
287 g_clear_error(&error);
288
289 return FALSE;
290 }
291
292 /* Bind the account id to the delete statement. */
293 success = seagull_statement_bind_text(manager->delete_tags,
294 ":account_id",
295 account_id,
296 -1,
297 NULL,
298 &error);
299 if(!success) {
300 g_warning("failed to bind account_id to delete tags: %s",
301 error->message);
302 g_clear_error(&error);
303
304 return FALSE;
305 }
306
307 /* Delete the existing tags, since we just replace everything. */
308 seagull_statement_step(manager->delete_tags, &error);
309 if(error != NULL) {
310 g_warning("failed to delete tags: %s", error->message);
311 g_clear_error(&error);
312
313 return FALSE;
314 }
315
316 /* Bind the conversation id to the insert statement. */
317 success = seagull_statement_bind_text(manager->insert_tags,
318 ":conversation_id",
319 conversation_id,
320 -1,
321 NULL,
322 &error);
323 if(!success) {
324 g_warning("failed to bind conversation_id to insert tags: %s",
325 error->message);
326 g_clear_error(&error);
327
328 return FALSE;
329 }
330
331 /* Bind the account id to the insert statement. */
332 success = seagull_statement_bind_text(manager->insert_tags,
333 ":account_id",
334 account_id,
335 -1,
336 NULL,
337 &error);
338 if(!success) {
339 g_warning("failed to bind account_id to insert tags: %s",
340 error->message);
341 g_clear_error(&error);
342
343 return FALSE;
344 }
345
346 /* Now run through tags and insert them. */
347 tags = purple_conversation_get_tags(conversation);
348 for(GList *l = purple_tags_get_all(tags); l != NULL; l = l->next) {
349 const char *tag = l->data;
350
351 success = seagull_statement_bind_text(manager->insert_tags,
352 ":tag",
353 tag,
354 -1,
355 NULL,
356 &error);
357
358 if(!success) {
359 g_warning("failed to bind tag to insert tags: %s",
360 error->message);
361 g_clear_error(&error);
362
363 return FALSE;
364 }
365
366 /* Step the statement. */
367 seagull_statement_step(manager->insert_tags, &error);
368 if(error != NULL) {
369 g_warning("sql: %s", seagull_statement_get_expanded_sql(manager->insert_tags));
370 g_warning("failed to insert tag: %s", error->message);
371 g_clear_error(&error);
372
373 return FALSE;
374 }
375
376 if(!seagull_statement_reset(manager->insert_tags, &error)) {
377 g_warning("sql: %s", seagull_statement_get_expanded_sql(manager->insert_tags));
378 g_warning("failed to reset the insert tags statement: %s",
379 error->message);
380 g_clear_error(&error);
381
382 return FALSE;
383 }
384 }
385
386 return TRUE;
387 }
388
389 static void
390 purple_conversation_manager_save_conversation(PurpleConversationManager *manager,
391 PurpleConversation *conversation)
392 {
393 GError *error = NULL;
394 gboolean success = FALSE;
395
396 if(!manager->database_initialized) {
397 return;
398 }
399
400 /* Start a transaction so that everything is atomic. */
401 if(!seagull_transaction_begin(manager->db, &error)) {
402 g_warning("failed to begin transaction to save conversation: %s",
403 error->message);
404 g_clear_error(&error);
405
406 return;
407 }
408
409 /* Save the conversation properties. */
410 success = purple_conversation_manager_save_conversation_properties(manager,
411 conversation);
412 if(!seagull_statement_clear_bindings(manager->insert_properties,
413 &error))
414 {
415 g_warning("failed to clear bindings on the insert properties "
416 "statement: %s",
417 error->message);
418 g_clear_error(&error);
419 }
420
421 if(!seagull_statement_reset(manager->insert_properties, &error)) {
422 g_warning("failed to reset the insert properties statement: %s",
423 error->message);
424 g_clear_error(&error);
425 }
426 if(!success) {
427 if(!seagull_transaction_rollback(manager->db, &error)) {
428 g_warning("failed to rollback transaction: %s", error->message);
429 g_clear_error(&error);
430 }
431
432 return;
433 }
434
435 /* Save the conversation tags. */
436 success = purple_conversation_manager_save_conversation_tags(manager,
437 conversation);
438 if(!seagull_statement_clear_bindings(manager->delete_tags,
439 &error))
440 {
441 g_warning("failed to clear bindings on the delete tags statement: "
442 "%s",
443 error->message);
444 g_clear_error(&error);
445 }
446
447 if(!seagull_statement_reset(manager->delete_tags, &error)) {
448 g_warning("failed to reset the delete tags statement: %s",
449 error->message);
450 g_clear_error(&error);
451 }
452
453 if(!seagull_statement_clear_bindings(manager->insert_tags,
454 &error))
455 {
456 g_warning("failed to clear bindings on the insert tags statement: "
457 "%s",
458 error->message);
459 g_clear_error(&error);
460 }
461
462 if(!seagull_statement_reset(manager->insert_tags, &error)) {
463 g_warning("failed to reset the insert tags statement: %s",
464 error->message);
465 g_clear_error(&error);
466 }
467
468 if(!success) {
469 if(!seagull_transaction_rollback(manager->db, &error)) {
470 g_warning("failed to rollback transaction: %s", error->message);
471 g_clear_error(&error);
472 }
473
474 return;
475 }
476
477 /* Commit the transaction. */
478 if(!seagull_transaction_commit(manager->db, &error)) {
479 g_warning("failed to commit transaction to save conversation: %s",
480 error->message);
481 g_clear_error(&error);
72 } 482 }
73 } 483 }
74 484
75 /****************************************************************************** 485 /******************************************************************************
76 * Callbacks 486 * Callbacks
78 static void 488 static void
79 purple_conversation_manager_conversation_changed_cb(GObject *source, 489 purple_conversation_manager_conversation_changed_cb(GObject *source,
80 GParamSpec *pspec, 490 GParamSpec *pspec,
81 gpointer data) 491 gpointer data)
82 { 492 {
493 purple_conversation_manager_save_conversation(data,
494 PURPLE_CONVERSATION(source));
495
83 g_signal_emit(data, signals[SIG_CONVERSATION_CHANGED], 496 g_signal_emit(data, signals[SIG_CONVERSATION_CHANGED],
84 g_param_spec_get_name_quark(pspec), 497 g_param_spec_get_name_quark(pspec),
85 source, pspec); 498 source, pspec);
86 } 499 }
87 500
138 551
139 /****************************************************************************** 552 /******************************************************************************
140 * GObject Implementation 553 * GObject Implementation
141 *****************************************************************************/ 554 *****************************************************************************/
142 static void 555 static void
143 purple_conversation_manager_init(PurpleConversationManager *manager) {
144 manager->conversations = g_ptr_array_new_full(10, g_object_unref);
145 }
146
147 static void
148 purple_conversation_manager_finalize(GObject *obj) { 556 purple_conversation_manager_finalize(GObject *obj) {
149 PurpleConversationManager *manager = PURPLE_CONVERSATION_MANAGER(obj); 557 PurpleConversationManager *manager = PURPLE_CONVERSATION_MANAGER(obj);
150 558
151 g_clear_pointer(&manager->conversations, g_ptr_array_unref); 559 g_clear_pointer(&manager->conversations, g_ptr_array_unref);
152 g_clear_pointer(&manager->filename, g_free); 560 g_clear_pointer(&manager->filename, g_free);
561
562 g_clear_object(&manager->insert_properties);
563 g_clear_object(&manager->insert_tags);
564 g_clear_object(&manager->delete_tags);
565
566 if(manager->db != NULL) {
567 GError *error = NULL;
568
569 seagull_db_close(manager->db, &error);
570 if(error != NULL) {
571 g_warning("failed to close database: %s", error->message);
572 g_clear_error(&error);
573 }
574 }
153 575
154 G_OBJECT_CLASS(purple_conversation_manager_parent_class)->finalize(obj); 576 G_OBJECT_CLASS(purple_conversation_manager_parent_class)->finalize(obj);
155 } 577 }
156 578
157 static void 579 static void
196 break; 618 break;
197 } 619 }
198 } 620 }
199 621
200 static void 622 static void
623 purple_conversation_manager_init(PurpleConversationManager *manager) {
624 manager->conversations = g_ptr_array_new_full(10, g_object_unref);
625 manager->database_initialized = FALSE;
626 }
627
628 static void
201 purple_conversation_manager_class_init(PurpleConversationManagerClass *klass) { 629 purple_conversation_manager_class_init(PurpleConversationManagerClass *klass) {
202 GObjectClass *obj_class = G_OBJECT_CLASS(klass); 630 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
203 631
204 obj_class->finalize = purple_conversation_manager_finalize; 632 obj_class->finalize = purple_conversation_manager_finalize;
205 obj_class->get_property = purple_conversation_manager_get_property; 633 obj_class->get_property = purple_conversation_manager_get_property;
391 /* Tell everyone about the new conversation. */ 819 /* Tell everyone about the new conversation. */
392 g_signal_emit(manager, signals[SIG_ADDED], 0, conversation); 820 g_signal_emit(manager, signals[SIG_ADDED], 0, conversation);
393 g_list_model_items_changed(G_LIST_MODEL(manager), position, 0, 1); 821 g_list_model_items_changed(G_LIST_MODEL(manager), position, 0, 1);
394 g_object_notify_by_pspec(G_OBJECT(manager), properties[PROP_N_ITEMS]); 822 g_object_notify_by_pspec(G_OBJECT(manager), properties[PROP_N_ITEMS]);
395 823
824 purple_conversation_manager_save_conversation(manager, conversation);
825
396 return TRUE; 826 return TRUE;
397 } 827 }
398 828
399 PurpleConversationManager * 829 PurpleConversationManager *
400 purple_conversation_manager_new(const char *filename) { 830 purple_conversation_manager_new(const char *filename) {

mercurial