10418
|
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 */
|
10419
|
45 struct _GaimSavedStatus
|
10418
|
46 {
|
|
47 char *title;
|
|
48 GaimStatusPrimitive type;
|
|
49 char *message;
|
|
50
|
10419
|
51 GList *substatuses; /**< A list of GaimSavedStatusSub's. */
|
10418
|
52 };
|
|
53
|
|
54 /*
|
|
55 * TODO: If an account is deleted, need to also delete any associated
|
10419
|
56 * GaimSavedStatusSub's.
|
10418
|
57 * TODO: If a GaimStatusType is deleted, need to also delete any
|
10419
|
58 * associated GaimSavedStatusSub's?
|
10418
|
59 */
|
10419
|
60 struct _GaimSavedStatusSub
|
10418
|
61 {
|
|
62 GaimAccount *account;
|
|
63 const GaimStatusType *type;
|
|
64 char *message;
|
|
65 };
|
|
66
|
10423
|
67 static GList *saved_statuses = NULL;
|
|
68 static guint statuses_save_timer = 0;
|
|
69 static gboolean statuses_loaded = FALSE;
|
10418
|
70
|
|
71 /**************************************************************************
|
|
72 * Helper functions
|
|
73 **************************************************************************/
|
|
74
|
|
75 static void
|
10419
|
76 free_statussavedsub(GaimSavedStatusSub *substatus)
|
10418
|
77 {
|
|
78 g_return_if_fail(substatus != NULL);
|
|
79
|
|
80 g_free(substatus->message);
|
|
81 g_free(substatus);
|
|
82 }
|
|
83
|
|
84 static void
|
10419
|
85 free_statussaved(GaimSavedStatus *status)
|
10418
|
86 {
|
|
87 g_return_if_fail(status != NULL);
|
|
88
|
|
89 g_free(status->title);
|
|
90 g_free(status->message);
|
|
91
|
|
92 while (status->substatuses != NULL)
|
|
93 {
|
10419
|
94 GaimSavedStatusSub *substatus = status->substatuses->data;
|
10418
|
95 status->substatuses = g_list_remove(status->substatuses, substatus);
|
|
96 free_statussavedsub(substatus);
|
|
97 }
|
|
98
|
|
99 g_free(status);
|
|
100 }
|
|
101
|
|
102
|
|
103 /**************************************************************************
|
|
104 * Saved status writting to disk
|
|
105 **************************************************************************/
|
|
106
|
|
107 static xmlnode *
|
10419
|
108 substatus_to_xmlnode(GaimSavedStatusSub *substatus)
|
10418
|
109 {
|
|
110 xmlnode *node, *child;
|
|
111
|
|
112 node = xmlnode_new("substatus");
|
|
113
|
10424
|
114 child = xmlnode_new_child(node, "account");
|
|
115 xmlnode_set_attrib(child, "protocol", gaim_account_get_protocol_id(substatus->account));
|
|
116 xmlnode_insert_data(child, gaim_account_get_username(substatus->account), -1);
|
10418
|
117
|
10424
|
118 child = xmlnode_new_child(node, "state");
|
10418
|
119 xmlnode_insert_data(child, gaim_status_type_get_id(substatus->type), -1);
|
|
120
|
|
121 if (substatus->message != NULL)
|
|
122 {
|
10424
|
123 child = xmlnode_new_child(node, "message");
|
10418
|
124 xmlnode_insert_data(child, substatus->message, -1);
|
|
125 }
|
|
126
|
|
127 return node;
|
|
128 }
|
|
129
|
|
130 static xmlnode *
|
10419
|
131 status_to_xmlnode(GaimSavedStatus *status)
|
10418
|
132 {
|
|
133 xmlnode *node, *child;
|
|
134 GList *cur;
|
|
135
|
|
136 node = xmlnode_new("status");
|
|
137 xmlnode_set_attrib(node, "name", status->title);
|
|
138
|
10424
|
139 child = xmlnode_new_child(node, "state");
|
|
140 xmlnode_insert_data(child, gaim_primitive_get_id_from_type(status->type), -1);
|
10418
|
141
|
10424
|
142 child = xmlnode_new_child(node, "message");
|
10418
|
143 xmlnode_insert_data(child, status->message, -1);
|
|
144
|
|
145 for (cur = status->substatuses; cur != NULL; cur = cur->next)
|
|
146 {
|
|
147 child = substatus_to_xmlnode(cur->data);
|
|
148 xmlnode_insert_child(node, child);
|
|
149 }
|
|
150
|
|
151 return node;
|
|
152 }
|
|
153
|
|
154 static xmlnode *
|
|
155 statuses_to_xmlnode(void)
|
|
156 {
|
|
157 xmlnode *node, *child;
|
|
158 GList *cur;
|
|
159
|
|
160 node = xmlnode_new("statuses");
|
10423
|
161 xmlnode_set_attrib(node, "version", "1.0");
|
10418
|
162
|
|
163 for (cur = saved_statuses; cur != NULL; cur = cur->next)
|
|
164 {
|
|
165 child = status_to_xmlnode(cur->data);
|
|
166 xmlnode_insert_child(node, child);
|
|
167 }
|
|
168
|
|
169 return node;
|
|
170 }
|
|
171
|
|
172 static void
|
|
173 sync_statuses(void)
|
|
174 {
|
10423
|
175 xmlnode *node;
|
10418
|
176 char *data;
|
|
177
|
10423
|
178 if (!statuses_loaded) {
|
10418
|
179 gaim_debug_error("status", "Attempted to save statuses before they "
|
|
180 "were read!\n");
|
|
181 return;
|
|
182 }
|
|
183
|
10423
|
184 node = statuses_to_xmlnode();
|
|
185 data = xmlnode_to_formatted_str(node, NULL);
|
10418
|
186 gaim_util_write_data_to_file("status.xml", data, -1);
|
|
187 g_free(data);
|
10423
|
188 xmlnode_free(node);
|
10418
|
189 }
|
|
190
|
|
191 static gboolean
|
|
192 save_callback(gpointer data)
|
|
193 {
|
|
194 sync_statuses();
|
|
195 statuses_save_timer = 0;
|
|
196 return FALSE;
|
|
197 }
|
|
198
|
|
199 static void
|
|
200 schedule_save(void)
|
|
201 {
|
|
202 if (statuses_save_timer != 0)
|
|
203 gaim_timeout_remove(statuses_save_timer);
|
|
204 statuses_save_timer = gaim_timeout_add(1000, save_callback, NULL);
|
|
205 }
|
|
206
|
|
207
|
|
208 /**************************************************************************
|
|
209 * Saved status reading from disk
|
|
210 **************************************************************************/
|
10419
|
211 static GaimSavedStatusSub *
|
10418
|
212 parse_substatus(xmlnode *substatus)
|
|
213 {
|
10419
|
214 GaimSavedStatusSub *ret;
|
10418
|
215 xmlnode *node;
|
|
216 char *data = NULL;
|
|
217
|
10419
|
218 ret = g_new0(GaimSavedStatusSub, 1);
|
10418
|
219
|
|
220 /* Read the account */
|
|
221 node = xmlnode_get_child(substatus, "account");
|
|
222 if (node != NULL)
|
|
223 {
|
|
224 char *acct_name;
|
|
225 const char *protocol;
|
|
226 acct_name = xmlnode_get_data(node);
|
|
227 protocol = xmlnode_get_attrib(node, "protocol");
|
|
228 if ((acct_name != NULL) && (protocol != NULL))
|
|
229 ret->account = gaim_accounts_find(acct_name, protocol);
|
|
230 g_free(acct_name);
|
|
231 }
|
|
232
|
|
233 if (ret->account == NULL)
|
|
234 {
|
|
235 g_free(ret);
|
|
236 return NULL;
|
|
237 }
|
|
238
|
|
239 /* Read the state */
|
|
240 node = xmlnode_get_child(substatus, "state");
|
|
241 if (node != NULL)
|
|
242 data = xmlnode_get_data(node);
|
|
243 if (data != NULL) {
|
|
244 ret->type = gaim_status_type_find_with_id(
|
|
245 ret->account->status_types, data);
|
|
246 g_free(data);
|
|
247 data = NULL;
|
|
248 }
|
|
249
|
|
250 /* Read the message */
|
|
251 node = xmlnode_get_child(substatus, "message");
|
|
252 if (node != NULL)
|
|
253 data = xmlnode_get_data(node);
|
|
254 if (data != NULL)
|
|
255 ret->message = data;
|
|
256
|
|
257 return ret;
|
|
258 }
|
|
259
|
|
260 /**
|
|
261 * Parse a saved status and add it to the saved_statuses linked list.
|
|
262 *
|
|
263 * Here's an example of the XML for a saved status:
|
|
264 * <status name="Girls">
|
|
265 * <state>away</state>
|
|
266 * <message>I like the way that they walk
|
|
267 * And it's chill to hear them talk
|
|
268 * And I can always make them smile
|
|
269 * From White Castle to the Nile</message>
|
|
270 * <substatus>
|
|
271 * <account protocol='prpl-oscar'>markdoliner</account>
|
|
272 * <state>available</state>
|
|
273 * <message>The ladies man is here to answer your queries.</message>
|
|
274 * </substatus>
|
|
275 * <substatus>
|
|
276 * <account protocol='prpl-oscar'>giantgraypanda</account>
|
|
277 * <state>away</state>
|
|
278 * <message>A.C. ain't in charge no more.</message>
|
|
279 * </substatus>
|
|
280 * </status>
|
|
281 *
|
|
282 * I know. Moving, huh?
|
|
283 */
|
10419
|
284 static GaimSavedStatus *
|
10418
|
285 parse_status(xmlnode *status)
|
|
286 {
|
10419
|
287 GaimSavedStatus *ret;
|
10418
|
288 xmlnode *node;
|
|
289 const char *attrib;
|
|
290 char *data = NULL;
|
|
291 int i;
|
|
292
|
10419
|
293 ret = g_new0(GaimSavedStatus, 1);
|
10418
|
294
|
|
295 /* Read the title */
|
|
296 attrib = xmlnode_get_attrib(status, "name");
|
|
297 if (attrib == NULL)
|
|
298 attrib = "No Title";
|
|
299 /* Ensure the title is unique */
|
|
300 ret->title = g_strdup(attrib);
|
|
301 i = 2;
|
10419
|
302 while (gaim_savedstatus_find(ret->title) != NULL)
|
10418
|
303 {
|
|
304 g_free(ret->title);
|
|
305 ret->title = g_strdup_printf("%s %d", attrib, i);
|
|
306 i++;
|
|
307 }
|
|
308
|
|
309 /* Read the primitive status type */
|
|
310 node = xmlnode_get_child(status, "state");
|
|
311 if (node != NULL)
|
|
312 data = xmlnode_get_data(node);
|
|
313 if (data != NULL) {
|
10419
|
314 ret->type = gaim_primitive_get_type_from_id(data);
|
10418
|
315 g_free(data);
|
|
316 data = NULL;
|
|
317 }
|
|
318
|
|
319 /* Read the message */
|
|
320 node = xmlnode_get_child(status, "message");
|
|
321 if (node != NULL)
|
|
322 data = xmlnode_get_data(node);
|
|
323 if (data != NULL)
|
|
324 ret->message = data;
|
|
325
|
|
326 /* Read substatuses */
|
|
327 for (node = xmlnode_get_child(status, "status"); node != NULL;
|
|
328 node = xmlnode_get_next_twin(node))
|
|
329 {
|
10419
|
330 GaimSavedStatusSub *new;
|
10418
|
331 new = parse_substatus(node);
|
|
332 if (new != NULL)
|
|
333 ret->substatuses = g_list_append(ret->substatuses, new);
|
|
334 }
|
|
335
|
|
336 return ret;
|
|
337 }
|
|
338
|
|
339 /**
|
|
340 * Read the saved statuses from a file in the Gaim user dir.
|
|
341 *
|
|
342 * @return TRUE on success, FALSE on failure (if the file can not
|
|
343 * be opened, or if it contains invalid XML).
|
|
344 */
|
|
345 static gboolean
|
|
346 read_statuses(const char *filename)
|
|
347 {
|
|
348 GError *error;
|
|
349 gchar *contents = NULL;
|
|
350 gsize length;
|
|
351 xmlnode *statuses, *status;
|
|
352
|
|
353 gaim_debug_info("status", "Reading %s\n", filename);
|
|
354
|
|
355 if (!g_file_get_contents(filename, &contents, &length, &error))
|
|
356 {
|
|
357 gaim_debug_error("status", "Error reading statuses: %s\n",
|
|
358 error->message);
|
|
359 g_error_free(error);
|
|
360 return FALSE;
|
|
361 }
|
|
362
|
|
363 statuses = xmlnode_from_str(contents, length);
|
|
364
|
|
365 if (statuses == NULL)
|
|
366 {
|
|
367 FILE *backup;
|
|
368 gchar *name;
|
|
369 gaim_debug_error("status", "Error parsing statuses\n");
|
|
370 name = g_strdup_printf("%s~", filename);
|
|
371 if ((backup = fopen(name, "w")))
|
|
372 {
|
|
373 fwrite(contents, length, 1, backup);
|
|
374 fclose(backup);
|
|
375 chmod(name, S_IRUSR | S_IWUSR);
|
|
376 }
|
|
377 else
|
|
378 {
|
|
379 gaim_debug_error("status", "Unable to write backup %s\n", name);
|
|
380 }
|
|
381 g_free(name);
|
|
382 g_free(contents);
|
|
383 return FALSE;
|
|
384 }
|
|
385
|
|
386 g_free(contents);
|
|
387
|
|
388 for (status = xmlnode_get_child(statuses, "status"); status != NULL;
|
|
389 status = xmlnode_get_next_twin(status))
|
|
390 {
|
10419
|
391 GaimSavedStatus *new;
|
10418
|
392 new = parse_status(status);
|
|
393 saved_statuses = g_list_append(saved_statuses, new);
|
|
394 }
|
|
395
|
|
396 gaim_debug_info("status", "Finished reading statuses\n");
|
|
397
|
|
398 xmlnode_free(statuses);
|
|
399
|
|
400 return TRUE;
|
|
401 }
|
|
402
|
|
403 static void
|
|
404 load_statuses(void)
|
|
405 {
|
|
406 const char *user_dir = gaim_user_dir();
|
|
407 gchar *filename;
|
|
408 gchar *msg;
|
|
409
|
|
410 g_return_if_fail(user_dir != NULL);
|
|
411
|
10423
|
412 statuses_loaded = TRUE;
|
10418
|
413
|
|
414 filename = g_build_filename(user_dir, "status.xml", NULL);
|
|
415
|
|
416 if (g_file_test(filename, G_FILE_TEST_EXISTS))
|
|
417 {
|
|
418 if (!read_statuses(filename))
|
|
419 {
|
|
420 msg = g_strdup_printf(_("An error was encountered parsing the "
|
|
421 "file containing your saved statuses (%s). They "
|
|
422 "have not been loaded, and the old file has been "
|
|
423 "renamed to status.xml~."), filename);
|
|
424 gaim_notify_error(NULL, NULL, _("Saved Statuses Error"), msg);
|
|
425 g_free(msg);
|
|
426 }
|
|
427 }
|
|
428
|
|
429 g_free(filename);
|
|
430 }
|
|
431
|
|
432
|
|
433 /**************************************************************************
|
|
434 * Saved status API
|
|
435 **************************************************************************/
|
10419
|
436 GaimSavedStatus *
|
|
437 gaim_savedstatus_new(const char *title, GaimStatusPrimitive type)
|
10418
|
438 {
|
10419
|
439 GaimSavedStatus *status;
|
10418
|
440
|
10420
|
441 g_return_val_if_fail(gaim_savedstatus_find(title) == NULL, NULL);
|
|
442
|
10419
|
443 status = g_new0(GaimSavedStatus, 1);
|
10418
|
444 status->title = g_strdup(title);
|
|
445 status->type = type;
|
|
446
|
|
447 saved_statuses = g_list_append(saved_statuses, status);
|
|
448
|
|
449 schedule_save();
|
|
450
|
|
451 return status;
|
|
452 }
|
|
453
|
10420
|
454 void
|
|
455 gaim_savedstatus_set_message(GaimSavedStatus *status, const char *message)
|
|
456 {
|
|
457 g_return_if_fail(status != NULL);
|
|
458
|
|
459 g_free(status->message);
|
|
460 status->message = g_strdup(message);
|
|
461
|
|
462 schedule_save();
|
|
463 }
|
|
464
|
10418
|
465 gboolean
|
10419
|
466 gaim_savedstatus_delete(const char *title)
|
10418
|
467 {
|
10419
|
468 GaimSavedStatus *status;
|
10418
|
469
|
10419
|
470 status = gaim_savedstatus_find(title);
|
10418
|
471
|
|
472 if (status == NULL)
|
|
473 return FALSE;
|
|
474
|
|
475 saved_statuses = g_list_remove(saved_statuses, status);
|
|
476 free_statussaved(status);
|
|
477
|
|
478 schedule_save();
|
|
479
|
|
480 return TRUE;
|
|
481 }
|
|
482
|
|
483 const GList *
|
|
484 gaim_savedstatuses_get_all(void)
|
|
485 {
|
|
486 return saved_statuses;
|
|
487 }
|
|
488
|
10419
|
489 GaimSavedStatus *
|
|
490 gaim_savedstatus_find(const char *title)
|
10418
|
491 {
|
|
492 GList *l;
|
10419
|
493 GaimSavedStatus *status;
|
10418
|
494
|
|
495 for (l = saved_statuses; l != NULL; l = g_list_next(l))
|
|
496 {
|
10419
|
497 status = (GaimSavedStatus *)l->data;
|
10418
|
498 if (!strcmp(status->title, title))
|
|
499 return status;
|
|
500 }
|
|
501
|
|
502 return NULL;
|
|
503 }
|
|
504
|
|
505 const char *
|
10419
|
506 gaim_savedstatus_get_title(const GaimSavedStatus *saved_status)
|
10418
|
507 {
|
|
508 return saved_status->title;
|
|
509 }
|
|
510
|
|
511 GaimStatusPrimitive
|
10419
|
512 gaim_savedstatus_get_type(const GaimSavedStatus *saved_status)
|
10418
|
513 {
|
|
514 return saved_status->type;
|
|
515 }
|
|
516
|
|
517 const char *
|
10419
|
518 gaim_savedstatus_get_message(const GaimSavedStatus *saved_status)
|
10418
|
519 {
|
|
520 return saved_status->message;
|
|
521 }
|
|
522
|
|
523 void
|
|
524 gaim_savedstatuses_init(void)
|
|
525 {
|
|
526 load_statuses();
|
|
527 }
|
|
528
|
|
529 void
|
|
530 gaim_savedstatuses_uninit(void)
|
|
531 {
|
|
532 if (statuses_save_timer != 0)
|
|
533 {
|
|
534 gaim_timeout_remove(statuses_save_timer);
|
|
535 statuses_save_timer = 0;
|
|
536 sync_statuses();
|
|
537 }
|
|
538
|
|
539 while (saved_statuses != NULL) {
|
10419
|
540 GaimSavedStatus *status = saved_statuses->data;
|
10418
|
541 saved_statuses = g_list_remove(saved_statuses, status);
|
|
542 free_statussaved(status);
|
|
543 }
|
|
544 }
|