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
|
10427
|
71
|
10418
|
72 /**************************************************************************
|
|
73 * Helper functions
|
|
74 **************************************************************************/
|
|
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
|
|
103
|
|
104 /**************************************************************************
|
10425
|
105 * Writting to disk
|
10418
|
106 **************************************************************************/
|
|
107
|
|
108 static xmlnode *
|
10419
|
109 substatus_to_xmlnode(GaimSavedStatusSub *substatus)
|
10418
|
110 {
|
|
111 xmlnode *node, *child;
|
|
112
|
|
113 node = xmlnode_new("substatus");
|
|
114
|
10424
|
115 child = xmlnode_new_child(node, "account");
|
|
116 xmlnode_set_attrib(child, "protocol", gaim_account_get_protocol_id(substatus->account));
|
|
117 xmlnode_insert_data(child, gaim_account_get_username(substatus->account), -1);
|
10418
|
118
|
10424
|
119 child = xmlnode_new_child(node, "state");
|
10418
|
120 xmlnode_insert_data(child, gaim_status_type_get_id(substatus->type), -1);
|
|
121
|
|
122 if (substatus->message != NULL)
|
|
123 {
|
10424
|
124 child = xmlnode_new_child(node, "message");
|
10418
|
125 xmlnode_insert_data(child, substatus->message, -1);
|
|
126 }
|
|
127
|
|
128 return node;
|
|
129 }
|
|
130
|
|
131 static xmlnode *
|
10419
|
132 status_to_xmlnode(GaimSavedStatus *status)
|
10418
|
133 {
|
|
134 xmlnode *node, *child;
|
|
135 GList *cur;
|
|
136
|
|
137 node = xmlnode_new("status");
|
|
138 xmlnode_set_attrib(node, "name", status->title);
|
|
139
|
10424
|
140 child = xmlnode_new_child(node, "state");
|
|
141 xmlnode_insert_data(child, gaim_primitive_get_id_from_type(status->type), -1);
|
10418
|
142
|
10424
|
143 child = xmlnode_new_child(node, "message");
|
10418
|
144 xmlnode_insert_data(child, status->message, -1);
|
|
145
|
|
146 for (cur = status->substatuses; cur != NULL; cur = cur->next)
|
|
147 {
|
|
148 child = substatus_to_xmlnode(cur->data);
|
|
149 xmlnode_insert_child(node, child);
|
|
150 }
|
|
151
|
|
152 return node;
|
|
153 }
|
|
154
|
|
155 static xmlnode *
|
|
156 statuses_to_xmlnode(void)
|
|
157 {
|
|
158 xmlnode *node, *child;
|
|
159 GList *cur;
|
|
160
|
|
161 node = xmlnode_new("statuses");
|
10423
|
162 xmlnode_set_attrib(node, "version", "1.0");
|
10418
|
163
|
|
164 for (cur = saved_statuses; cur != NULL; cur = cur->next)
|
|
165 {
|
|
166 child = status_to_xmlnode(cur->data);
|
|
167 xmlnode_insert_child(node, child);
|
|
168 }
|
|
169
|
|
170 return node;
|
|
171 }
|
|
172
|
|
173 static void
|
|
174 sync_statuses(void)
|
|
175 {
|
10423
|
176 xmlnode *node;
|
10418
|
177 char *data;
|
|
178
|
10423
|
179 if (!statuses_loaded) {
|
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
|
|
193 save_callback(gpointer data)
|
|
194 {
|
|
195 sync_statuses();
|
|
196 statuses_save_timer = 0;
|
|
197 return FALSE;
|
|
198 }
|
|
199
|
|
200 static void
|
|
201 schedule_save(void)
|
|
202 {
|
10427
|
203 if (statuses_save_timer == 0)
|
|
204 statuses_save_timer = gaim_timeout_add(5000, save_callback, NULL);
|
10418
|
205 }
|
|
206
|
|
207
|
|
208 /**************************************************************************
|
10425
|
209 * Reading from disk
|
10418
|
210 **************************************************************************/
|
10419
|
211 static GaimSavedStatusSub *
|
10418
|
212 parse_substatus(xmlnode *substatus)
|
|
213 {
|
10419
|
214 GaimSavedStatusSub *ret;
|
10418
|
215 xmlnode *node;
|
10425
|
216 char *data;
|
10418
|
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");
|
10426
|
241 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL))
|
10425
|
242 {
|
10418
|
243 ret->type = gaim_status_type_find_with_id(
|
10425
|
244 ret->account->status_types, data);
|
10418
|
245 g_free(data);
|
|
246 }
|
|
247
|
|
248 /* Read the message */
|
|
249 node = xmlnode_get_child(substatus, "message");
|
10426
|
250 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL))
|
10425
|
251 {
|
10418
|
252 ret->message = data;
|
10425
|
253 }
|
10418
|
254
|
|
255 return ret;
|
|
256 }
|
|
257
|
|
258 /**
|
|
259 * Parse a saved status and add it to the saved_statuses linked list.
|
|
260 *
|
|
261 * Here's an example of the XML for a saved status:
|
|
262 * <status name="Girls">
|
|
263 * <state>away</state>
|
|
264 * <message>I like the way that they walk
|
|
265 * And it's chill to hear them talk
|
|
266 * And I can always make them smile
|
|
267 * From White Castle to the Nile</message>
|
|
268 * <substatus>
|
|
269 * <account protocol='prpl-oscar'>markdoliner</account>
|
|
270 * <state>available</state>
|
|
271 * <message>The ladies man is here to answer your queries.</message>
|
|
272 * </substatus>
|
|
273 * <substatus>
|
|
274 * <account protocol='prpl-oscar'>giantgraypanda</account>
|
|
275 * <state>away</state>
|
|
276 * <message>A.C. ain't in charge no more.</message>
|
|
277 * </substatus>
|
|
278 * </status>
|
|
279 *
|
|
280 * I know. Moving, huh?
|
|
281 */
|
10419
|
282 static GaimSavedStatus *
|
10418
|
283 parse_status(xmlnode *status)
|
|
284 {
|
10419
|
285 GaimSavedStatus *ret;
|
10418
|
286 xmlnode *node;
|
|
287 const char *attrib;
|
10425
|
288 char *data;
|
10418
|
289 int i;
|
|
290
|
10419
|
291 ret = g_new0(GaimSavedStatus, 1);
|
10418
|
292
|
|
293 /* Read the title */
|
|
294 attrib = xmlnode_get_attrib(status, "name");
|
|
295 if (attrib == NULL)
|
|
296 attrib = "No Title";
|
|
297 /* Ensure the title is unique */
|
|
298 ret->title = g_strdup(attrib);
|
|
299 i = 2;
|
10419
|
300 while (gaim_savedstatus_find(ret->title) != NULL)
|
10418
|
301 {
|
|
302 g_free(ret->title);
|
|
303 ret->title = g_strdup_printf("%s %d", attrib, i);
|
|
304 i++;
|
|
305 }
|
|
306
|
|
307 /* Read the primitive status type */
|
|
308 node = xmlnode_get_child(status, "state");
|
10426
|
309 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL))
|
10425
|
310 {
|
10419
|
311 ret->type = gaim_primitive_get_type_from_id(data);
|
10418
|
312 g_free(data);
|
|
313 }
|
|
314
|
|
315 /* Read the message */
|
|
316 node = xmlnode_get_child(status, "message");
|
10426
|
317 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL))
|
10425
|
318 {
|
10418
|
319 ret->message = data;
|
10425
|
320 }
|
10418
|
321
|
|
322 /* Read substatuses */
|
|
323 for (node = xmlnode_get_child(status, "status"); node != NULL;
|
|
324 node = xmlnode_get_next_twin(node))
|
|
325 {
|
10419
|
326 GaimSavedStatusSub *new;
|
10418
|
327 new = parse_substatus(node);
|
|
328 if (new != NULL)
|
|
329 ret->substatuses = g_list_append(ret->substatuses, new);
|
|
330 }
|
|
331
|
|
332 return ret;
|
|
333 }
|
|
334
|
|
335 /**
|
|
336 * Read the saved statuses from a file in the Gaim user dir.
|
|
337 *
|
|
338 * @return TRUE on success, FALSE on failure (if the file can not
|
|
339 * be opened, or if it contains invalid XML).
|
|
340 */
|
10425
|
341 static void
|
|
342 load_statuses(void)
|
10418
|
343 {
|
|
344 xmlnode *statuses, *status;
|
|
345
|
10426
|
346 statuses_loaded = TRUE;
|
|
347
|
10425
|
348 statuses = gaim_util_read_xml_from_file("status.xml", _("saved statuses"));
|
10418
|
349
|
|
350 if (statuses == NULL)
|
10425
|
351 return;
|
10418
|
352
|
|
353 for (status = xmlnode_get_child(statuses, "status"); status != NULL;
|
|
354 status = xmlnode_get_next_twin(status))
|
|
355 {
|
10419
|
356 GaimSavedStatus *new;
|
10418
|
357 new = parse_status(status);
|
|
358 saved_statuses = g_list_append(saved_statuses, new);
|
|
359 }
|
|
360
|
|
361 xmlnode_free(statuses);
|
|
362 }
|
|
363
|
|
364
|
|
365 /**************************************************************************
|
|
366 * Saved status API
|
|
367 **************************************************************************/
|
10419
|
368 GaimSavedStatus *
|
|
369 gaim_savedstatus_new(const char *title, GaimStatusPrimitive type)
|
10418
|
370 {
|
10419
|
371 GaimSavedStatus *status;
|
10418
|
372
|
10420
|
373 g_return_val_if_fail(gaim_savedstatus_find(title) == NULL, NULL);
|
|
374
|
10419
|
375 status = g_new0(GaimSavedStatus, 1);
|
10418
|
376 status->title = g_strdup(title);
|
|
377 status->type = type;
|
|
378
|
|
379 saved_statuses = g_list_append(saved_statuses, status);
|
|
380
|
|
381 schedule_save();
|
|
382
|
|
383 return status;
|
|
384 }
|
|
385
|
10420
|
386 void
|
|
387 gaim_savedstatus_set_message(GaimSavedStatus *status, const char *message)
|
|
388 {
|
|
389 g_return_if_fail(status != NULL);
|
|
390
|
|
391 g_free(status->message);
|
|
392 status->message = g_strdup(message);
|
|
393
|
|
394 schedule_save();
|
|
395 }
|
|
396
|
10418
|
397 gboolean
|
10419
|
398 gaim_savedstatus_delete(const char *title)
|
10418
|
399 {
|
10419
|
400 GaimSavedStatus *status;
|
10418
|
401
|
10419
|
402 status = gaim_savedstatus_find(title);
|
10418
|
403
|
|
404 if (status == NULL)
|
|
405 return FALSE;
|
|
406
|
|
407 saved_statuses = g_list_remove(saved_statuses, status);
|
|
408 free_statussaved(status);
|
|
409
|
|
410 schedule_save();
|
|
411
|
|
412 return TRUE;
|
|
413 }
|
|
414
|
|
415 const GList *
|
|
416 gaim_savedstatuses_get_all(void)
|
|
417 {
|
|
418 return saved_statuses;
|
|
419 }
|
|
420
|
10419
|
421 GaimSavedStatus *
|
|
422 gaim_savedstatus_find(const char *title)
|
10418
|
423 {
|
|
424 GList *l;
|
10419
|
425 GaimSavedStatus *status;
|
10418
|
426
|
|
427 for (l = saved_statuses; l != NULL; l = g_list_next(l))
|
|
428 {
|
10419
|
429 status = (GaimSavedStatus *)l->data;
|
10418
|
430 if (!strcmp(status->title, title))
|
|
431 return status;
|
|
432 }
|
|
433
|
|
434 return NULL;
|
|
435 }
|
|
436
|
|
437 const char *
|
10419
|
438 gaim_savedstatus_get_title(const GaimSavedStatus *saved_status)
|
10418
|
439 {
|
|
440 return saved_status->title;
|
|
441 }
|
|
442
|
|
443 GaimStatusPrimitive
|
10419
|
444 gaim_savedstatus_get_type(const GaimSavedStatus *saved_status)
|
10418
|
445 {
|
|
446 return saved_status->type;
|
|
447 }
|
|
448
|
|
449 const char *
|
10419
|
450 gaim_savedstatus_get_message(const GaimSavedStatus *saved_status)
|
10418
|
451 {
|
|
452 return saved_status->message;
|
|
453 }
|
|
454
|
|
455 void
|
|
456 gaim_savedstatuses_init(void)
|
|
457 {
|
|
458 load_statuses();
|
|
459 }
|
|
460
|
|
461 void
|
|
462 gaim_savedstatuses_uninit(void)
|
|
463 {
|
|
464 if (statuses_save_timer != 0)
|
|
465 {
|
|
466 gaim_timeout_remove(statuses_save_timer);
|
|
467 statuses_save_timer = 0;
|
|
468 sync_statuses();
|
|
469 }
|
|
470
|
|
471 while (saved_statuses != NULL) {
|
10419
|
472 GaimSavedStatus *status = saved_statuses->data;
|
10418
|
473 saved_statuses = g_list_remove(saved_statuses, status);
|
|
474 free_statussaved(status);
|
|
475 }
|
|
476 }
|