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