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 }