Mercurial > pidgin
comparison src/savedstatuses.c @ 10418:bed2c96bc1fb
[gaim-migrate @ 11669]
I split the status-saving code into it's own little API, because it
really is separate from the other status.c
savedstatuses.c sits on top of the rest of the status API.
And you can delete saved statuses now.
committer: Tailor Script <tailor@pidgin.im>
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Sun, 26 Dec 2004 00:46:26 +0000 |
parents | |
children | c9b1f3fac753 |
comparison
equal
deleted
inserted
replaced
10417:823ad21cd95a | 10418:bed2c96bc1fb |
---|---|
1 /** | |
2 * @file savedstatus.c Saved Status API | |
3 * @ingroup core | |
4 * | |
5 * gaim | |
6 * | |
7 * Gaim is the legal property of its developers, whose names are too numerous | |
8 * to list here. Please refer to the COPYRIGHT file distributed with this | |
9 * source distribution. | |
10 * | |
11 * This program is free software; you can redistribute it and/or modify | |
12 * it under the terms of the GNU General Public License as published by | |
13 * the Free Software Foundation; either version 2 of the License, or | |
14 * (at your option) any later version. | |
15 * | |
16 * This program is distributed in the hope that it will be useful, | |
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 * GNU General Public License for more details. | |
20 * | |
21 * You should have received a copy of the GNU General Public License | |
22 * along with this program; if not, write to the Free Software | |
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
24 */ | |
25 #include "internal.h" | |
26 | |
27 #include "debug.h" | |
28 #include "notify.h" | |
29 #include "savedstatuses.h" | |
30 #include "status.h" | |
31 #include "util.h" | |
32 #include "xmlnode.h" | |
33 | |
34 /** | |
35 * The information of a snap-shot of the statuses of all | |
36 * your accounts. Basically these are your saved away messages. | |
37 * There is an overall status and message that applies to | |
38 * all your accounts, and then each individual account can | |
39 * optionally have a different custom status and message. | |
40 * | |
41 * The changes to status.xml caused by the new status API | |
42 * are fully backward compatible. The new status API just | |
43 * adds the optional sub-statuses to the XML file. | |
44 */ | |
45 struct _GaimStatusSaved | |
46 { | |
47 char *title; | |
48 GaimStatusPrimitive type; | |
49 char *message; | |
50 | |
51 GList *substatuses; /**< A list of GaimStatusSavedSub's. */ | |
52 }; | |
53 | |
54 /* | |
55 * TODO: If an account is deleted, need to also delete any associated | |
56 * GaimStatusSavedSub's. | |
57 * TODO: If a GaimStatusType is deleted, need to also delete any | |
58 * associated GaimStatusSavedSub's? | |
59 */ | |
60 struct _GaimStatusSavedSub | |
61 { | |
62 GaimAccount *account; | |
63 const GaimStatusType *type; | |
64 char *message; | |
65 }; | |
66 | |
67 static GList *saved_statuses = NULL; | |
68 gboolean have_read_saved_statuses = FALSE; | |
69 static guint statuses_save_timer = 0; | |
70 | |
71 /************************************************************************** | |
72 * Helper functions | |
73 **************************************************************************/ | |
74 | |
75 /** | |
76 * Elements of this array correspond to the GaimStatusPrimitive | |
77 * enumeration. | |
78 */ | |
79 static const char *primitive_names[] = | |
80 { | |
81 "unset", | |
82 "offline", | |
83 "available", | |
84 "unavailable", | |
85 "hidden", | |
86 "away", | |
87 "extended_away" | |
88 }; | |
89 | |
90 static GaimStatusPrimitive | |
91 gaim_primitive_get_type(const char *name) | |
92 { | |
93 int i; | |
94 | |
95 g_return_val_if_fail(name != NULL, GAIM_STATUS_UNSET); | |
96 | |
97 for (i = 0; i < GAIM_STATUS_NUM_PRIMITIVES; i++) | |
98 { | |
99 if (!strcmp(name, primitive_names[i])) | |
100 return i; | |
101 } | |
102 | |
103 return GAIM_STATUS_UNSET; | |
104 } | |
105 | |
106 static void | |
107 free_statussavedsub(GaimStatusSavedSub *substatus) | |
108 { | |
109 g_return_if_fail(substatus != NULL); | |
110 | |
111 g_free(substatus->message); | |
112 g_free(substatus); | |
113 } | |
114 | |
115 static void | |
116 free_statussaved(GaimStatusSaved *status) | |
117 { | |
118 g_return_if_fail(status != NULL); | |
119 | |
120 g_free(status->title); | |
121 g_free(status->message); | |
122 | |
123 while (status->substatuses != NULL) | |
124 { | |
125 GaimStatusSavedSub *substatus = status->substatuses->data; | |
126 status->substatuses = g_list_remove(status->substatuses, substatus); | |
127 free_statussavedsub(substatus); | |
128 } | |
129 | |
130 g_free(status); | |
131 } | |
132 | |
133 | |
134 /************************************************************************** | |
135 * Saved status writting to disk | |
136 **************************************************************************/ | |
137 | |
138 static xmlnode * | |
139 substatus_to_xmlnode(GaimStatusSavedSub *substatus) | |
140 { | |
141 xmlnode *node, *child; | |
142 | |
143 node = xmlnode_new("substatus"); | |
144 | |
145 child = xmlnode_new("account"); | |
146 xmlnode_set_attrib(node, "protocol", | |
147 gaim_account_get_protocol_id(substatus->account)); | |
148 xmlnode_insert_data(child, | |
149 gaim_account_get_username(substatus->account), -1); | |
150 xmlnode_insert_child(node, child); | |
151 | |
152 child = xmlnode_new("state"); | |
153 xmlnode_insert_data(child, gaim_status_type_get_id(substatus->type), -1); | |
154 xmlnode_insert_child(node, child); | |
155 | |
156 if (substatus->message != NULL) | |
157 { | |
158 child = xmlnode_new("message"); | |
159 xmlnode_insert_data(child, substatus->message, -1); | |
160 xmlnode_insert_child(node, child); | |
161 } | |
162 | |
163 return node; | |
164 } | |
165 | |
166 static xmlnode * | |
167 status_to_xmlnode(GaimStatusSaved *status) | |
168 { | |
169 xmlnode *node, *child; | |
170 GList *cur; | |
171 | |
172 node = xmlnode_new("status"); | |
173 xmlnode_set_attrib(node, "name", status->title); | |
174 | |
175 child = xmlnode_new("state"); | |
176 xmlnode_insert_data(child, primitive_names[status->type], -1); | |
177 xmlnode_insert_child(node, child); | |
178 | |
179 child = xmlnode_new("message"); | |
180 xmlnode_insert_data(child, status->message, -1); | |
181 xmlnode_insert_child(node, child); | |
182 | |
183 for (cur = status->substatuses; cur != NULL; cur = cur->next) | |
184 { | |
185 child = substatus_to_xmlnode(cur->data); | |
186 xmlnode_insert_child(node, child); | |
187 } | |
188 | |
189 return node; | |
190 } | |
191 | |
192 static xmlnode * | |
193 statuses_to_xmlnode(void) | |
194 { | |
195 xmlnode *node, *child; | |
196 GList *cur; | |
197 | |
198 node = xmlnode_new("statuses"); | |
199 xmlnode_set_attrib(node, "version", "1"); | |
200 | |
201 for (cur = saved_statuses; cur != NULL; cur = cur->next) | |
202 { | |
203 child = status_to_xmlnode(cur->data); | |
204 xmlnode_insert_child(node, child); | |
205 } | |
206 | |
207 return node; | |
208 } | |
209 | |
210 static void | |
211 sync_statuses(void) | |
212 { | |
213 xmlnode *statuses; | |
214 char *data; | |
215 | |
216 if (!have_read_saved_statuses) { | |
217 gaim_debug_error("status", "Attempted to save statuses before they " | |
218 "were read!\n"); | |
219 return; | |
220 } | |
221 | |
222 statuses = statuses_to_xmlnode(); | |
223 data = xmlnode_to_formatted_str(statuses, NULL); | |
224 gaim_util_write_data_to_file("status.xml", data, -1); | |
225 g_free(data); | |
226 xmlnode_free(statuses); | |
227 } | |
228 | |
229 static gboolean | |
230 save_callback(gpointer data) | |
231 { | |
232 sync_statuses(); | |
233 statuses_save_timer = 0; | |
234 return FALSE; | |
235 } | |
236 | |
237 static void | |
238 schedule_save(void) | |
239 { | |
240 if (statuses_save_timer != 0) | |
241 gaim_timeout_remove(statuses_save_timer); | |
242 statuses_save_timer = gaim_timeout_add(1000, save_callback, NULL); | |
243 } | |
244 | |
245 | |
246 /************************************************************************** | |
247 * Saved status reading from disk | |
248 **************************************************************************/ | |
249 static GaimStatusSavedSub * | |
250 parse_substatus(xmlnode *substatus) | |
251 { | |
252 GaimStatusSavedSub *ret; | |
253 xmlnode *node; | |
254 char *data = NULL; | |
255 | |
256 ret = g_new0(GaimStatusSavedSub, 1); | |
257 | |
258 /* Read the account */ | |
259 node = xmlnode_get_child(substatus, "account"); | |
260 if (node != NULL) | |
261 { | |
262 char *acct_name; | |
263 const char *protocol; | |
264 acct_name = xmlnode_get_data(node); | |
265 protocol = xmlnode_get_attrib(node, "protocol"); | |
266 if ((acct_name != NULL) && (protocol != NULL)) | |
267 ret->account = gaim_accounts_find(acct_name, protocol); | |
268 g_free(acct_name); | |
269 } | |
270 | |
271 if (ret->account == NULL) | |
272 { | |
273 g_free(ret); | |
274 return NULL; | |
275 } | |
276 | |
277 /* Read the state */ | |
278 node = xmlnode_get_child(substatus, "state"); | |
279 if (node != NULL) | |
280 data = xmlnode_get_data(node); | |
281 if (data != NULL) { | |
282 ret->type = gaim_status_type_find_with_id( | |
283 ret->account->status_types, data); | |
284 g_free(data); | |
285 data = NULL; | |
286 } | |
287 | |
288 /* Read the message */ | |
289 node = xmlnode_get_child(substatus, "message"); | |
290 if (node != NULL) | |
291 data = xmlnode_get_data(node); | |
292 if (data != NULL) | |
293 ret->message = data; | |
294 | |
295 return ret; | |
296 } | |
297 | |
298 /** | |
299 * Parse a saved status and add it to the saved_statuses linked list. | |
300 * | |
301 * Here's an example of the XML for a saved status: | |
302 * <status name="Girls"> | |
303 * <state>away</state> | |
304 * <message>I like the way that they walk | |
305 * And it's chill to hear them talk | |
306 * And I can always make them smile | |
307 * From White Castle to the Nile</message> | |
308 * <substatus> | |
309 * <account protocol='prpl-oscar'>markdoliner</account> | |
310 * <state>available</state> | |
311 * <message>The ladies man is here to answer your queries.</message> | |
312 * </substatus> | |
313 * <substatus> | |
314 * <account protocol='prpl-oscar'>giantgraypanda</account> | |
315 * <state>away</state> | |
316 * <message>A.C. ain't in charge no more.</message> | |
317 * </substatus> | |
318 * </status> | |
319 * | |
320 * I know. Moving, huh? | |
321 */ | |
322 static GaimStatusSaved * | |
323 parse_status(xmlnode *status) | |
324 { | |
325 GaimStatusSaved *ret; | |
326 xmlnode *node; | |
327 const char *attrib; | |
328 char *data = NULL; | |
329 int i; | |
330 | |
331 ret = g_new0(GaimStatusSaved, 1); | |
332 | |
333 /* Read the title */ | |
334 attrib = xmlnode_get_attrib(status, "name"); | |
335 if (attrib == NULL) | |
336 attrib = "No Title"; | |
337 /* Ensure the title is unique */ | |
338 ret->title = g_strdup(attrib); | |
339 i = 2; | |
340 while (gaim_savedstatuses_find(ret->title) != NULL) | |
341 { | |
342 g_free(ret->title); | |
343 ret->title = g_strdup_printf("%s %d", attrib, i); | |
344 i++; | |
345 } | |
346 | |
347 /* Read the primitive status type */ | |
348 node = xmlnode_get_child(status, "state"); | |
349 if (node != NULL) | |
350 data = xmlnode_get_data(node); | |
351 if (data != NULL) { | |
352 ret->type = gaim_primitive_get_type(data); | |
353 g_free(data); | |
354 data = NULL; | |
355 } | |
356 | |
357 /* Read the message */ | |
358 node = xmlnode_get_child(status, "message"); | |
359 if (node != NULL) | |
360 data = xmlnode_get_data(node); | |
361 if (data != NULL) | |
362 ret->message = data; | |
363 | |
364 /* Read substatuses */ | |
365 for (node = xmlnode_get_child(status, "status"); node != NULL; | |
366 node = xmlnode_get_next_twin(node)) | |
367 { | |
368 GaimStatusSavedSub *new; | |
369 new = parse_substatus(node); | |
370 if (new != NULL) | |
371 ret->substatuses = g_list_append(ret->substatuses, new); | |
372 } | |
373 | |
374 return ret; | |
375 } | |
376 | |
377 /** | |
378 * Read the saved statuses from a file in the Gaim user dir. | |
379 * | |
380 * @return TRUE on success, FALSE on failure (if the file can not | |
381 * be opened, or if it contains invalid XML). | |
382 */ | |
383 static gboolean | |
384 read_statuses(const char *filename) | |
385 { | |
386 GError *error; | |
387 gchar *contents = NULL; | |
388 gsize length; | |
389 xmlnode *statuses, *status; | |
390 | |
391 gaim_debug_info("status", "Reading %s\n", filename); | |
392 | |
393 if (!g_file_get_contents(filename, &contents, &length, &error)) | |
394 { | |
395 gaim_debug_error("status", "Error reading statuses: %s\n", | |
396 error->message); | |
397 g_error_free(error); | |
398 return FALSE; | |
399 } | |
400 | |
401 statuses = xmlnode_from_str(contents, length); | |
402 | |
403 if (statuses == NULL) | |
404 { | |
405 FILE *backup; | |
406 gchar *name; | |
407 gaim_debug_error("status", "Error parsing statuses\n"); | |
408 name = g_strdup_printf("%s~", filename); | |
409 if ((backup = fopen(name, "w"))) | |
410 { | |
411 fwrite(contents, length, 1, backup); | |
412 fclose(backup); | |
413 chmod(name, S_IRUSR | S_IWUSR); | |
414 } | |
415 else | |
416 { | |
417 gaim_debug_error("status", "Unable to write backup %s\n", name); | |
418 } | |
419 g_free(name); | |
420 g_free(contents); | |
421 return FALSE; | |
422 } | |
423 | |
424 g_free(contents); | |
425 | |
426 for (status = xmlnode_get_child(statuses, "status"); status != NULL; | |
427 status = xmlnode_get_next_twin(status)) | |
428 { | |
429 GaimStatusSaved *new; | |
430 new = parse_status(status); | |
431 saved_statuses = g_list_append(saved_statuses, new); | |
432 } | |
433 | |
434 gaim_debug_info("status", "Finished reading statuses\n"); | |
435 | |
436 xmlnode_free(statuses); | |
437 | |
438 return TRUE; | |
439 } | |
440 | |
441 static void | |
442 load_statuses(void) | |
443 { | |
444 const char *user_dir = gaim_user_dir(); | |
445 gchar *filename; | |
446 gchar *msg; | |
447 | |
448 g_return_if_fail(user_dir != NULL); | |
449 | |
450 have_read_saved_statuses = TRUE; | |
451 | |
452 filename = g_build_filename(user_dir, "status.xml", NULL); | |
453 | |
454 if (g_file_test(filename, G_FILE_TEST_EXISTS)) | |
455 { | |
456 if (!read_statuses(filename)) | |
457 { | |
458 msg = g_strdup_printf(_("An error was encountered parsing the " | |
459 "file containing your saved statuses (%s). They " | |
460 "have not been loaded, and the old file has been " | |
461 "renamed to status.xml~."), filename); | |
462 gaim_notify_error(NULL, NULL, _("Saved Statuses Error"), msg); | |
463 g_free(msg); | |
464 } | |
465 } | |
466 | |
467 g_free(filename); | |
468 } | |
469 | |
470 | |
471 /************************************************************************** | |
472 * Saved status API | |
473 **************************************************************************/ | |
474 GaimStatusSaved * | |
475 gaim_savedstatuses_new(const char *title, GaimStatusPrimitive type) | |
476 { | |
477 GaimStatusSaved *status; | |
478 | |
479 status = g_new0(GaimStatusSaved, 1); | |
480 status->title = g_strdup(title); | |
481 status->type = type; | |
482 | |
483 saved_statuses = g_list_append(saved_statuses, status); | |
484 | |
485 schedule_save(); | |
486 | |
487 return status; | |
488 } | |
489 | |
490 gboolean | |
491 gaim_savedstatuses_delete(const char *title) | |
492 { | |
493 GaimStatusSaved *status; | |
494 | |
495 status = gaim_savedstatuses_find(title); | |
496 | |
497 if (status == NULL) | |
498 return FALSE; | |
499 | |
500 saved_statuses = g_list_remove(saved_statuses, status); | |
501 free_statussaved(status); | |
502 | |
503 schedule_save(); | |
504 | |
505 return TRUE; | |
506 } | |
507 | |
508 const GList * | |
509 gaim_savedstatuses_get_all(void) | |
510 { | |
511 return saved_statuses; | |
512 } | |
513 | |
514 GaimStatusSaved * | |
515 gaim_savedstatuses_find(const char *title) | |
516 { | |
517 GList *l; | |
518 GaimStatusSaved *status; | |
519 | |
520 for (l = saved_statuses; l != NULL; l = g_list_next(l)) | |
521 { | |
522 status = (GaimStatusSaved *)l->data; | |
523 if (!strcmp(status->title, title)) | |
524 return status; | |
525 } | |
526 | |
527 return NULL; | |
528 } | |
529 | |
530 const char * | |
531 gaim_savedstatuses_get_title(const GaimStatusSaved *saved_status) | |
532 { | |
533 return saved_status->title; | |
534 } | |
535 | |
536 GaimStatusPrimitive | |
537 gaim_savedstatuses_get_type(const GaimStatusSaved *saved_status) | |
538 { | |
539 return saved_status->type; | |
540 } | |
541 | |
542 const char * | |
543 gaim_savedstatuses_get_message(const GaimStatusSaved *saved_status) | |
544 { | |
545 return saved_status->message; | |
546 } | |
547 | |
548 void | |
549 gaim_savedstatuses_init(void) | |
550 { | |
551 load_statuses(); | |
552 } | |
553 | |
554 void | |
555 gaim_savedstatuses_uninit(void) | |
556 { | |
557 if (statuses_save_timer != 0) | |
558 { | |
559 gaim_timeout_remove(statuses_save_timer); | |
560 statuses_save_timer = 0; | |
561 sync_statuses(); | |
562 } | |
563 | |
564 while (saved_statuses != NULL) { | |
565 GaimStatusSaved *status = saved_statuses->data; | |
566 saved_statuses = g_list_remove(saved_statuses, status); | |
567 free_statussaved(status); | |
568 } | |
569 } |