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;
|
10428
|
68 static guint save_timer = 0;
|
10423
|
69 static gboolean statuses_loaded = FALSE;
|
10418
|
70
|
10427
|
71
|
10428
|
72 /*********************************************************************
|
|
73 * Private utility functions *
|
|
74 *********************************************************************/
|
10418
|
75
|
|
76 static void
|
10419
|
77 free_statussavedsub(GaimSavedStatusSub *substatus)
|
10418
|
78 {
|
|
79 g_return_if_fail(substatus != NULL);
|
|
80
|
|
81 g_free(substatus->message);
|
|
82 g_free(substatus);
|
|
83 }
|
|
84
|
|
85 static void
|
10419
|
86 free_statussaved(GaimSavedStatus *status)
|
10418
|
87 {
|
|
88 g_return_if_fail(status != NULL);
|
|
89
|
|
90 g_free(status->title);
|
|
91 g_free(status->message);
|
|
92
|
|
93 while (status->substatuses != NULL)
|
|
94 {
|
10419
|
95 GaimSavedStatusSub *substatus = status->substatuses->data;
|
10418
|
96 status->substatuses = g_list_remove(status->substatuses, substatus);
|
|
97 free_statussavedsub(substatus);
|
|
98 }
|
|
99
|
|
100 g_free(status);
|
|
101 }
|
|
102
|
10428
|
103 /*********************************************************************
|
10429
|
104 * Writing to disk *
|
10428
|
105 *********************************************************************/
|
10418
|
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
|
10428
|
178 if (!statuses_loaded)
|
|
179 {
|
10418
|
180 gaim_debug_error("status", "Attempted to save statuses before they "
|
|
181 "were read!\n");
|
|
182 return;
|
|
183 }
|
|
184
|
10423
|
185 node = statuses_to_xmlnode();
|
|
186 data = xmlnode_to_formatted_str(node, NULL);
|
10418
|
187 gaim_util_write_data_to_file("status.xml", data, -1);
|
|
188 g_free(data);
|
10423
|
189 xmlnode_free(node);
|
10418
|
190 }
|
|
191
|
|
192 static gboolean
|
10428
|
193 save_cb(gpointer data)
|
10418
|
194 {
|
|
195 sync_statuses();
|
10428
|
196 save_timer = 0;
|
10418
|
197 return FALSE;
|
|
198 }
|
|
199
|
|
200 static void
|
|
201 schedule_save(void)
|
|
202 {
|
10428
|
203 if (save_timer == 0)
|
|
204 save_timer = gaim_timeout_add(5000, save_cb, NULL);
|
10418
|
205 }
|
|
206
|
|
207
|
10428
|
208 /*********************************************************************
|
|
209 * Reading from disk *
|
|
210 *********************************************************************/
|
|
211
|
10419
|
212 static GaimSavedStatusSub *
|
10418
|
213 parse_substatus(xmlnode *substatus)
|
|
214 {
|
10419
|
215 GaimSavedStatusSub *ret;
|
10418
|
216 xmlnode *node;
|
10425
|
217 char *data;
|
10418
|
218
|
10419
|
219 ret = g_new0(GaimSavedStatusSub, 1);
|
10418
|
220
|
|
221 /* Read the account */
|
|
222 node = xmlnode_get_child(substatus, "account");
|
|
223 if (node != NULL)
|
|
224 {
|
|
225 char *acct_name;
|
|
226 const char *protocol;
|
|
227 acct_name = xmlnode_get_data(node);
|
|
228 protocol = xmlnode_get_attrib(node, "protocol");
|
|
229 if ((acct_name != NULL) && (protocol != NULL))
|
|
230 ret->account = gaim_accounts_find(acct_name, protocol);
|
|
231 g_free(acct_name);
|
|
232 }
|
|
233
|
|
234 if (ret->account == NULL)
|
|
235 {
|
|
236 g_free(ret);
|
|
237 return NULL;
|
|
238 }
|
|
239
|
|
240 /* Read the state */
|
|
241 node = xmlnode_get_child(substatus, "state");
|
10426
|
242 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL))
|
10425
|
243 {
|
10418
|
244 ret->type = gaim_status_type_find_with_id(
|
10425
|
245 ret->account->status_types, data);
|
10418
|
246 g_free(data);
|
|
247 }
|
|
248
|
|
249 /* Read the message */
|
|
250 node = xmlnode_get_child(substatus, "message");
|
10426
|
251 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL))
|
10425
|
252 {
|
10418
|
253 ret->message = data;
|
10425
|
254 }
|
10418
|
255
|
|
256 return ret;
|
|
257 }
|
|
258
|
|
259 /**
|
|
260 * Parse a saved status and add it to the saved_statuses linked list.
|
|
261 *
|
|
262 * Here's an example of the XML for a saved status:
|
|
263 * <status name="Girls">
|
|
264 * <state>away</state>
|
|
265 * <message>I like the way that they walk
|
|
266 * And it's chill to hear them talk
|
|
267 * And I can always make them smile
|
|
268 * From White Castle to the Nile</message>
|
|
269 * <substatus>
|
|
270 * <account protocol='prpl-oscar'>markdoliner</account>
|
|
271 * <state>available</state>
|
|
272 * <message>The ladies man is here to answer your queries.</message>
|
|
273 * </substatus>
|
|
274 * <substatus>
|
|
275 * <account protocol='prpl-oscar'>giantgraypanda</account>
|
|
276 * <state>away</state>
|
|
277 * <message>A.C. ain't in charge no more.</message>
|
|
278 * </substatus>
|
|
279 * </status>
|
|
280 *
|
|
281 * I know. Moving, huh?
|
|
282 */
|
10419
|
283 static GaimSavedStatus *
|
10418
|
284 parse_status(xmlnode *status)
|
|
285 {
|
10419
|
286 GaimSavedStatus *ret;
|
10418
|
287 xmlnode *node;
|
|
288 const char *attrib;
|
10425
|
289 char *data;
|
10418
|
290 int i;
|
|
291
|
10419
|
292 ret = g_new0(GaimSavedStatus, 1);
|
10418
|
293
|
|
294 /* Read the title */
|
|
295 attrib = xmlnode_get_attrib(status, "name");
|
|
296 if (attrib == NULL)
|
|
297 attrib = "No Title";
|
|
298 /* Ensure the title is unique */
|
|
299 ret->title = g_strdup(attrib);
|
|
300 i = 2;
|
10419
|
301 while (gaim_savedstatus_find(ret->title) != NULL)
|
10418
|
302 {
|
|
303 g_free(ret->title);
|
|
304 ret->title = g_strdup_printf("%s %d", attrib, i);
|
|
305 i++;
|
|
306 }
|
|
307
|
|
308 /* Read the primitive status type */
|
|
309 node = xmlnode_get_child(status, "state");
|
10426
|
310 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL))
|
10425
|
311 {
|
10419
|
312 ret->type = gaim_primitive_get_type_from_id(data);
|
10418
|
313 g_free(data);
|
|
314 }
|
|
315
|
|
316 /* Read the message */
|
|
317 node = xmlnode_get_child(status, "message");
|
10426
|
318 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL))
|
10425
|
319 {
|
10418
|
320 ret->message = data;
|
10425
|
321 }
|
10418
|
322
|
|
323 /* Read substatuses */
|
|
324 for (node = xmlnode_get_child(status, "status"); node != NULL;
|
|
325 node = xmlnode_get_next_twin(node))
|
|
326 {
|
10419
|
327 GaimSavedStatusSub *new;
|
10418
|
328 new = parse_substatus(node);
|
|
329 if (new != NULL)
|
|
330 ret->substatuses = g_list_append(ret->substatuses, new);
|
|
331 }
|
|
332
|
|
333 return ret;
|
|
334 }
|
|
335
|
|
336 /**
|
|
337 * Read the saved statuses from a file in the Gaim user dir.
|
|
338 *
|
|
339 * @return TRUE on success, FALSE on failure (if the file can not
|
|
340 * be opened, or if it contains invalid XML).
|
|
341 */
|
10425
|
342 static void
|
|
343 load_statuses(void)
|
10418
|
344 {
|
|
345 xmlnode *statuses, *status;
|
|
346
|
10426
|
347 statuses_loaded = TRUE;
|
|
348
|
10425
|
349 statuses = gaim_util_read_xml_from_file("status.xml", _("saved statuses"));
|
10418
|
350
|
|
351 if (statuses == NULL)
|
10425
|
352 return;
|
10418
|
353
|
|
354 for (status = xmlnode_get_child(statuses, "status"); status != NULL;
|
|
355 status = xmlnode_get_next_twin(status))
|
|
356 {
|
10419
|
357 GaimSavedStatus *new;
|
10418
|
358 new = parse_status(status);
|
|
359 saved_statuses = g_list_append(saved_statuses, new);
|
|
360 }
|
|
361
|
|
362 xmlnode_free(statuses);
|
|
363 }
|
|
364
|
|
365
|
|
366 /**************************************************************************
|
|
367 * Saved status API
|
|
368 **************************************************************************/
|
10419
|
369 GaimSavedStatus *
|
|
370 gaim_savedstatus_new(const char *title, GaimStatusPrimitive type)
|
10418
|
371 {
|
10419
|
372 GaimSavedStatus *status;
|
10418
|
373
|
10420
|
374 g_return_val_if_fail(gaim_savedstatus_find(title) == NULL, NULL);
|
|
375
|
10419
|
376 status = g_new0(GaimSavedStatus, 1);
|
10418
|
377 status->title = g_strdup(title);
|
|
378 status->type = type;
|
|
379
|
|
380 saved_statuses = g_list_append(saved_statuses, status);
|
|
381
|
|
382 schedule_save();
|
|
383
|
|
384 return status;
|
|
385 }
|
|
386
|
10420
|
387 void
|
|
388 gaim_savedstatus_set_message(GaimSavedStatus *status, const char *message)
|
|
389 {
|
|
390 g_return_if_fail(status != NULL);
|
|
391
|
|
392 g_free(status->message);
|
|
393 status->message = g_strdup(message);
|
|
394
|
|
395 schedule_save();
|
|
396 }
|
|
397
|
10418
|
398 gboolean
|
10419
|
399 gaim_savedstatus_delete(const char *title)
|
10418
|
400 {
|
10419
|
401 GaimSavedStatus *status;
|
10418
|
402
|
10419
|
403 status = gaim_savedstatus_find(title);
|
10418
|
404
|
|
405 if (status == NULL)
|
|
406 return FALSE;
|
|
407
|
|
408 saved_statuses = g_list_remove(saved_statuses, status);
|
|
409 free_statussaved(status);
|
|
410
|
|
411 schedule_save();
|
|
412
|
|
413 return TRUE;
|
|
414 }
|
|
415
|
|
416 const GList *
|
|
417 gaim_savedstatuses_get_all(void)
|
|
418 {
|
|
419 return saved_statuses;
|
|
420 }
|
|
421
|
10419
|
422 GaimSavedStatus *
|
|
423 gaim_savedstatus_find(const char *title)
|
10418
|
424 {
|
|
425 GList *l;
|
10419
|
426 GaimSavedStatus *status;
|
10418
|
427
|
|
428 for (l = saved_statuses; l != NULL; l = g_list_next(l))
|
|
429 {
|
10419
|
430 status = (GaimSavedStatus *)l->data;
|
10418
|
431 if (!strcmp(status->title, title))
|
|
432 return status;
|
|
433 }
|
|
434
|
|
435 return NULL;
|
|
436 }
|
|
437
|
|
438 const char *
|
10419
|
439 gaim_savedstatus_get_title(const GaimSavedStatus *saved_status)
|
10418
|
440 {
|
|
441 return saved_status->title;
|
|
442 }
|
|
443
|
|
444 GaimStatusPrimitive
|
10419
|
445 gaim_savedstatus_get_type(const GaimSavedStatus *saved_status)
|
10418
|
446 {
|
|
447 return saved_status->type;
|
|
448 }
|
|
449
|
|
450 const char *
|
10419
|
451 gaim_savedstatus_get_message(const GaimSavedStatus *saved_status)
|
10418
|
452 {
|
|
453 return saved_status->message;
|
|
454 }
|
|
455
|
|
456 void
|
|
457 gaim_savedstatuses_init(void)
|
|
458 {
|
|
459 load_statuses();
|
|
460 }
|
|
461
|
|
462 void
|
|
463 gaim_savedstatuses_uninit(void)
|
|
464 {
|
10428
|
465 if (save_timer != 0)
|
10418
|
466 {
|
10428
|
467 gaim_timeout_remove(save_timer);
|
|
468 save_timer = 0;
|
10418
|
469 sync_statuses();
|
|
470 }
|
|
471
|
|
472 while (saved_statuses != NULL) {
|
10419
|
473 GaimSavedStatus *status = saved_statuses->data;
|
10418
|
474 saved_statuses = g_list_remove(saved_statuses, status);
|
|
475 free_statussaved(status);
|
|
476 }
|
|
477 }
|