| |
1 /** |
| |
2 * @file savedstatuses.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 "idle.h" |
| |
29 #include "notify.h" |
| |
30 #include "savedstatuses.h" |
| |
31 #include "dbus-maybe.h" |
| |
32 #include "status.h" |
| |
33 #include "util.h" |
| |
34 #include "xmlnode.h" |
| |
35 |
| |
36 /** |
| |
37 * The maximum number of transient statuses to save. This |
| |
38 * is used during the shutdown process to clean out old |
| |
39 * transient statuses. |
| |
40 */ |
| |
41 #define MAX_TRANSIENTS 5 |
| |
42 |
| |
43 /** |
| |
44 * The default message to use when the user becomes auto-away. |
| |
45 */ |
| |
46 #define DEFAULT_AUTOAWAY_MESSAGE _("I'm not here right now") |
| |
47 |
| |
48 /** |
| |
49 * The information stores a snap-shot of the statuses of all |
| |
50 * your accounts. Basically these are your saved away messages. |
| |
51 * There is an overall status and message that applies to |
| |
52 * all your accounts, and then each individual account can |
| |
53 * optionally have a different custom status and message. |
| |
54 * |
| |
55 * The changes to status.xml caused by the new status API |
| |
56 * are fully backward compatible. The new status API just |
| |
57 * adds the optional sub-statuses to the XML file. |
| |
58 */ |
| |
59 struct _GaimSavedStatus |
| |
60 { |
| |
61 char *title; |
| |
62 GaimStatusPrimitive type; |
| |
63 char *message; |
| |
64 |
| |
65 /** The timestamp when this saved status was created. This must be unique. */ |
| |
66 time_t creation_time; |
| |
67 |
| |
68 time_t lastused; |
| |
69 |
| |
70 unsigned int usage_count; |
| |
71 |
| |
72 GList *substatuses; /**< A list of GaimSavedStatusSub's. */ |
| |
73 }; |
| |
74 |
| |
75 /* |
| |
76 * TODO: If a GaimStatusType is deleted, need to also delete any |
| |
77 * associated GaimSavedStatusSub's? |
| |
78 */ |
| |
79 struct _GaimSavedStatusSub |
| |
80 { |
| |
81 GaimAccount *account; |
| |
82 const GaimStatusType *type; |
| |
83 char *message; |
| |
84 }; |
| |
85 |
| |
86 static GList *saved_statuses = NULL; |
| |
87 static guint save_timer = 0; |
| |
88 static gboolean statuses_loaded = FALSE; |
| |
89 |
| |
90 /* |
| |
91 * This hash table keeps track of which timestamps we've |
| |
92 * used so that we don't have two saved statuses with the |
| |
93 * same 'creation_time' timestamp. The 'created' timestamp |
| |
94 * is used as a unique identifier. |
| |
95 * |
| |
96 * So the key in this hash table is the creation_time and |
| |
97 * the value is a pointer to the GaimSavedStatus. |
| |
98 */ |
| |
99 static GHashTable *creation_times; |
| |
100 |
| |
101 static void schedule_save(void); |
| |
102 |
| |
103 /********************************************************************* |
| |
104 * Private utility functions * |
| |
105 *********************************************************************/ |
| |
106 |
| |
107 static void |
| |
108 free_saved_status_sub(GaimSavedStatusSub *substatus) |
| |
109 { |
| |
110 g_return_if_fail(substatus != NULL); |
| |
111 |
| |
112 g_free(substatus->message); |
| |
113 GAIM_DBUS_UNREGISTER_POINTER(substatus); |
| |
114 g_free(substatus); |
| |
115 } |
| |
116 |
| |
117 static void |
| |
118 free_saved_status(GaimSavedStatus *status) |
| |
119 { |
| |
120 g_return_if_fail(status != NULL); |
| |
121 |
| |
122 g_free(status->title); |
| |
123 g_free(status->message); |
| |
124 |
| |
125 while (status->substatuses != NULL) |
| |
126 { |
| |
127 GaimSavedStatusSub *substatus = status->substatuses->data; |
| |
128 status->substatuses = g_list_remove(status->substatuses, substatus); |
| |
129 free_saved_status_sub(substatus); |
| |
130 } |
| |
131 |
| |
132 GAIM_DBUS_UNREGISTER_POINTER(status); |
| |
133 g_free(status); |
| |
134 } |
| |
135 |
| |
136 /* |
| |
137 * Set the timestamp for when this saved status was created, and |
| |
138 * make sure it is unique. |
| |
139 */ |
| |
140 static void |
| |
141 set_creation_time(GaimSavedStatus *status, time_t creation_time) |
| |
142 { |
| |
143 g_return_if_fail(status != NULL); |
| |
144 |
| |
145 /* Avoid using 0 because it's an invalid hash key */ |
| |
146 status->creation_time = creation_time != 0 ? creation_time : 1; |
| |
147 |
| |
148 while (g_hash_table_lookup(creation_times, &status->creation_time) != NULL) |
| |
149 status->creation_time++; |
| |
150 |
| |
151 g_hash_table_insert(creation_times, |
| |
152 &status->creation_time, |
| |
153 status); |
| |
154 } |
| |
155 |
| |
156 /** |
| |
157 * A magic number is calcuated for each status, and then the |
| |
158 * statuses are ordered by the magic number. The magic number |
| |
159 * is the date the status was last used offset by one day for |
| |
160 * each time the status has been used (but only by 10 days at |
| |
161 * the most). |
| |
162 * |
| |
163 * The goal is to have recently used statuses at the top of |
| |
164 * the list, but to also keep frequently used statuses near |
| |
165 * the top. |
| |
166 */ |
| |
167 static gint |
| |
168 saved_statuses_sort_func(gconstpointer a, gconstpointer b) |
| |
169 { |
| |
170 const GaimSavedStatus *saved_status_a = a; |
| |
171 const GaimSavedStatus *saved_status_b = b; |
| |
172 time_t time_a = saved_status_a->lastused + |
| |
173 (MIN(saved_status_a->usage_count, 10) * 86400); |
| |
174 time_t time_b = saved_status_b->lastused + |
| |
175 (MIN(saved_status_b->usage_count, 10) * 86400); |
| |
176 if (time_a > time_b) |
| |
177 return -1; |
| |
178 if (time_a < time_b) |
| |
179 return 1; |
| |
180 return 0; |
| |
181 } |
| |
182 |
| |
183 /** |
| |
184 * Transient statuses are added and removed automatically by |
| |
185 * Gaim. If they're not used for a certain length of time then |
| |
186 * they'll expire and be automatically removed. This function |
| |
187 * does the expiration. |
| |
188 */ |
| |
189 static void |
| |
190 remove_old_transient_statuses() |
| |
191 { |
| |
192 GList *l, *next; |
| |
193 GaimSavedStatus *saved_status, *current_status; |
| |
194 int count; |
| |
195 time_t creation_time; |
| |
196 |
| |
197 current_status = gaim_savedstatus_get_current(); |
| |
198 |
| |
199 /* |
| |
200 * Iterate through the list of saved statuses. Delete all |
| |
201 * transient statuses except for the first MAX_TRANSIENTS |
| |
202 * (remember, the saved statuses are already sorted by popularity). |
| |
203 */ |
| |
204 count = 0; |
| |
205 for (l = saved_statuses; l != NULL; l = next) |
| |
206 { |
| |
207 next = l->next; |
| |
208 saved_status = l->data; |
| |
209 if (gaim_savedstatus_is_transient(saved_status)) |
| |
210 { |
| |
211 if (count == MAX_TRANSIENTS) |
| |
212 { |
| |
213 if (saved_status != current_status) |
| |
214 { |
| |
215 saved_statuses = g_list_remove(saved_statuses, saved_status); |
| |
216 creation_time = gaim_savedstatus_get_creation_time(saved_status); |
| |
217 g_hash_table_remove(creation_times, &creation_time); |
| |
218 free_saved_status(saved_status); |
| |
219 } |
| |
220 } |
| |
221 else |
| |
222 count++; |
| |
223 } |
| |
224 } |
| |
225 |
| |
226 if (count == MAX_TRANSIENTS) |
| |
227 schedule_save(); |
| |
228 } |
| |
229 |
| |
230 /********************************************************************* |
| |
231 * Writing to disk * |
| |
232 *********************************************************************/ |
| |
233 |
| |
234 static xmlnode * |
| |
235 substatus_to_xmlnode(GaimSavedStatusSub *substatus) |
| |
236 { |
| |
237 xmlnode *node, *child; |
| |
238 |
| |
239 node = xmlnode_new("substatus"); |
| |
240 |
| |
241 child = xmlnode_new_child(node, "account"); |
| |
242 xmlnode_set_attrib(child, "protocol", gaim_account_get_protocol_id(substatus->account)); |
| |
243 xmlnode_insert_data(child, gaim_account_get_username(substatus->account), -1); |
| |
244 |
| |
245 child = xmlnode_new_child(node, "state"); |
| |
246 xmlnode_insert_data(child, gaim_status_type_get_id(substatus->type), -1); |
| |
247 |
| |
248 if (substatus->message != NULL) |
| |
249 { |
| |
250 child = xmlnode_new_child(node, "message"); |
| |
251 xmlnode_insert_data(child, substatus->message, -1); |
| |
252 } |
| |
253 |
| |
254 return node; |
| |
255 } |
| |
256 |
| |
257 static xmlnode * |
| |
258 status_to_xmlnode(GaimSavedStatus *status) |
| |
259 { |
| |
260 xmlnode *node, *child; |
| |
261 char buf[21]; |
| |
262 GList *cur; |
| |
263 |
| |
264 node = xmlnode_new("status"); |
| |
265 if (status->title != NULL) |
| |
266 { |
| |
267 xmlnode_set_attrib(node, "name", status->title); |
| |
268 } |
| |
269 else |
| |
270 { |
| |
271 /* |
| |
272 * Gaim 1.5.0 and earlier require a name to be set, so we |
| |
273 * do this little hack to maintain backward compatability |
| |
274 * in the status.xml file. Eventually this should be removed |
| |
275 * and we should determine if a status is transient by |
| |
276 * whether the "name" attribute is set to something or if |
| |
277 * it does not exist at all. |
| |
278 */ |
| |
279 xmlnode_set_attrib(node, "name", "Auto-Cached"); |
| |
280 xmlnode_set_attrib(node, "transient", "true"); |
| |
281 } |
| |
282 |
| |
283 snprintf(buf, sizeof(buf), "%lu", status->creation_time); |
| |
284 xmlnode_set_attrib(node, "created", buf); |
| |
285 |
| |
286 snprintf(buf, sizeof(buf), "%lu", status->lastused); |
| |
287 xmlnode_set_attrib(node, "lastused", buf); |
| |
288 |
| |
289 snprintf(buf, sizeof(buf), "%u", status->usage_count); |
| |
290 xmlnode_set_attrib(node, "usage_count", buf); |
| |
291 |
| |
292 child = xmlnode_new_child(node, "state"); |
| |
293 xmlnode_insert_data(child, gaim_primitive_get_id_from_type(status->type), -1); |
| |
294 |
| |
295 if (status->message != NULL) |
| |
296 { |
| |
297 child = xmlnode_new_child(node, "message"); |
| |
298 xmlnode_insert_data(child, status->message, -1); |
| |
299 } |
| |
300 |
| |
301 for (cur = status->substatuses; cur != NULL; cur = cur->next) |
| |
302 { |
| |
303 child = substatus_to_xmlnode(cur->data); |
| |
304 xmlnode_insert_child(node, child); |
| |
305 } |
| |
306 |
| |
307 return node; |
| |
308 } |
| |
309 |
| |
310 static xmlnode * |
| |
311 statuses_to_xmlnode(void) |
| |
312 { |
| |
313 xmlnode *node, *child; |
| |
314 GList *cur; |
| |
315 |
| |
316 node = xmlnode_new("statuses"); |
| |
317 xmlnode_set_attrib(node, "version", "1.0"); |
| |
318 |
| |
319 for (cur = saved_statuses; cur != NULL; cur = cur->next) |
| |
320 { |
| |
321 child = status_to_xmlnode(cur->data); |
| |
322 xmlnode_insert_child(node, child); |
| |
323 } |
| |
324 |
| |
325 return node; |
| |
326 } |
| |
327 |
| |
328 static void |
| |
329 sync_statuses(void) |
| |
330 { |
| |
331 xmlnode *node; |
| |
332 char *data; |
| |
333 |
| |
334 if (!statuses_loaded) |
| |
335 { |
| |
336 gaim_debug_error("status", "Attempted to save statuses before they " |
| |
337 "were read!\n"); |
| |
338 return; |
| |
339 } |
| |
340 |
| |
341 node = statuses_to_xmlnode(); |
| |
342 data = xmlnode_to_formatted_str(node, NULL); |
| |
343 gaim_util_write_data_to_file("status.xml", data, -1); |
| |
344 g_free(data); |
| |
345 xmlnode_free(node); |
| |
346 } |
| |
347 |
| |
348 static gboolean |
| |
349 save_cb(gpointer data) |
| |
350 { |
| |
351 sync_statuses(); |
| |
352 save_timer = 0; |
| |
353 return FALSE; |
| |
354 } |
| |
355 |
| |
356 static void |
| |
357 schedule_save(void) |
| |
358 { |
| |
359 if (save_timer == 0) |
| |
360 save_timer = gaim_timeout_add(5000, save_cb, NULL); |
| |
361 } |
| |
362 |
| |
363 |
| |
364 /********************************************************************* |
| |
365 * Reading from disk * |
| |
366 *********************************************************************/ |
| |
367 |
| |
368 static GaimSavedStatusSub * |
| |
369 parse_substatus(xmlnode *substatus) |
| |
370 { |
| |
371 GaimSavedStatusSub *ret; |
| |
372 xmlnode *node; |
| |
373 char *data; |
| |
374 |
| |
375 ret = g_new0(GaimSavedStatusSub, 1); |
| |
376 |
| |
377 /* Read the account */ |
| |
378 node = xmlnode_get_child(substatus, "account"); |
| |
379 if (node != NULL) |
| |
380 { |
| |
381 char *acct_name; |
| |
382 const char *protocol; |
| |
383 acct_name = xmlnode_get_data(node); |
| |
384 protocol = xmlnode_get_attrib(node, "protocol"); |
| |
385 if ((acct_name != NULL) && (protocol != NULL)) |
| |
386 ret->account = gaim_accounts_find(acct_name, protocol); |
| |
387 g_free(acct_name); |
| |
388 } |
| |
389 |
| |
390 if (ret->account == NULL) |
| |
391 { |
| |
392 g_free(ret); |
| |
393 return NULL; |
| |
394 } |
| |
395 |
| |
396 /* Read the state */ |
| |
397 node = xmlnode_get_child(substatus, "state"); |
| |
398 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL)) |
| |
399 { |
| |
400 ret->type = gaim_status_type_find_with_id( |
| |
401 ret->account->status_types, data); |
| |
402 g_free(data); |
| |
403 } |
| |
404 |
| |
405 /* Read the message */ |
| |
406 node = xmlnode_get_child(substatus, "message"); |
| |
407 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL)) |
| |
408 { |
| |
409 ret->message = data; |
| |
410 } |
| |
411 |
| |
412 GAIM_DBUS_REGISTER_POINTER(ret, GaimSavedStatusSub); |
| |
413 return ret; |
| |
414 } |
| |
415 |
| |
416 /** |
| |
417 * Parse a saved status and add it to the saved_statuses linked list. |
| |
418 * |
| |
419 * Here's an example of the XML for a saved status: |
| |
420 * <status name="Girls"> |
| |
421 * <state>away</state> |
| |
422 * <message>I like the way that they walk |
| |
423 * And it's chill to hear them talk |
| |
424 * And I can always make them smile |
| |
425 * From White Castle to the Nile</message> |
| |
426 * <substatus> |
| |
427 * <account protocol='prpl-oscar'>markdoliner</account> |
| |
428 * <state>available</state> |
| |
429 * <message>The ladies man is here to answer your queries.</message> |
| |
430 * </substatus> |
| |
431 * <substatus> |
| |
432 * <account protocol='prpl-oscar'>giantgraypanda</account> |
| |
433 * <state>away</state> |
| |
434 * <message>A.C. ain't in charge no more.</message> |
| |
435 * </substatus> |
| |
436 * </status> |
| |
437 * |
| |
438 * I know. Moving, huh? |
| |
439 */ |
| |
440 static GaimSavedStatus * |
| |
441 parse_status(xmlnode *status) |
| |
442 { |
| |
443 GaimSavedStatus *ret; |
| |
444 xmlnode *node; |
| |
445 const char *attrib; |
| |
446 char *data; |
| |
447 int i; |
| |
448 |
| |
449 ret = g_new0(GaimSavedStatus, 1); |
| |
450 |
| |
451 attrib = xmlnode_get_attrib(status, "transient"); |
| |
452 if ((attrib == NULL) || (strcmp(attrib, "true"))) |
| |
453 { |
| |
454 /* Read the title */ |
| |
455 attrib = xmlnode_get_attrib(status, "name"); |
| |
456 ret->title = g_strdup(attrib); |
| |
457 } |
| |
458 |
| |
459 if (ret->title != NULL) |
| |
460 { |
| |
461 /* Ensure the title is unique */ |
| |
462 i = 2; |
| |
463 while (gaim_savedstatus_find(ret->title) != NULL) |
| |
464 { |
| |
465 g_free(ret->title); |
| |
466 ret->title = g_strdup_printf("%s %d", attrib, i); |
| |
467 i++; |
| |
468 } |
| |
469 } |
| |
470 |
| |
471 /* Read the creation time */ |
| |
472 attrib = xmlnode_get_attrib(status, "created"); |
| |
473 set_creation_time(ret, (attrib != NULL ? atol(attrib) : 0)); |
| |
474 |
| |
475 /* Read the last used time */ |
| |
476 attrib = xmlnode_get_attrib(status, "lastused"); |
| |
477 ret->lastused = (attrib != NULL ? atol(attrib) : 0); |
| |
478 |
| |
479 /* Read the usage count */ |
| |
480 attrib = xmlnode_get_attrib(status, "usage_count"); |
| |
481 ret->usage_count = (attrib != NULL ? atol(attrib) : 0); |
| |
482 |
| |
483 /* Read the primitive status type */ |
| |
484 node = xmlnode_get_child(status, "state"); |
| |
485 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL)) |
| |
486 { |
| |
487 ret->type = gaim_primitive_get_type_from_id(data); |
| |
488 g_free(data); |
| |
489 } |
| |
490 |
| |
491 /* Read the message */ |
| |
492 node = xmlnode_get_child(status, "message"); |
| |
493 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL)) |
| |
494 { |
| |
495 ret->message = data; |
| |
496 } |
| |
497 |
| |
498 /* Read substatuses */ |
| |
499 for (node = xmlnode_get_child(status, "substatus"); node != NULL; |
| |
500 node = xmlnode_get_next_twin(node)) |
| |
501 { |
| |
502 GaimSavedStatusSub *new; |
| |
503 new = parse_substatus(node); |
| |
504 if (new != NULL) |
| |
505 ret->substatuses = g_list_prepend(ret->substatuses, new); |
| |
506 } |
| |
507 |
| |
508 GAIM_DBUS_REGISTER_POINTER(ret, GaimSavedStatus); |
| |
509 return ret; |
| |
510 } |
| |
511 |
| |
512 /** |
| |
513 * Read the saved statuses from a file in the Gaim user dir. |
| |
514 * |
| |
515 * @return TRUE on success, FALSE on failure (if the file can not |
| |
516 * be opened, or if it contains invalid XML). |
| |
517 */ |
| |
518 static void |
| |
519 load_statuses(void) |
| |
520 { |
| |
521 xmlnode *statuses, *status; |
| |
522 |
| |
523 statuses_loaded = TRUE; |
| |
524 |
| |
525 statuses = gaim_util_read_xml_from_file("status.xml", _("saved statuses")); |
| |
526 |
| |
527 if (statuses == NULL) |
| |
528 return; |
| |
529 |
| |
530 for (status = xmlnode_get_child(statuses, "status"); status != NULL; |
| |
531 status = xmlnode_get_next_twin(status)) |
| |
532 { |
| |
533 GaimSavedStatus *new; |
| |
534 new = parse_status(status); |
| |
535 saved_statuses = g_list_prepend(saved_statuses, new); |
| |
536 } |
| |
537 saved_statuses = g_list_sort(saved_statuses, saved_statuses_sort_func); |
| |
538 |
| |
539 xmlnode_free(statuses); |
| |
540 } |
| |
541 |
| |
542 |
| |
543 /************************************************************************** |
| |
544 * Saved status API |
| |
545 **************************************************************************/ |
| |
546 GaimSavedStatus * |
| |
547 gaim_savedstatus_new(const char *title, GaimStatusPrimitive type) |
| |
548 { |
| |
549 GaimSavedStatus *status; |
| |
550 |
| |
551 /* Make sure we don't already have a saved status with this title. */ |
| |
552 if (title != NULL) |
| |
553 g_return_val_if_fail(gaim_savedstatus_find(title) == NULL, NULL); |
| |
554 |
| |
555 status = g_new0(GaimSavedStatus, 1); |
| |
556 GAIM_DBUS_REGISTER_POINTER(status, GaimSavedStatus); |
| |
557 status->title = g_strdup(title); |
| |
558 status->type = type; |
| |
559 set_creation_time(status, time(NULL)); |
| |
560 |
| |
561 saved_statuses = g_list_insert_sorted(saved_statuses, status, saved_statuses_sort_func); |
| |
562 |
| |
563 schedule_save(); |
| |
564 |
| |
565 return status; |
| |
566 } |
| |
567 |
| |
568 void |
| |
569 gaim_savedstatus_set_title(GaimSavedStatus *status, const char *title) |
| |
570 { |
| |
571 g_return_if_fail(status != NULL); |
| |
572 |
| |
573 /* Make sure we don't already have a saved status with this title. */ |
| |
574 g_return_if_fail(gaim_savedstatus_find(title) == NULL); |
| |
575 |
| |
576 g_free(status->title); |
| |
577 status->title = g_strdup(title); |
| |
578 |
| |
579 schedule_save(); |
| |
580 } |
| |
581 |
| |
582 void |
| |
583 gaim_savedstatus_set_type(GaimSavedStatus *status, GaimStatusPrimitive type) |
| |
584 { |
| |
585 g_return_if_fail(status != NULL); |
| |
586 |
| |
587 status->type = type; |
| |
588 |
| |
589 schedule_save(); |
| |
590 } |
| |
591 |
| |
592 void |
| |
593 gaim_savedstatus_set_message(GaimSavedStatus *status, const char *message) |
| |
594 { |
| |
595 g_return_if_fail(status != NULL); |
| |
596 |
| |
597 g_free(status->message); |
| |
598 if ((message != NULL) && (*message == '\0')) |
| |
599 status->message = NULL; |
| |
600 else |
| |
601 status->message = g_strdup(message); |
| |
602 |
| |
603 schedule_save(); |
| |
604 } |
| |
605 |
| |
606 void |
| |
607 gaim_savedstatus_set_substatus(GaimSavedStatus *saved_status, |
| |
608 const GaimAccount *account, |
| |
609 const GaimStatusType *type, |
| |
610 const char *message) |
| |
611 { |
| |
612 GaimSavedStatusSub *substatus; |
| |
613 |
| |
614 g_return_if_fail(saved_status != NULL); |
| |
615 g_return_if_fail(account != NULL); |
| |
616 g_return_if_fail(type != NULL); |
| |
617 |
| |
618 /* Find an existing substatus or create a new one */ |
| |
619 substatus = gaim_savedstatus_get_substatus(saved_status, account); |
| |
620 if (substatus == NULL) |
| |
621 { |
| |
622 substatus = g_new0(GaimSavedStatusSub, 1); |
| |
623 GAIM_DBUS_REGISTER_POINTER(substatus, GaimSavedStatusSub); |
| |
624 substatus->account = (GaimAccount *)account; |
| |
625 saved_status->substatuses = g_list_prepend(saved_status->substatuses, substatus); |
| |
626 } |
| |
627 |
| |
628 substatus->type = type; |
| |
629 g_free(substatus->message); |
| |
630 substatus->message = g_strdup(message); |
| |
631 |
| |
632 schedule_save(); |
| |
633 } |
| |
634 |
| |
635 void |
| |
636 gaim_savedstatus_unset_substatus(GaimSavedStatus *saved_status, |
| |
637 const GaimAccount *account) |
| |
638 { |
| |
639 GList *iter; |
| |
640 GaimSavedStatusSub *substatus; |
| |
641 |
| |
642 g_return_if_fail(saved_status != NULL); |
| |
643 g_return_if_fail(account != NULL); |
| |
644 |
| |
645 for (iter = saved_status->substatuses; iter != NULL; iter = iter->next) |
| |
646 { |
| |
647 substatus = iter->data; |
| |
648 if (substatus->account == account) |
| |
649 { |
| |
650 saved_status->substatuses = g_list_delete_link(saved_status->substatuses, iter); |
| |
651 g_free(substatus->message); |
| |
652 g_free(substatus); |
| |
653 return; |
| |
654 } |
| |
655 } |
| |
656 } |
| |
657 |
| |
658 /* |
| |
659 * This gets called when an account is deleted. We iterate through |
| |
660 * all of our saved statuses and delete any substatuses that may |
| |
661 * exist for this account. |
| |
662 */ |
| |
663 static void |
| |
664 gaim_savedstatus_unset_all_substatuses(const GaimAccount *account, |
| |
665 gpointer user_data) |
| |
666 { |
| |
667 GList *iter; |
| |
668 GaimSavedStatus *status; |
| |
669 |
| |
670 g_return_if_fail(account != NULL); |
| |
671 |
| |
672 for (iter = saved_statuses; iter != NULL; iter = iter->next) |
| |
673 { |
| |
674 status = (GaimSavedStatus *)iter->data; |
| |
675 gaim_savedstatus_unset_substatus(status, account); |
| |
676 } |
| |
677 } |
| |
678 |
| |
679 gboolean |
| |
680 gaim_savedstatus_delete(const char *title) |
| |
681 { |
| |
682 GaimSavedStatus *status; |
| |
683 time_t creation_time, current, idleaway; |
| |
684 |
| |
685 status = gaim_savedstatus_find(title); |
| |
686 |
| |
687 if (status == NULL) |
| |
688 return FALSE; |
| |
689 |
| |
690 saved_statuses = g_list_remove(saved_statuses, status); |
| |
691 creation_time = gaim_savedstatus_get_creation_time(status); |
| |
692 g_hash_table_remove(creation_times, &creation_time); |
| |
693 free_saved_status(status); |
| |
694 |
| |
695 schedule_save(); |
| |
696 |
| |
697 /* |
| |
698 * If we just deleted our current status or our idleaway status, |
| |
699 * then set the appropriate pref back to 0. |
| |
700 */ |
| |
701 current = gaim_prefs_get_int("/core/savedstatus/default"); |
| |
702 if (current == creation_time) |
| |
703 gaim_prefs_set_int("/core/savedstatus/default", 0); |
| |
704 |
| |
705 idleaway = gaim_prefs_get_int("/core/savedstatus/idleaway"); |
| |
706 if (idleaway == creation_time) |
| |
707 gaim_prefs_set_int("/core/savedstatus/idleaway", 0); |
| |
708 |
| |
709 return TRUE; |
| |
710 } |
| |
711 |
| |
712 const GList * |
| |
713 gaim_savedstatuses_get_all(void) |
| |
714 { |
| |
715 return saved_statuses; |
| |
716 } |
| |
717 |
| |
718 GList * |
| |
719 gaim_savedstatuses_get_popular(unsigned int how_many) |
| |
720 { |
| |
721 GList *popular = NULL; |
| |
722 GList *cur; |
| |
723 int i; |
| |
724 GaimSavedStatus *next; |
| |
725 |
| |
726 /* Copy 'how_many' elements to a new list */ |
| |
727 i = 0; |
| |
728 cur = saved_statuses; |
| |
729 while ((i < how_many) && (cur != NULL)) |
| |
730 { |
| |
731 next = cur->data; |
| |
732 if ((!gaim_savedstatus_is_transient(next) |
| |
733 || gaim_savedstatus_get_message(next) != NULL)) |
| |
734 { |
| |
735 popular = g_list_prepend(popular, cur->data); |
| |
736 i++; |
| |
737 } |
| |
738 cur = cur->next; |
| |
739 } |
| |
740 |
| |
741 popular = g_list_reverse(popular); |
| |
742 |
| |
743 return popular; |
| |
744 } |
| |
745 |
| |
746 GaimSavedStatus * |
| |
747 gaim_savedstatus_get_current(void) |
| |
748 { |
| |
749 if (gaim_savedstatus_is_idleaway()) |
| |
750 return gaim_savedstatus_get_idleaway(); |
| |
751 else |
| |
752 return gaim_savedstatus_get_default(); |
| |
753 } |
| |
754 |
| |
755 GaimSavedStatus * |
| |
756 gaim_savedstatus_get_default() |
| |
757 { |
| |
758 int creation_time; |
| |
759 GaimSavedStatus *saved_status = NULL; |
| |
760 |
| |
761 creation_time = gaim_prefs_get_int("/core/savedstatus/default"); |
| |
762 |
| |
763 if (creation_time != 0) |
| |
764 saved_status = g_hash_table_lookup(creation_times, &creation_time); |
| |
765 |
| |
766 if (saved_status == NULL) |
| |
767 { |
| |
768 /* |
| |
769 * We don't have a current saved status! This is either a new |
| |
770 * Gaim user or someone upgrading from Gaim 1.5.0 or older, or |
| |
771 * possibly someone who deleted the status they were currently |
| |
772 * using? In any case, add a default status. |
| |
773 */ |
| |
774 saved_status = gaim_savedstatus_new(NULL, GAIM_STATUS_AVAILABLE); |
| |
775 gaim_prefs_set_int("/core/savedstatus/default", |
| |
776 gaim_savedstatus_get_creation_time(saved_status)); |
| |
777 } |
| |
778 |
| |
779 return saved_status; |
| |
780 } |
| |
781 |
| |
782 GaimSavedStatus * |
| |
783 gaim_savedstatus_get_idleaway() |
| |
784 { |
| |
785 int creation_time; |
| |
786 GaimSavedStatus *saved_status = NULL; |
| |
787 |
| |
788 creation_time = gaim_prefs_get_int("/core/savedstatus/idleaway"); |
| |
789 |
| |
790 if (creation_time != 0) |
| |
791 saved_status = g_hash_table_lookup(creation_times, &creation_time); |
| |
792 |
| |
793 if (saved_status == NULL) |
| |
794 { |
| |
795 /* We don't have a specified "idle" status! Weird. */ |
| |
796 saved_status = gaim_savedstatus_find_transient_by_type_and_message( |
| |
797 GAIM_STATUS_AWAY, DEFAULT_AUTOAWAY_MESSAGE); |
| |
798 |
| |
799 if (saved_status == NULL) |
| |
800 { |
| |
801 saved_status = gaim_savedstatus_new(NULL, GAIM_STATUS_AWAY); |
| |
802 gaim_savedstatus_set_message(saved_status, DEFAULT_AUTOAWAY_MESSAGE); |
| |
803 gaim_prefs_set_int("/core/savedstatus/idleaway", |
| |
804 gaim_savedstatus_get_creation_time(saved_status)); |
| |
805 } |
| |
806 } |
| |
807 |
| |
808 return saved_status; |
| |
809 } |
| |
810 |
| |
811 gboolean |
| |
812 gaim_savedstatus_is_idleaway() |
| |
813 { |
| |
814 return gaim_prefs_get_bool("/core/savedstatus/isidleaway"); |
| |
815 } |
| |
816 |
| |
817 void |
| |
818 gaim_savedstatus_set_idleaway(gboolean idleaway) |
| |
819 { |
| |
820 GList *accounts, *node; |
| |
821 GaimSavedStatus *old, *saved_status; |
| |
822 |
| |
823 if (gaim_savedstatus_is_idleaway() == idleaway) |
| |
824 /* Don't need to do anything */ |
| |
825 return; |
| |
826 |
| |
827 /* Changing our status makes us un-idle */ |
| |
828 if (!idleaway) |
| |
829 gaim_idle_touch(); |
| |
830 |
| |
831 old = gaim_savedstatus_get_current(); |
| |
832 gaim_prefs_set_bool("/core/savedstatus/isidleaway", idleaway); |
| |
833 saved_status = idleaway ? gaim_savedstatus_get_idleaway() |
| |
834 : gaim_savedstatus_get_default(); |
| |
835 |
| |
836 if (idleaway && (gaim_savedstatus_get_type(old) != GAIM_STATUS_AVAILABLE)) |
| |
837 /* Our global status is already "away," so don't change anything */ |
| |
838 return; |
| |
839 |
| |
840 accounts = gaim_accounts_get_all_active(); |
| |
841 for (node = accounts; node != NULL; node = node->next) |
| |
842 { |
| |
843 GaimAccount *account; |
| |
844 GaimPresence *presence; |
| |
845 GaimStatus *status; |
| |
846 |
| |
847 account = node->data; |
| |
848 presence = gaim_account_get_presence(account); |
| |
849 status = gaim_presence_get_active_status(presence); |
| |
850 |
| |
851 if (!idleaway || gaim_status_is_available(status)) |
| |
852 gaim_savedstatus_activate_for_account(saved_status, account); |
| |
853 } |
| |
854 |
| |
855 g_list_free(accounts); |
| |
856 |
| |
857 gaim_signal_emit(gaim_savedstatuses_get_handle(), "savedstatus-changed", |
| |
858 saved_status, old); |
| |
859 } |
| |
860 |
| |
861 GaimSavedStatus * |
| |
862 gaim_savedstatus_get_startup() |
| |
863 { |
| |
864 int creation_time; |
| |
865 GaimSavedStatus *saved_status = NULL; |
| |
866 |
| |
867 creation_time = gaim_prefs_get_int("/core/savedstatus/startup"); |
| |
868 |
| |
869 if (creation_time != 0) |
| |
870 saved_status = g_hash_table_lookup(creation_times, &creation_time); |
| |
871 |
| |
872 if (saved_status == NULL) |
| |
873 { |
| |
874 /* |
| |
875 * We don't have a status to apply. |
| |
876 * This may be the first login, or the user wants to |
| |
877 * restore the "current" status. |
| |
878 */ |
| |
879 saved_status = gaim_savedstatus_get_current(); |
| |
880 } |
| |
881 |
| |
882 return saved_status; |
| |
883 } |
| |
884 |
| |
885 |
| |
886 GaimSavedStatus * |
| |
887 gaim_savedstatus_find(const char *title) |
| |
888 { |
| |
889 GList *iter; |
| |
890 GaimSavedStatus *status; |
| |
891 |
| |
892 g_return_val_if_fail(title != NULL, NULL); |
| |
893 |
| |
894 for (iter = saved_statuses; iter != NULL; iter = iter->next) |
| |
895 { |
| |
896 status = (GaimSavedStatus *)iter->data; |
| |
897 if ((status->title != NULL) && !strcmp(status->title, title)) |
| |
898 return status; |
| |
899 } |
| |
900 |
| |
901 return NULL; |
| |
902 } |
| |
903 |
| |
904 GaimSavedStatus * |
| |
905 gaim_savedstatus_find_by_creation_time(time_t creation_time) |
| |
906 { |
| |
907 GList *iter; |
| |
908 GaimSavedStatus *status; |
| |
909 |
| |
910 for (iter = saved_statuses; iter != NULL; iter = iter->next) |
| |
911 { |
| |
912 status = (GaimSavedStatus *)iter->data; |
| |
913 if (status->creation_time == creation_time) |
| |
914 return status; |
| |
915 } |
| |
916 |
| |
917 return NULL; |
| |
918 } |
| |
919 |
| |
920 GaimSavedStatus * |
| |
921 gaim_savedstatus_find_transient_by_type_and_message(GaimStatusPrimitive type, |
| |
922 const char *message) |
| |
923 { |
| |
924 GList *iter; |
| |
925 GaimSavedStatus *status; |
| |
926 |
| |
927 for (iter = saved_statuses; iter != NULL; iter = iter->next) |
| |
928 { |
| |
929 status = (GaimSavedStatus *)iter->data; |
| |
930 if ((status->type == type) && gaim_savedstatus_is_transient(status) && |
| |
931 !gaim_savedstatus_has_substatuses(status) && |
| |
932 (((status->message == NULL) && (message == NULL)) || |
| |
933 ((status->message != NULL) && (message != NULL) && !strcmp(status->message, message)))) |
| |
934 { |
| |
935 return status; |
| |
936 } |
| |
937 } |
| |
938 |
| |
939 return NULL; |
| |
940 } |
| |
941 |
| |
942 gboolean |
| |
943 gaim_savedstatus_is_transient(const GaimSavedStatus *saved_status) |
| |
944 { |
| |
945 g_return_val_if_fail(saved_status != NULL, TRUE); |
| |
946 |
| |
947 return (saved_status->title == NULL); |
| |
948 } |
| |
949 |
| |
950 const char * |
| |
951 gaim_savedstatus_get_title(const GaimSavedStatus *saved_status) |
| |
952 { |
| |
953 const char *message; |
| |
954 |
| |
955 g_return_val_if_fail(saved_status != NULL, NULL); |
| |
956 |
| |
957 /* If we have a title then return it */ |
| |
958 if (saved_status->title != NULL) |
| |
959 return saved_status->title; |
| |
960 |
| |
961 /* Otherwise, this is a transient status and we make up a title on the fly */ |
| |
962 message = gaim_savedstatus_get_message(saved_status); |
| |
963 |
| |
964 if ((message == NULL) || (*message == '\0')) |
| |
965 { |
| |
966 GaimStatusPrimitive primitive; |
| |
967 primitive = gaim_savedstatus_get_type(saved_status); |
| |
968 return gaim_primitive_get_name_from_type(primitive); |
| |
969 } |
| |
970 else |
| |
971 { |
| |
972 char *stripped; |
| |
973 static char buf[64]; |
| |
974 stripped = gaim_markup_strip_html(message); |
| |
975 gaim_util_chrreplace(stripped, '\n', ' '); |
| |
976 strncpy(buf, stripped, sizeof(buf)); |
| |
977 buf[sizeof(buf) - 1] = '\0'; |
| |
978 if ((strlen(stripped) + 1) > sizeof(buf)) |
| |
979 { |
| |
980 /* Truncate and ellipsize */ |
| |
981 char *tmp = g_utf8_find_prev_char(buf, &buf[sizeof(buf) - 4]); |
| |
982 strcpy(tmp, "..."); |
| |
983 } |
| |
984 g_free(stripped); |
| |
985 return buf; |
| |
986 } |
| |
987 } |
| |
988 |
| |
989 GaimStatusPrimitive |
| |
990 gaim_savedstatus_get_type(const GaimSavedStatus *saved_status) |
| |
991 { |
| |
992 g_return_val_if_fail(saved_status != NULL, GAIM_STATUS_OFFLINE); |
| |
993 |
| |
994 return saved_status->type; |
| |
995 } |
| |
996 |
| |
997 const char * |
| |
998 gaim_savedstatus_get_message(const GaimSavedStatus *saved_status) |
| |
999 { |
| |
1000 g_return_val_if_fail(saved_status != NULL, NULL); |
| |
1001 |
| |
1002 return saved_status->message; |
| |
1003 } |
| |
1004 |
| |
1005 time_t |
| |
1006 gaim_savedstatus_get_creation_time(const GaimSavedStatus *saved_status) |
| |
1007 { |
| |
1008 g_return_val_if_fail(saved_status != NULL, 0); |
| |
1009 |
| |
1010 return saved_status->creation_time; |
| |
1011 } |
| |
1012 |
| |
1013 gboolean |
| |
1014 gaim_savedstatus_has_substatuses(const GaimSavedStatus *saved_status) |
| |
1015 { |
| |
1016 g_return_val_if_fail(saved_status != NULL, FALSE); |
| |
1017 |
| |
1018 return (saved_status->substatuses != NULL); |
| |
1019 } |
| |
1020 |
| |
1021 GaimSavedStatusSub * |
| |
1022 gaim_savedstatus_get_substatus(const GaimSavedStatus *saved_status, |
| |
1023 const GaimAccount *account) |
| |
1024 { |
| |
1025 GList *iter; |
| |
1026 GaimSavedStatusSub *substatus; |
| |
1027 |
| |
1028 g_return_val_if_fail(saved_status != NULL, NULL); |
| |
1029 g_return_val_if_fail(account != NULL, NULL); |
| |
1030 |
| |
1031 for (iter = saved_status->substatuses; iter != NULL; iter = iter->next) |
| |
1032 { |
| |
1033 substatus = iter->data; |
| |
1034 if (substatus->account == account) |
| |
1035 return substatus; |
| |
1036 } |
| |
1037 |
| |
1038 return NULL; |
| |
1039 } |
| |
1040 |
| |
1041 const GaimStatusType * |
| |
1042 gaim_savedstatus_substatus_get_type(const GaimSavedStatusSub *substatus) |
| |
1043 { |
| |
1044 g_return_val_if_fail(substatus != NULL, NULL); |
| |
1045 |
| |
1046 return substatus->type; |
| |
1047 } |
| |
1048 |
| |
1049 const char * |
| |
1050 gaim_savedstatus_substatus_get_message(const GaimSavedStatusSub *substatus) |
| |
1051 { |
| |
1052 g_return_val_if_fail(substatus != NULL, NULL); |
| |
1053 |
| |
1054 return substatus->message; |
| |
1055 } |
| |
1056 |
| |
1057 void |
| |
1058 gaim_savedstatus_activate(GaimSavedStatus *saved_status) |
| |
1059 { |
| |
1060 GList *accounts, *node; |
| |
1061 GaimSavedStatus *old = gaim_savedstatus_get_current(); |
| |
1062 |
| |
1063 g_return_if_fail(saved_status != NULL); |
| |
1064 |
| |
1065 /* Make sure our list of saved statuses remains sorted */ |
| |
1066 saved_status->lastused = time(NULL); |
| |
1067 saved_status->usage_count++; |
| |
1068 saved_statuses = g_list_remove(saved_statuses, saved_status); |
| |
1069 saved_statuses = g_list_insert_sorted(saved_statuses, saved_status, saved_statuses_sort_func); |
| |
1070 |
| |
1071 accounts = gaim_accounts_get_all_active(); |
| |
1072 for (node = accounts; node != NULL; node = node->next) |
| |
1073 { |
| |
1074 GaimAccount *account; |
| |
1075 |
| |
1076 account = node->data; |
| |
1077 |
| |
1078 gaim_savedstatus_activate_for_account(saved_status, account); |
| |
1079 } |
| |
1080 |
| |
1081 g_list_free(accounts); |
| |
1082 |
| |
1083 gaim_prefs_set_int("/core/savedstatus/default", |
| |
1084 gaim_savedstatus_get_creation_time(saved_status)); |
| |
1085 gaim_savedstatus_set_idleaway(FALSE); |
| |
1086 |
| |
1087 gaim_signal_emit(gaim_savedstatuses_get_handle(), "savedstatus-changed", |
| |
1088 saved_status, old); |
| |
1089 } |
| |
1090 |
| |
1091 void |
| |
1092 gaim_savedstatus_activate_for_account(const GaimSavedStatus *saved_status, |
| |
1093 GaimAccount *account) |
| |
1094 { |
| |
1095 const GaimStatusType *status_type; |
| |
1096 const GaimSavedStatusSub *substatus; |
| |
1097 const char *message = NULL; |
| |
1098 |
| |
1099 g_return_if_fail(saved_status != NULL); |
| |
1100 g_return_if_fail(account != NULL); |
| |
1101 |
| |
1102 substatus = gaim_savedstatus_get_substatus(saved_status, account); |
| |
1103 if (substatus != NULL) |
| |
1104 { |
| |
1105 status_type = substatus->type; |
| |
1106 message = substatus->message; |
| |
1107 } |
| |
1108 else |
| |
1109 { |
| |
1110 status_type = gaim_account_get_status_type_with_primitive(account, saved_status->type); |
| |
1111 if (status_type == NULL) |
| |
1112 return; |
| |
1113 message = saved_status->message; |
| |
1114 } |
| |
1115 |
| |
1116 if ((message != NULL) && |
| |
1117 (gaim_status_type_get_attr(status_type, "message"))) |
| |
1118 { |
| |
1119 gaim_account_set_status(account, gaim_status_type_get_id(status_type), |
| |
1120 TRUE, "message", message, NULL); |
| |
1121 } |
| |
1122 else |
| |
1123 { |
| |
1124 gaim_account_set_status(account, gaim_status_type_get_id(status_type), |
| |
1125 TRUE, NULL); |
| |
1126 } |
| |
1127 } |
| |
1128 |
| |
1129 void * |
| |
1130 gaim_savedstatuses_get_handle(void) |
| |
1131 { |
| |
1132 static int handle; |
| |
1133 |
| |
1134 return &handle; |
| |
1135 } |
| |
1136 |
| |
1137 void |
| |
1138 gaim_savedstatuses_init(void) |
| |
1139 { |
| |
1140 void *handle = gaim_savedstatuses_get_handle(); |
| |
1141 |
| |
1142 creation_times = g_hash_table_new(g_int_hash, g_int_equal); |
| |
1143 |
| |
1144 /* |
| |
1145 * Using 0 as the creation_time is a special case. |
| |
1146 * If someone calls gaim_savedstatus_get_current() or |
| |
1147 * gaim_savedstatus_get_idleaway() and either of those functions |
| |
1148 * sees a creation_time of 0, then it will create a default |
| |
1149 * saved status and return that to the user. |
| |
1150 */ |
| |
1151 gaim_prefs_add_none("/core/savedstatus"); |
| |
1152 gaim_prefs_add_int("/core/savedstatus/default", 0); |
| |
1153 gaim_prefs_add_int("/core/savedstatus/startup", 0); |
| |
1154 gaim_prefs_add_bool("/core/savedstatus/startup_current_status", TRUE); |
| |
1155 gaim_prefs_add_int("/core/savedstatus/idleaway", 0); |
| |
1156 gaim_prefs_add_bool("/core/savedstatus/isidleaway", FALSE); |
| |
1157 |
| |
1158 load_statuses(); |
| |
1159 |
| |
1160 gaim_signal_register(handle, "savedstatus-changed", |
| |
1161 gaim_marshal_VOID__POINTER_POINTER, NULL, 2, |
| |
1162 gaim_value_new(GAIM_TYPE_SUBTYPE, |
| |
1163 GAIM_SUBTYPE_SAVEDSTATUS), |
| |
1164 gaim_value_new(GAIM_TYPE_SUBTYPE, |
| |
1165 GAIM_SUBTYPE_SAVEDSTATUS)); |
| |
1166 |
| |
1167 gaim_signal_connect(gaim_accounts_get_handle(), "account-removed", |
| |
1168 handle, |
| |
1169 GAIM_CALLBACK(gaim_savedstatus_unset_all_substatuses), |
| |
1170 NULL); |
| |
1171 } |
| |
1172 |
| |
1173 void |
| |
1174 gaim_savedstatuses_uninit(void) |
| |
1175 { |
| |
1176 remove_old_transient_statuses(); |
| |
1177 |
| |
1178 if (save_timer != 0) |
| |
1179 { |
| |
1180 gaim_timeout_remove(save_timer); |
| |
1181 save_timer = 0; |
| |
1182 sync_statuses(); |
| |
1183 } |
| |
1184 |
| |
1185 while (saved_statuses != NULL) { |
| |
1186 GaimSavedStatus *saved_status = saved_statuses->data; |
| |
1187 saved_statuses = g_list_remove(saved_statuses, saved_status); |
| |
1188 free_saved_status(saved_status); |
| |
1189 } |
| |
1190 |
| |
1191 g_hash_table_destroy(creation_times); |
| |
1192 |
| |
1193 gaim_signals_unregister_by_instance(gaim_savedstatuses_get_handle()); |
| |
1194 } |
| |
1195 |