| 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 GAIM_DBUS_REGISTER_POINTER(ret, GaimSavedStatusSub); |
|
| 377 |
|
| 378 /* Read the account */ |
|
| 379 node = xmlnode_get_child(substatus, "account"); |
|
| 380 if (node != NULL) |
|
| 381 { |
|
| 382 char *acct_name; |
|
| 383 const char *protocol; |
|
| 384 acct_name = xmlnode_get_data(node); |
|
| 385 protocol = xmlnode_get_attrib(node, "protocol"); |
|
| 386 if ((acct_name != NULL) && (protocol != NULL)) |
|
| 387 ret->account = gaim_accounts_find(acct_name, protocol); |
|
| 388 g_free(acct_name); |
|
| 389 } |
|
| 390 |
|
| 391 if (ret->account == NULL) |
|
| 392 { |
|
| 393 g_free(ret); |
|
| 394 return NULL; |
|
| 395 } |
|
| 396 |
|
| 397 /* Read the state */ |
|
| 398 node = xmlnode_get_child(substatus, "state"); |
|
| 399 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL)) |
|
| 400 { |
|
| 401 ret->type = gaim_status_type_find_with_id( |
|
| 402 ret->account->status_types, data); |
|
| 403 g_free(data); |
|
| 404 } |
|
| 405 |
|
| 406 /* Read the message */ |
|
| 407 node = xmlnode_get_child(substatus, "message"); |
|
| 408 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL)) |
|
| 409 { |
|
| 410 ret->message = data; |
|
| 411 } |
|
| 412 |
|
| 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 GAIM_DBUS_REGISTER_POINTER(ret, GaimSavedStatus); |
|
| 451 |
|
| 452 attrib = xmlnode_get_attrib(status, "transient"); |
|
| 453 if ((attrib == NULL) || (strcmp(attrib, "true"))) |
|
| 454 { |
|
| 455 /* Read the title */ |
|
| 456 attrib = xmlnode_get_attrib(status, "name"); |
|
| 457 ret->title = g_strdup(attrib); |
|
| 458 } |
|
| 459 |
|
| 460 if (ret->title != NULL) |
|
| 461 { |
|
| 462 /* Ensure the title is unique */ |
|
| 463 i = 2; |
|
| 464 while (gaim_savedstatus_find(ret->title) != NULL) |
|
| 465 { |
|
| 466 g_free(ret->title); |
|
| 467 ret->title = g_strdup_printf("%s %d", attrib, i); |
|
| 468 i++; |
|
| 469 } |
|
| 470 } |
|
| 471 |
|
| 472 /* Read the creation time */ |
|
| 473 attrib = xmlnode_get_attrib(status, "created"); |
|
| 474 set_creation_time(ret, (attrib != NULL ? atol(attrib) : 0)); |
|
| 475 |
|
| 476 /* Read the last used time */ |
|
| 477 attrib = xmlnode_get_attrib(status, "lastused"); |
|
| 478 ret->lastused = (attrib != NULL ? atol(attrib) : 0); |
|
| 479 |
|
| 480 /* Read the usage count */ |
|
| 481 attrib = xmlnode_get_attrib(status, "usage_count"); |
|
| 482 ret->usage_count = (attrib != NULL ? atol(attrib) : 0); |
|
| 483 |
|
| 484 /* Read the primitive status type */ |
|
| 485 node = xmlnode_get_child(status, "state"); |
|
| 486 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL)) |
|
| 487 { |
|
| 488 ret->type = gaim_primitive_get_type_from_id(data); |
|
| 489 g_free(data); |
|
| 490 } |
|
| 491 |
|
| 492 /* Read the message */ |
|
| 493 node = xmlnode_get_child(status, "message"); |
|
| 494 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL)) |
|
| 495 { |
|
| 496 ret->message = data; |
|
| 497 } |
|
| 498 |
|
| 499 /* Read substatuses */ |
|
| 500 for (node = xmlnode_get_child(status, "substatus"); node != NULL; |
|
| 501 node = xmlnode_get_next_twin(node)) |
|
| 502 { |
|
| 503 GaimSavedStatusSub *new; |
|
| 504 new = parse_substatus(node); |
|
| 505 if (new != NULL) |
|
| 506 ret->substatuses = g_list_prepend(ret->substatuses, new); |
|
| 507 } |
|
| 508 |
|
| 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 gboolean |
|
| 659 gaim_savedstatus_delete(const char *title) |
|
| 660 { |
|
| 661 GaimSavedStatus *status; |
|
| 662 time_t creation_time, current, idleaway; |
|
| 663 |
|
| 664 status = gaim_savedstatus_find(title); |
|
| 665 |
|
| 666 if (status == NULL) |
|
| 667 return FALSE; |
|
| 668 |
|
| 669 saved_statuses = g_list_remove(saved_statuses, status); |
|
| 670 creation_time = gaim_savedstatus_get_creation_time(status); |
|
| 671 g_hash_table_remove(creation_times, &creation_time); |
|
| 672 free_saved_status(status); |
|
| 673 |
|
| 674 schedule_save(); |
|
| 675 |
|
| 676 /* |
|
| 677 * If we just deleted our current status or our idleaway status, |
|
| 678 * then set the appropriate pref back to 0. |
|
| 679 */ |
|
| 680 current = gaim_prefs_get_int("/core/savedstatus/default"); |
|
| 681 if (current == creation_time) |
|
| 682 gaim_prefs_set_int("/core/savedstatus/default", 0); |
|
| 683 |
|
| 684 idleaway = gaim_prefs_get_int("/core/savedstatus/idleaway"); |
|
| 685 if (idleaway == creation_time) |
|
| 686 gaim_prefs_set_int("/core/savedstatus/idleaway", 0); |
|
| 687 |
|
| 688 return TRUE; |
|
| 689 } |
|
| 690 |
|
| 691 const GList * |
|
| 692 gaim_savedstatuses_get_all(void) |
|
| 693 { |
|
| 694 return saved_statuses; |
|
| 695 } |
|
| 696 |
|
| 697 GList * |
|
| 698 gaim_savedstatuses_get_popular(unsigned int how_many) |
|
| 699 { |
|
| 700 GList *popular = NULL; |
|
| 701 GList *cur; |
|
| 702 int i; |
|
| 703 GaimSavedStatus *current, *next; |
|
| 704 |
|
| 705 /* We don't want the current status to be in the GList */ |
|
| 706 current = gaim_savedstatus_get_current(); |
|
| 707 |
|
| 708 /* Copy 'how_many' elements to a new list */ |
|
| 709 i = 0; |
|
| 710 cur = saved_statuses; |
|
| 711 while ((i < how_many) && (cur != NULL)) |
|
| 712 { |
|
| 713 next = cur->data; |
|
| 714 if ((next != current) && (!gaim_savedstatus_is_transient(next) |
|
| 715 || gaim_savedstatus_get_message(next) != NULL)) |
|
| 716 { |
|
| 717 popular = g_list_prepend(popular, cur->data); |
|
| 718 i++; |
|
| 719 } |
|
| 720 cur = cur->next; |
|
| 721 } |
|
| 722 |
|
| 723 popular = g_list_reverse(popular); |
|
| 724 |
|
| 725 return popular; |
|
| 726 } |
|
| 727 |
|
| 728 GaimSavedStatus * |
|
| 729 gaim_savedstatus_get_current(void) |
|
| 730 { |
|
| 731 if (gaim_savedstatus_is_idleaway()) |
|
| 732 return gaim_savedstatus_get_idleaway(); |
|
| 733 else |
|
| 734 return gaim_savedstatus_get_default(); |
|
| 735 } |
|
| 736 |
|
| 737 GaimSavedStatus * |
|
| 738 gaim_savedstatus_get_default() |
|
| 739 { |
|
| 740 int creation_time; |
|
| 741 GaimSavedStatus *saved_status = NULL; |
|
| 742 |
|
| 743 creation_time = gaim_prefs_get_int("/core/savedstatus/default"); |
|
| 744 |
|
| 745 if (creation_time != 0) |
|
| 746 saved_status = g_hash_table_lookup(creation_times, &creation_time); |
|
| 747 |
|
| 748 if (saved_status == NULL) |
|
| 749 { |
|
| 750 /* |
|
| 751 * We don't have a current saved status! This is either a new |
|
| 752 * Gaim user or someone upgrading from Gaim 1.5.0 or older, or |
|
| 753 * possibly someone who deleted the status they were currently |
|
| 754 * using? In any case, add a default status. |
|
| 755 */ |
|
| 756 saved_status = gaim_savedstatus_new(NULL, GAIM_STATUS_AVAILABLE); |
|
| 757 gaim_prefs_set_int("/core/savedstatus/default", |
|
| 758 gaim_savedstatus_get_creation_time(saved_status)); |
|
| 759 } |
|
| 760 |
|
| 761 return saved_status; |
|
| 762 } |
|
| 763 |
|
| 764 GaimSavedStatus * |
|
| 765 gaim_savedstatus_get_idleaway() |
|
| 766 { |
|
| 767 int creation_time; |
|
| 768 GaimSavedStatus *saved_status = NULL; |
|
| 769 |
|
| 770 creation_time = gaim_prefs_get_int("/core/savedstatus/idleaway"); |
|
| 771 |
|
| 772 if (creation_time != 0) |
|
| 773 saved_status = g_hash_table_lookup(creation_times, &creation_time); |
|
| 774 |
|
| 775 if (saved_status == NULL) |
|
| 776 { |
|
| 777 /* We don't have a specified "idle" status! Weird. */ |
|
| 778 saved_status = gaim_savedstatus_find_transient_by_type_and_message( |
|
| 779 GAIM_STATUS_AWAY, DEFAULT_AUTOAWAY_MESSAGE); |
|
| 780 |
|
| 781 if (saved_status == NULL) |
|
| 782 { |
|
| 783 saved_status = gaim_savedstatus_new(NULL, GAIM_STATUS_AWAY); |
|
| 784 gaim_savedstatus_set_message(saved_status, DEFAULT_AUTOAWAY_MESSAGE); |
|
| 785 gaim_prefs_set_int("/core/savedstatus/idleaway", |
|
| 786 gaim_savedstatus_get_creation_time(saved_status)); |
|
| 787 } |
|
| 788 } |
|
| 789 |
|
| 790 return saved_status; |
|
| 791 } |
|
| 792 |
|
| 793 gboolean |
|
| 794 gaim_savedstatus_is_idleaway() |
|
| 795 { |
|
| 796 return gaim_prefs_get_bool("/core/savedstatus/isidleaway"); |
|
| 797 } |
|
| 798 |
|
| 799 void |
|
| 800 gaim_savedstatus_set_idleaway(gboolean idleaway) |
|
| 801 { |
|
| 802 GList *accounts, *node; |
|
| 803 GaimSavedStatus *old, *saved_status; |
|
| 804 |
|
| 805 if (gaim_savedstatus_is_idleaway() == idleaway) |
|
| 806 /* Don't need to do anything */ |
|
| 807 return; |
|
| 808 |
|
| 809 /* Changing our status makes us un-idle */ |
|
| 810 if (!idleaway) |
|
| 811 gaim_idle_touch(); |
|
| 812 |
|
| 813 old = gaim_savedstatus_get_current(); |
|
| 814 gaim_prefs_set_bool("/core/savedstatus/isidleaway", idleaway); |
|
| 815 saved_status = gaim_savedstatus_get_current(); |
|
| 816 |
|
| 817 accounts = gaim_accounts_get_all_active(); |
|
| 818 for (node = accounts; node != NULL; node = node->next) |
|
| 819 { |
|
| 820 GaimAccount *account; |
|
| 821 GaimPresence *presence; |
|
| 822 GaimStatus *status; |
|
| 823 |
|
| 824 account = node->data; |
|
| 825 presence = gaim_account_get_presence(account); |
|
| 826 status = gaim_presence_get_active_status(presence); |
|
| 827 |
|
| 828 if (!idleaway || gaim_status_is_available(status)) |
|
| 829 gaim_savedstatus_activate_for_account(saved_status, account); |
|
| 830 } |
|
| 831 |
|
| 832 g_list_free(accounts); |
|
| 833 |
|
| 834 gaim_signal_emit(gaim_savedstatuses_get_handle(), "savedstatus-changed", |
|
| 835 saved_status, old); |
|
| 836 } |
|
| 837 |
|
| 838 GaimSavedStatus * |
|
| 839 gaim_savedstatus_get_startup() |
|
| 840 { |
|
| 841 int creation_time; |
|
| 842 GaimSavedStatus *saved_status = NULL; |
|
| 843 |
|
| 844 creation_time = gaim_prefs_get_int("/core/savedstatus/startup"); |
|
| 845 |
|
| 846 if (creation_time != 0) |
|
| 847 saved_status = g_hash_table_lookup(creation_times, &creation_time); |
|
| 848 |
|
| 849 if (saved_status == NULL) |
|
| 850 { |
|
| 851 /* |
|
| 852 * We don't have a status to apply. |
|
| 853 * This may be the first login, or the user wants to |
|
| 854 * restore the "current" status. |
|
| 855 */ |
|
| 856 saved_status = gaim_savedstatus_get_current(); |
|
| 857 } |
|
| 858 |
|
| 859 return saved_status; |
|
| 860 } |
|
| 861 |
|
| 862 |
|
| 863 GaimSavedStatus * |
|
| 864 gaim_savedstatus_find(const char *title) |
|
| 865 { |
|
| 866 GList *iter; |
|
| 867 GaimSavedStatus *status; |
|
| 868 |
|
| 869 g_return_val_if_fail(title != NULL, NULL); |
|
| 870 |
|
| 871 for (iter = saved_statuses; iter != NULL; iter = iter->next) |
|
| 872 { |
|
| 873 status = (GaimSavedStatus *)iter->data; |
|
| 874 if ((status->title != NULL) && !strcmp(status->title, title)) |
|
| 875 return status; |
|
| 876 } |
|
| 877 |
|
| 878 return NULL; |
|
| 879 } |
|
| 880 |
|
| 881 GaimSavedStatus * |
|
| 882 gaim_savedstatus_find_by_creation_time(time_t creation_time) |
|
| 883 { |
|
| 884 GList *iter; |
|
| 885 GaimSavedStatus *status; |
|
| 886 |
|
| 887 for (iter = saved_statuses; iter != NULL; iter = iter->next) |
|
| 888 { |
|
| 889 status = (GaimSavedStatus *)iter->data; |
|
| 890 if (status->creation_time == creation_time) |
|
| 891 return status; |
|
| 892 } |
|
| 893 |
|
| 894 return NULL; |
|
| 895 } |
|
| 896 |
|
| 897 GaimSavedStatus * |
|
| 898 gaim_savedstatus_find_transient_by_type_and_message(GaimStatusPrimitive type, |
|
| 899 const char *message) |
|
| 900 { |
|
| 901 GList *iter; |
|
| 902 GaimSavedStatus *status; |
|
| 903 |
|
| 904 for (iter = saved_statuses; iter != NULL; iter = iter->next) |
|
| 905 { |
|
| 906 status = (GaimSavedStatus *)iter->data; |
|
| 907 if ((status->type == type) && gaim_savedstatus_is_transient(status) && |
|
| 908 (((status->message == NULL) && (message == NULL)) || |
|
| 909 ((status->message != NULL) && (message != NULL) && !strcmp(status->message, message)))) |
|
| 910 { |
|
| 911 return status; |
|
| 912 } |
|
| 913 } |
|
| 914 |
|
| 915 return NULL; |
|
| 916 } |
|
| 917 |
|
| 918 gboolean |
|
| 919 gaim_savedstatus_is_transient(const GaimSavedStatus *saved_status) |
|
| 920 { |
|
| 921 g_return_val_if_fail(saved_status != NULL, TRUE); |
|
| 922 |
|
| 923 return (saved_status->title == NULL); |
|
| 924 } |
|
| 925 |
|
| 926 const char * |
|
| 927 gaim_savedstatus_get_title(const GaimSavedStatus *saved_status) |
|
| 928 { |
|
| 929 const char *message; |
|
| 930 |
|
| 931 g_return_val_if_fail(saved_status != NULL, NULL); |
|
| 932 |
|
| 933 /* If we have a title then return it */ |
|
| 934 if (saved_status->title != NULL) |
|
| 935 return saved_status->title; |
|
| 936 |
|
| 937 /* Otherwise, this is a transient status and we make up a title on the fly */ |
|
| 938 message = gaim_savedstatus_get_message(saved_status); |
|
| 939 |
|
| 940 if ((message == NULL) || (*message == '\0')) |
|
| 941 { |
|
| 942 GaimStatusPrimitive primitive; |
|
| 943 primitive = gaim_savedstatus_get_type(saved_status); |
|
| 944 return gaim_primitive_get_name_from_type(primitive); |
|
| 945 } |
|
| 946 else |
|
| 947 { |
|
| 948 char *stripped; |
|
| 949 static char buf[64]; |
|
| 950 stripped = gaim_markup_strip_html(message); |
|
| 951 gaim_util_chrreplace(stripped, '\n', ' '); |
|
| 952 strncpy(buf, stripped, sizeof(buf)); |
|
| 953 buf[sizeof(buf) - 1] = '\0'; |
|
| 954 if ((strlen(stripped) + 1) > sizeof(buf)) |
|
| 955 { |
|
| 956 /* Truncate and ellipsize */ |
|
| 957 char *tmp = g_utf8_find_prev_char(buf, &buf[sizeof(buf) - 4]); |
|
| 958 strcpy(tmp, "..."); |
|
| 959 } |
|
| 960 g_free(stripped); |
|
| 961 return buf; |
|
| 962 } |
|
| 963 } |
|
| 964 |
|
| 965 GaimStatusPrimitive |
|
| 966 gaim_savedstatus_get_type(const GaimSavedStatus *saved_status) |
|
| 967 { |
|
| 968 g_return_val_if_fail(saved_status != NULL, GAIM_STATUS_OFFLINE); |
|
| 969 |
|
| 970 return saved_status->type; |
|
| 971 } |
|
| 972 |
|
| 973 const char * |
|
| 974 gaim_savedstatus_get_message(const GaimSavedStatus *saved_status) |
|
| 975 { |
|
| 976 g_return_val_if_fail(saved_status != NULL, NULL); |
|
| 977 |
|
| 978 return saved_status->message; |
|
| 979 } |
|
| 980 |
|
| 981 time_t |
|
| 982 gaim_savedstatus_get_creation_time(const GaimSavedStatus *saved_status) |
|
| 983 { |
|
| 984 g_return_val_if_fail(saved_status != NULL, 0); |
|
| 985 |
|
| 986 return saved_status->creation_time; |
|
| 987 } |
|
| 988 |
|
| 989 gboolean |
|
| 990 gaim_savedstatus_has_substatuses(const GaimSavedStatus *saved_status) |
|
| 991 { |
|
| 992 g_return_val_if_fail(saved_status != NULL, FALSE); |
|
| 993 |
|
| 994 return (saved_status->substatuses != NULL); |
|
| 995 } |
|
| 996 |
|
| 997 GaimSavedStatusSub * |
|
| 998 gaim_savedstatus_get_substatus(const GaimSavedStatus *saved_status, |
|
| 999 const GaimAccount *account) |
|
| 1000 { |
|
| 1001 GList *iter; |
|
| 1002 GaimSavedStatusSub *substatus; |
|
| 1003 |
|
| 1004 g_return_val_if_fail(saved_status != NULL, NULL); |
|
| 1005 g_return_val_if_fail(account != NULL, NULL); |
|
| 1006 |
|
| 1007 for (iter = saved_status->substatuses; iter != NULL; iter = iter->next) |
|
| 1008 { |
|
| 1009 substatus = iter->data; |
|
| 1010 if (substatus->account == account) |
|
| 1011 return substatus; |
|
| 1012 } |
|
| 1013 |
|
| 1014 return NULL; |
|
| 1015 } |
|
| 1016 |
|
| 1017 const GaimStatusType * |
|
| 1018 gaim_savedstatus_substatus_get_type(const GaimSavedStatusSub *substatus) |
|
| 1019 { |
|
| 1020 g_return_val_if_fail(substatus != NULL, NULL); |
|
| 1021 |
|
| 1022 return substatus->type; |
|
| 1023 } |
|
| 1024 |
|
| 1025 const char * |
|
| 1026 gaim_savedstatus_substatus_get_message(const GaimSavedStatusSub *substatus) |
|
| 1027 { |
|
| 1028 g_return_val_if_fail(substatus != NULL, NULL); |
|
| 1029 |
|
| 1030 return substatus->message; |
|
| 1031 } |
|
| 1032 |
|
| 1033 void |
|
| 1034 gaim_savedstatus_activate(GaimSavedStatus *saved_status) |
|
| 1035 { |
|
| 1036 GList *accounts, *node; |
|
| 1037 GaimSavedStatus *old = gaim_savedstatus_get_current(); |
|
| 1038 |
|
| 1039 g_return_if_fail(saved_status != NULL); |
|
| 1040 |
|
| 1041 /* Make sure our list of saved statuses remains sorted */ |
|
| 1042 saved_status->lastused = time(NULL); |
|
| 1043 saved_status->usage_count++; |
|
| 1044 saved_statuses = g_list_remove(saved_statuses, saved_status); |
|
| 1045 saved_statuses = g_list_insert_sorted(saved_statuses, saved_status, saved_statuses_sort_func); |
|
| 1046 |
|
| 1047 accounts = gaim_accounts_get_all_active(); |
|
| 1048 for (node = accounts; node != NULL; node = node->next) |
|
| 1049 { |
|
| 1050 GaimAccount *account; |
|
| 1051 |
|
| 1052 account = node->data; |
|
| 1053 |
|
| 1054 gaim_savedstatus_activate_for_account(saved_status, account); |
|
| 1055 } |
|
| 1056 |
|
| 1057 g_list_free(accounts); |
|
| 1058 |
|
| 1059 gaim_prefs_set_int("/core/savedstatus/default", |
|
| 1060 gaim_savedstatus_get_creation_time(saved_status)); |
|
| 1061 gaim_savedstatus_set_idleaway(FALSE); |
|
| 1062 |
|
| 1063 gaim_signal_emit(gaim_savedstatuses_get_handle(), "savedstatus-changed", |
|
| 1064 saved_status, old); |
|
| 1065 } |
|
| 1066 |
|
| 1067 void |
|
| 1068 gaim_savedstatus_activate_for_account(const GaimSavedStatus *saved_status, |
|
| 1069 GaimAccount *account) |
|
| 1070 { |
|
| 1071 const GaimStatusType *status_type; |
|
| 1072 const GaimSavedStatusSub *substatus; |
|
| 1073 const char *message = NULL; |
|
| 1074 |
|
| 1075 g_return_if_fail(saved_status != NULL); |
|
| 1076 g_return_if_fail(account != NULL); |
|
| 1077 |
|
| 1078 substatus = gaim_savedstatus_get_substatus(saved_status, account); |
|
| 1079 if (substatus != NULL) |
|
| 1080 { |
|
| 1081 status_type = substatus->type; |
|
| 1082 message = substatus->message; |
|
| 1083 } |
|
| 1084 else |
|
| 1085 { |
|
| 1086 status_type = gaim_account_get_status_type_with_primitive(account, saved_status->type); |
|
| 1087 if (status_type == NULL) |
|
| 1088 return; |
|
| 1089 message = saved_status->message; |
|
| 1090 } |
|
| 1091 |
|
| 1092 if ((message != NULL) && |
|
| 1093 (gaim_status_type_get_attr(status_type, "message"))) |
|
| 1094 { |
|
| 1095 gaim_account_set_status(account, gaim_status_type_get_id(status_type), |
|
| 1096 TRUE, "message", message, NULL); |
|
| 1097 } |
|
| 1098 else |
|
| 1099 { |
|
| 1100 gaim_account_set_status(account, gaim_status_type_get_id(status_type), |
|
| 1101 TRUE, NULL); |
|
| 1102 } |
|
| 1103 } |
|
| 1104 |
|
| 1105 void * |
|
| 1106 gaim_savedstatuses_get_handle(void) |
|
| 1107 { |
|
| 1108 static int handle; |
|
| 1109 |
|
| 1110 return &handle; |
|
| 1111 } |
|
| 1112 |
|
| 1113 void |
|
| 1114 gaim_savedstatuses_init(void) |
|
| 1115 { |
|
| 1116 void *handle = gaim_savedstatuses_get_handle(); |
|
| 1117 |
|
| 1118 creation_times = g_hash_table_new(g_int_hash, g_int_equal); |
|
| 1119 |
|
| 1120 /* |
|
| 1121 * Using 0 as the creation_time is a special case. |
|
| 1122 * If someone calls gaim_savedstatus_get_current() or |
|
| 1123 * gaim_savedstatus_get_idleaway() and either of those functions |
|
| 1124 * sees a creation_time of 0, then it will create a default |
|
| 1125 * saved status and return that to the user. |
|
| 1126 */ |
|
| 1127 gaim_prefs_add_none("/core/savedstatus"); |
|
| 1128 gaim_prefs_add_int("/core/savedstatus/default", 0); |
|
| 1129 gaim_prefs_add_int("/core/savedstatus/startup", 0); |
|
| 1130 gaim_prefs_add_bool("/core/savedstatus/startup_current_status", TRUE); |
|
| 1131 gaim_prefs_add_int("/core/savedstatus/idleaway", 0); |
|
| 1132 gaim_prefs_add_bool("/core/savedstatus/isidleaway", FALSE); |
|
| 1133 |
|
| 1134 load_statuses(); |
|
| 1135 |
|
| 1136 gaim_signal_register(handle, "savedstatus-changed", |
|
| 1137 gaim_marshal_VOID__POINTER_POINTER, NULL, 2, |
|
| 1138 gaim_value_new(GAIM_TYPE_SUBTYPE, |
|
| 1139 GAIM_SUBTYPE_SAVEDSTATUS), |
|
| 1140 gaim_value_new(GAIM_TYPE_SUBTYPE, |
|
| 1141 GAIM_SUBTYPE_SAVEDSTATUS)); |
|
| 1142 } |
|
| 1143 |
|
| 1144 void |
|
| 1145 gaim_savedstatuses_uninit(void) |
|
| 1146 { |
|
| 1147 remove_old_transient_statuses(); |
|
| 1148 |
|
| 1149 if (save_timer != 0) |
|
| 1150 { |
|
| 1151 gaim_timeout_remove(save_timer); |
|
| 1152 save_timer = 0; |
|
| 1153 sync_statuses(); |
|
| 1154 } |
|
| 1155 |
|
| 1156 while (saved_statuses != NULL) { |
|
| 1157 GaimSavedStatus *saved_status = saved_statuses->data; |
|
| 1158 saved_statuses = g_list_remove(saved_statuses, saved_status); |
|
| 1159 free_saved_status(saved_status); |
|
| 1160 } |
|
| 1161 |
|
| 1162 g_hash_table_destroy(creation_times); |
|
| 1163 |
|
| 1164 gaim_signals_unregister_by_instance(gaim_savedstatuses_get_handle()); |
|
| 1165 } |
|
| 1166 |
|