| 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 |