Mercurial > pidgin
annotate src/savedstatuses.c @ 12986:4e9935a539db
[gaim-migrate @ 15339]
We need to figure this out so that things don't break again. The new plugin
dependency unload code assumed that all plugins would have ids set, this was
true for most plugins, but both the perl and tcl loaders didn't set id for
perl and tcl plugins. And I didn't see any code in my quick looking which
actually verified (m)any parts of the struct.
committer: Tailor Script <tailor@pidgin.im>
| author | Etan Reisner <pidgin@unreliablesource.net> |
|---|---|
| date | Sun, 22 Jan 2006 07:09:06 +0000 |
| parents | e5f780a6137b |
| children | e3b9c6c7bcf6 |
| 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; | |
| 184 GaimSavedStatus *saved_status; | |
| 12724 | 185 int count; |
| 186 time_t creation_time; | |
| 12327 | 187 |
| 12724 | 188 /* |
| 189 * Iterate through the list of saved statuses. Delete all | |
| 190 * transient statuses except for the first MAX_TRANSIENTS | |
| 191 * (remember, the saved statuses are already sorted by popularity). | |
| 192 */ | |
| 193 count = 0; | |
| 12327 | 194 for (l = saved_statuses; l != NULL; l = next) |
| 195 { | |
| 196 next = l->next; | |
| 197 saved_status = l->data; | |
| 12724 | 198 if (gaim_savedstatus_is_transient(saved_status)) |
| 12327 | 199 { |
| 12724 | 200 if (count == MAX_TRANSIENTS) |
| 201 { | |
| 202 saved_statuses = g_list_remove(saved_statuses, saved_status); | |
| 203 creation_time = gaim_savedstatus_get_creation_time(saved_status); | |
| 204 g_hash_table_remove(creation_times, &creation_time); | |
| 205 free_saved_status(saved_status); | |
| 206 } | |
| 207 else | |
| 208 count++; | |
| 12327 | 209 } |
| 210 } | |
| 12724 | 211 |
| 212 if (count == MAX_TRANSIENTS) | |
| 213 schedule_save(); | |
| 12327 | 214 } |
| 215 | |
| 10428 | 216 /********************************************************************* |
| 10429 | 217 * Writing to disk * |
| 10428 | 218 *********************************************************************/ |
| 10418 | 219 |
| 220 static xmlnode * | |
| 10419 | 221 substatus_to_xmlnode(GaimSavedStatusSub *substatus) |
| 10418 | 222 { |
| 223 xmlnode *node, *child; | |
| 224 | |
| 225 node = xmlnode_new("substatus"); | |
| 226 | |
| 10424 | 227 child = xmlnode_new_child(node, "account"); |
| 228 xmlnode_set_attrib(child, "protocol", gaim_account_get_protocol_id(substatus->account)); | |
| 229 xmlnode_insert_data(child, gaim_account_get_username(substatus->account), -1); | |
| 10418 | 230 |
| 10424 | 231 child = xmlnode_new_child(node, "state"); |
| 10418 | 232 xmlnode_insert_data(child, gaim_status_type_get_id(substatus->type), -1); |
| 233 | |
| 234 if (substatus->message != NULL) | |
| 235 { | |
| 10424 | 236 child = xmlnode_new_child(node, "message"); |
| 10418 | 237 xmlnode_insert_data(child, substatus->message, -1); |
| 238 } | |
| 239 | |
| 240 return node; | |
| 241 } | |
| 242 | |
| 243 static xmlnode * | |
| 10419 | 244 status_to_xmlnode(GaimSavedStatus *status) |
| 10418 | 245 { |
| 246 xmlnode *node, *child; | |
| 12125 | 247 char buf[21]; |
| 10418 | 248 GList *cur; |
| 249 | |
| 12125 | 250 node = xmlnode_new("status"); |
| 251 if (status->title != NULL) | |
| 12283 | 252 { |
| 12125 | 253 xmlnode_set_attrib(node, "name", status->title); |
| 12283 | 254 } |
| 255 else | |
| 256 { | |
| 257 /* | |
| 258 * Gaim 1.5.0 and earlier require a name to be set, so we | |
| 259 * do this little hack to maintain backward compatability | |
| 12309 | 260 * in the status.xml file. Eventually this should be removed |
| 12283 | 261 * and we should determine if a status is transient by |
| 262 * whether the "name" attribute is set to something or if | |
| 263 * it does not exist at all. | |
| 264 */ | |
| 265 xmlnode_set_attrib(node, "name", "Auto-Cached"); | |
| 266 xmlnode_set_attrib(node, "transient", "true"); | |
| 267 } | |
| 11651 | 268 |
| 12125 | 269 snprintf(buf, sizeof(buf), "%lu", status->creation_time); |
| 270 xmlnode_set_attrib(node, "created", buf); | |
| 271 | |
| 272 snprintf(buf, sizeof(buf), "%lu", status->lastused); | |
| 273 xmlnode_set_attrib(node, "lastused", buf); | |
| 10418 | 274 |
| 12688 | 275 snprintf(buf, sizeof(buf), "%u", status->usage_count); |
| 276 xmlnode_set_attrib(node, "usage_count", buf); | |
| 277 | |
| 10424 | 278 child = xmlnode_new_child(node, "state"); |
| 279 xmlnode_insert_data(child, gaim_primitive_get_id_from_type(status->type), -1); | |
| 10418 | 280 |
| 11651 | 281 if (status->message != NULL) |
| 282 { | |
| 283 child = xmlnode_new_child(node, "message"); | |
| 284 xmlnode_insert_data(child, status->message, -1); | |
| 285 } | |
| 10418 | 286 |
| 287 for (cur = status->substatuses; cur != NULL; cur = cur->next) | |
| 288 { | |
| 289 child = substatus_to_xmlnode(cur->data); | |
| 290 xmlnode_insert_child(node, child); | |
| 291 } | |
| 292 | |
| 293 return node; | |
| 294 } | |
| 295 | |
| 296 static xmlnode * | |
| 297 statuses_to_xmlnode(void) | |
| 298 { | |
| 299 xmlnode *node, *child; | |
| 300 GList *cur; | |
| 301 | |
| 302 node = xmlnode_new("statuses"); | |
| 10423 | 303 xmlnode_set_attrib(node, "version", "1.0"); |
| 10418 | 304 |
| 305 for (cur = saved_statuses; cur != NULL; cur = cur->next) | |
| 306 { | |
| 307 child = status_to_xmlnode(cur->data); | |
| 308 xmlnode_insert_child(node, child); | |
| 309 } | |
| 310 | |
| 311 return node; | |
| 312 } | |
| 313 | |
| 314 static void | |
| 315 sync_statuses(void) | |
| 316 { | |
| 10423 | 317 xmlnode *node; |
| 10418 | 318 char *data; |
| 319 | |
| 10428 | 320 if (!statuses_loaded) |
| 321 { | |
| 10418 | 322 gaim_debug_error("status", "Attempted to save statuses before they " |
| 323 "were read!\n"); | |
| 324 return; | |
| 325 } | |
| 326 | |
| 10423 | 327 node = statuses_to_xmlnode(); |
| 328 data = xmlnode_to_formatted_str(node, NULL); | |
| 10418 | 329 gaim_util_write_data_to_file("status.xml", data, -1); |
| 330 g_free(data); | |
| 10423 | 331 xmlnode_free(node); |
| 10418 | 332 } |
| 333 | |
| 334 static gboolean | |
| 10428 | 335 save_cb(gpointer data) |
| 10418 | 336 { |
| 337 sync_statuses(); | |
| 10428 | 338 save_timer = 0; |
| 10418 | 339 return FALSE; |
| 340 } | |
| 341 | |
| 342 static void | |
| 343 schedule_save(void) | |
| 344 { | |
| 10428 | 345 if (save_timer == 0) |
| 346 save_timer = gaim_timeout_add(5000, save_cb, NULL); | |
| 10418 | 347 } |
| 348 | |
| 349 | |
| 10428 | 350 /********************************************************************* |
| 351 * Reading from disk * | |
| 352 *********************************************************************/ | |
| 353 | |
| 10419 | 354 static GaimSavedStatusSub * |
| 10418 | 355 parse_substatus(xmlnode *substatus) |
| 356 { | |
| 10419 | 357 GaimSavedStatusSub *ret; |
| 10418 | 358 xmlnode *node; |
| 10425 | 359 char *data; |
| 10418 | 360 |
| 10419 | 361 ret = g_new0(GaimSavedStatusSub, 1); |
| 10418 | 362 |
| 363 /* Read the account */ | |
| 364 node = xmlnode_get_child(substatus, "account"); | |
| 365 if (node != NULL) | |
| 366 { | |
| 367 char *acct_name; | |
| 368 const char *protocol; | |
| 369 acct_name = xmlnode_get_data(node); | |
| 370 protocol = xmlnode_get_attrib(node, "protocol"); | |
| 371 if ((acct_name != NULL) && (protocol != NULL)) | |
| 372 ret->account = gaim_accounts_find(acct_name, protocol); | |
| 373 g_free(acct_name); | |
| 374 } | |
| 375 | |
| 376 if (ret->account == NULL) | |
| 377 { | |
| 378 g_free(ret); | |
| 379 return NULL; | |
| 380 } | |
| 381 | |
| 382 /* Read the state */ | |
| 383 node = xmlnode_get_child(substatus, "state"); | |
| 10426 | 384 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL)) |
| 10425 | 385 { |
| 10418 | 386 ret->type = gaim_status_type_find_with_id( |
| 10425 | 387 ret->account->status_types, data); |
| 10418 | 388 g_free(data); |
| 389 } | |
| 390 | |
| 391 /* Read the message */ | |
| 392 node = xmlnode_get_child(substatus, "message"); | |
| 10426 | 393 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL)) |
| 10425 | 394 { |
| 10418 | 395 ret->message = data; |
| 10425 | 396 } |
| 10418 | 397 |
| 398 return ret; | |
| 399 } | |
| 400 | |
| 401 /** | |
| 402 * Parse a saved status and add it to the saved_statuses linked list. | |
| 403 * | |
| 404 * Here's an example of the XML for a saved status: | |
| 405 * <status name="Girls"> | |
| 406 * <state>away</state> | |
| 407 * <message>I like the way that they walk | |
| 408 * And it's chill to hear them talk | |
| 409 * And I can always make them smile | |
| 410 * From White Castle to the Nile</message> | |
| 411 * <substatus> | |
| 412 * <account protocol='prpl-oscar'>markdoliner</account> | |
| 413 * <state>available</state> | |
| 414 * <message>The ladies man is here to answer your queries.</message> | |
| 415 * </substatus> | |
| 416 * <substatus> | |
| 417 * <account protocol='prpl-oscar'>giantgraypanda</account> | |
| 418 * <state>away</state> | |
| 419 * <message>A.C. ain't in charge no more.</message> | |
| 420 * </substatus> | |
| 421 * </status> | |
| 422 * | |
| 423 * I know. Moving, huh? | |
| 424 */ | |
| 10419 | 425 static GaimSavedStatus * |
| 10418 | 426 parse_status(xmlnode *status) |
| 427 { | |
| 10419 | 428 GaimSavedStatus *ret; |
| 10418 | 429 xmlnode *node; |
| 430 const char *attrib; | |
| 10425 | 431 char *data; |
| 10418 | 432 int i; |
| 433 | |
| 10419 | 434 ret = g_new0(GaimSavedStatus, 1); |
| 10418 | 435 |
| 12283 | 436 attrib = xmlnode_get_attrib(status, "transient"); |
| 437 if ((attrib == NULL) || (strcmp(attrib, "true"))) | |
| 438 { | |
| 439 /* Read the title */ | |
| 440 attrib = xmlnode_get_attrib(status, "name"); | |
| 441 ret->title = g_strdup(attrib); | |
| 442 } | |
| 12125 | 443 |
| 444 if (ret->title != NULL) | |
| 10418 | 445 { |
| 12125 | 446 /* Ensure the title is unique */ |
| 447 i = 2; | |
| 448 while (gaim_savedstatus_find(ret->title) != NULL) | |
| 449 { | |
| 450 g_free(ret->title); | |
| 451 ret->title = g_strdup_printf("%s %d", attrib, i); | |
| 452 i++; | |
| 453 } | |
| 10418 | 454 } |
| 455 | |
| 12125 | 456 /* Read the creation time */ |
| 457 attrib = xmlnode_get_attrib(status, "created"); | |
| 458 set_creation_time(ret, (attrib != NULL ? atol(attrib) : 0)); | |
| 459 | |
| 460 /* Read the last used time */ | |
| 461 attrib = xmlnode_get_attrib(status, "lastused"); | |
| 462 ret->lastused = (attrib != NULL ? atol(attrib) : 0); | |
| 463 | |
| 12688 | 464 /* Read the usage count */ |
| 465 attrib = xmlnode_get_attrib(status, "usage_count"); | |
| 466 ret->usage_count = (attrib != NULL ? atol(attrib) : 0); | |
| 467 | |
| 10418 | 468 /* Read the primitive status type */ |
| 469 node = xmlnode_get_child(status, "state"); | |
| 10426 | 470 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL)) |
| 10425 | 471 { |
| 10419 | 472 ret->type = gaim_primitive_get_type_from_id(data); |
| 10418 | 473 g_free(data); |
| 474 } | |
| 475 | |
| 476 /* Read the message */ | |
| 477 node = xmlnode_get_child(status, "message"); | |
| 10426 | 478 if ((node != NULL) && ((data = xmlnode_get_data(node)) != NULL)) |
| 10425 | 479 { |
| 10418 | 480 ret->message = data; |
| 10425 | 481 } |
| 10418 | 482 |
| 483 /* Read substatuses */ | |
| 12056 | 484 for (node = xmlnode_get_child(status, "substatus"); node != NULL; |
| 10418 | 485 node = xmlnode_get_next_twin(node)) |
| 486 { | |
| 10419 | 487 GaimSavedStatusSub *new; |
| 10418 | 488 new = parse_substatus(node); |
| 489 if (new != NULL) | |
| 12056 | 490 ret->substatuses = g_list_prepend(ret->substatuses, new); |
| 10418 | 491 } |
| 492 | |
| 493 return ret; | |
| 494 } | |
| 495 | |
| 496 /** | |
| 497 * Read the saved statuses from a file in the Gaim user dir. | |
| 498 * | |
| 499 * @return TRUE on success, FALSE on failure (if the file can not | |
| 500 * be opened, or if it contains invalid XML). | |
| 501 */ | |
| 10425 | 502 static void |
| 503 load_statuses(void) | |
| 10418 | 504 { |
| 505 xmlnode *statuses, *status; | |
| 506 | |
| 10426 | 507 statuses_loaded = TRUE; |
| 508 | |
| 10425 | 509 statuses = gaim_util_read_xml_from_file("status.xml", _("saved statuses")); |
| 10418 | 510 |
| 511 if (statuses == NULL) | |
| 10425 | 512 return; |
| 10418 | 513 |
| 514 for (status = xmlnode_get_child(statuses, "status"); status != NULL; | |
| 515 status = xmlnode_get_next_twin(status)) | |
| 516 { | |
| 10419 | 517 GaimSavedStatus *new; |
| 10418 | 518 new = parse_status(status); |
| 12056 | 519 saved_statuses = g_list_prepend(saved_statuses, new); |
| 10418 | 520 } |
| 12724 | 521 saved_statuses = g_list_sort(saved_statuses, saved_statuses_sort_func); |
| 10418 | 522 |
| 523 xmlnode_free(statuses); | |
| 524 } | |
| 525 | |
| 526 | |
| 527 /************************************************************************** | |
| 528 * Saved status API | |
| 529 **************************************************************************/ | |
| 10419 | 530 GaimSavedStatus * |
| 531 gaim_savedstatus_new(const char *title, GaimStatusPrimitive type) | |
| 10418 | 532 { |
| 10419 | 533 GaimSavedStatus *status; |
| 10418 | 534 |
| 12056 | 535 /* Make sure we don't already have a saved status with this title. */ |
| 12125 | 536 if (title != NULL) |
| 537 g_return_val_if_fail(gaim_savedstatus_find(title) == NULL, NULL); | |
| 10420 | 538 |
| 10419 | 539 status = g_new0(GaimSavedStatus, 1); |
| 10418 | 540 status->title = g_strdup(title); |
| 541 status->type = type; | |
| 12125 | 542 set_creation_time(status, time(NULL)); |
| 10418 | 543 |
| 12724 | 544 saved_statuses = g_list_insert_sorted(saved_statuses, status, saved_statuses_sort_func); |
| 10418 | 545 |
| 546 schedule_save(); | |
| 547 | |
| 548 return status; | |
| 549 } | |
| 550 | |
| 10420 | 551 void |
| 12056 | 552 gaim_savedstatus_set_title(GaimSavedStatus *status, const char *title) |
| 553 { | |
| 554 g_return_if_fail(status != NULL); | |
| 555 | |
| 556 /* Make sure we don't already have a saved status with this title. */ | |
| 557 g_return_if_fail(gaim_savedstatus_find(title) == NULL); | |
| 558 | |
| 559 g_free(status->title); | |
| 560 status->title = g_strdup(title); | |
| 561 | |
| 562 schedule_save(); | |
| 563 } | |
| 564 | |
| 565 void | |
| 11651 | 566 gaim_savedstatus_set_type(GaimSavedStatus *status, GaimStatusPrimitive type) |
| 567 { | |
| 568 g_return_if_fail(status != NULL); | |
| 569 | |
| 570 status->type = type; | |
| 571 | |
| 572 schedule_save(); | |
| 573 } | |
| 574 | |
| 575 void | |
| 10420 | 576 gaim_savedstatus_set_message(GaimSavedStatus *status, const char *message) |
| 577 { | |
| 578 g_return_if_fail(status != NULL); | |
| 579 | |
| 580 g_free(status->message); | |
| 581 status->message = g_strdup(message); | |
| 582 | |
| 583 schedule_save(); | |
| 584 } | |
| 585 | |
| 12056 | 586 void |
| 12080 | 587 gaim_savedstatus_set_substatus(GaimSavedStatus *saved_status, |
| 588 const GaimAccount *account, | |
| 589 const GaimStatusType *type, | |
| 590 const char *message) | |
| 12056 | 591 { |
| 592 GaimSavedStatusSub *substatus; | |
| 593 | |
| 594 g_return_if_fail(saved_status != NULL); | |
| 595 g_return_if_fail(account != NULL); | |
| 596 g_return_if_fail(type != NULL); | |
| 597 | |
| 598 /* Find an existing substatus or create a new one */ | |
| 12080 | 599 substatus = gaim_savedstatus_get_substatus(saved_status, account); |
| 12056 | 600 if (substatus == NULL) |
| 601 { | |
| 602 substatus = g_new0(GaimSavedStatusSub, 1); | |
| 603 substatus->account = (GaimAccount *)account; | |
| 604 saved_status->substatuses = g_list_prepend(saved_status->substatuses, substatus); | |
| 605 } | |
| 606 | |
| 607 substatus->type = type; | |
| 608 g_free(substatus->message); | |
| 609 substatus->message = g_strdup(message); | |
| 610 | |
| 611 schedule_save(); | |
| 612 } | |
| 613 | |
| 614 void | |
| 12080 | 615 gaim_savedstatus_unset_substatus(GaimSavedStatus *saved_status, |
| 616 const GaimAccount *account) | |
| 12056 | 617 { |
| 618 GList *iter; | |
| 619 GaimSavedStatusSub *substatus; | |
| 620 | |
| 621 g_return_if_fail(saved_status != NULL); | |
| 622 g_return_if_fail(account != NULL); | |
| 623 | |
| 624 for (iter = saved_status->substatuses; iter != NULL; iter = iter->next) | |
| 625 { | |
| 626 substatus = iter->data; | |
| 627 if (substatus->account == account) | |
| 628 { | |
| 629 saved_status->substatuses = g_list_delete_link(saved_status->substatuses, iter); | |
| 630 g_free(substatus->message); | |
| 631 g_free(substatus); | |
| 632 return; | |
| 633 } | |
| 634 } | |
| 635 } | |
| 636 | |
| 10418 | 637 gboolean |
| 10419 | 638 gaim_savedstatus_delete(const char *title) |
| 10418 | 639 { |
| 10419 | 640 GaimSavedStatus *status; |
| 12125 | 641 time_t creation_time, current, idleaway; |
| 10418 | 642 |
| 10419 | 643 status = gaim_savedstatus_find(title); |
| 10418 | 644 |
| 645 if (status == NULL) | |
| 646 return FALSE; | |
| 647 | |
| 648 saved_statuses = g_list_remove(saved_statuses, status); | |
| 12125 | 649 creation_time = gaim_savedstatus_get_creation_time(status); |
| 650 g_hash_table_remove(creation_times, &creation_time); | |
| 12724 | 651 free_saved_status(status); |
| 10418 | 652 |
| 653 schedule_save(); | |
| 654 | |
| 12125 | 655 /* |
| 656 * If we just deleted our current status or our idleaway status, | |
| 657 * then set the appropriate pref back to 0. | |
| 658 */ | |
| 659 current = gaim_prefs_get_int("/core/savedstatus/current"); | |
| 660 if (current == creation_time) | |
| 661 gaim_prefs_set_int("/core/savedstatus/current", 0); | |
| 662 | |
| 663 idleaway = gaim_prefs_get_int("/core/savedstatus/idleaway"); | |
| 664 if (idleaway == creation_time) | |
| 665 gaim_prefs_set_int("/core/savedstatus/idleaway", 0); | |
| 666 | |
| 10418 | 667 return TRUE; |
| 668 } | |
| 669 | |
| 670 const GList * | |
| 671 gaim_savedstatuses_get_all(void) | |
| 672 { | |
| 673 return saved_statuses; | |
| 674 } | |
| 675 | |
| 12688 | 676 GList * |
| 677 gaim_savedstatuses_get_popular(unsigned int how_many) | |
| 678 { | |
| 679 GList *truncated = NULL; | |
| 680 GList *cur; | |
| 681 int i; | |
| 682 | |
| 683 /* Copy 'how_many' elements to a new list */ | |
| 12724 | 684 for (i = 0, cur = saved_statuses; (i < how_many) && (cur != NULL); i++) |
| 12688 | 685 { |
| 686 truncated = g_list_append(truncated, cur->data); | |
| 687 cur = cur->next; | |
| 688 } | |
| 689 | |
| 690 return truncated; | |
| 691 } | |
| 692 | |
| 10419 | 693 GaimSavedStatus * |
|
12857
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
694 gaim_savedstatus_get_startup() |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
695 { |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
696 int creation_time; |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
697 GaimSavedStatus *saved_status = NULL; |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
698 |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
699 creation_time = gaim_prefs_get_int("/core/savedstatus/startup"); |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
700 |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
701 if (creation_time != 0) |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
702 saved_status = g_hash_table_lookup(creation_times, &creation_time); |
|
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 if (saved_status == NULL) |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
705 { |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
706 /* We don't have a status to apply. |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
707 * 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
|
708 * restore the "current" status */ |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
709 saved_status = gaim_savedstatus_get_current(); |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
710 } |
|
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 return saved_status; |
|
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 |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
715 |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
716 GaimSavedStatus * |
| 12125 | 717 gaim_savedstatus_get_current() |
| 718 { | |
| 719 int creation_time; | |
| 12197 | 720 GaimSavedStatus *saved_status = NULL; |
| 12125 | 721 |
| 722 creation_time = gaim_prefs_get_int("/core/savedstatus/current"); | |
| 723 | |
| 12197 | 724 if (creation_time != 0) |
| 725 saved_status = g_hash_table_lookup(creation_times, &creation_time); | |
| 726 | |
| 727 if (saved_status == NULL) | |
| 12125 | 728 { |
| 729 /* | |
| 730 * We don't have a current saved statuses! This is either a new | |
| 12197 | 731 * Gaim user or someone upgrading from Gaim 1.5.0 or older, or |
| 732 * possibly someone who deleted the status they were currently | |
| 733 * using? In any case, add a default status. | |
| 12125 | 734 */ |
| 735 saved_status = gaim_savedstatus_new(NULL, GAIM_STATUS_AVAILABLE); | |
| 736 } | |
| 737 | |
| 738 return saved_status; | |
| 739 } | |
| 740 | |
| 741 GaimSavedStatus * | |
| 742 gaim_savedstatus_get_idleaway() | |
| 743 { | |
| 744 int creation_time; | |
| 745 GaimSavedStatus *saved_status; | |
| 746 | |
| 747 creation_time = gaim_prefs_get_int("/core/savedstatus/idleaway"); | |
| 748 | |
| 749 if (creation_time == 0) | |
| 750 { | |
| 751 /* | |
| 752 * We don't have a current saved statuses! This is either a new | |
| 753 * Gaim user or someone upgrading from Gaim 1.5.0 or older. Add | |
| 754 * a default status. | |
| 755 */ | |
| 756 saved_status = gaim_savedstatus_new(NULL, GAIM_STATUS_AWAY); | |
| 757 gaim_savedstatus_set_message(saved_status, _("I'm not here right now")); | |
| 758 } | |
| 759 else | |
| 760 { | |
| 761 saved_status = g_hash_table_lookup(creation_times, &creation_time); | |
| 762 } | |
| 763 | |
| 764 return saved_status; | |
| 765 } | |
| 766 | |
| 767 GaimSavedStatus * | |
| 10419 | 768 gaim_savedstatus_find(const char *title) |
| 10418 | 769 { |
| 12056 | 770 GList *iter; |
| 10419 | 771 GaimSavedStatus *status; |
| 10418 | 772 |
| 11977 | 773 g_return_val_if_fail(title != NULL, NULL); |
| 774 | |
| 12056 | 775 for (iter = saved_statuses; iter != NULL; iter = iter->next) |
| 10418 | 776 { |
| 12056 | 777 status = (GaimSavedStatus *)iter->data; |
| 12125 | 778 if ((status->title != NULL) && !strcmp(status->title, title)) |
| 10418 | 779 return status; |
| 780 } | |
| 781 | |
| 782 return NULL; | |
| 783 } | |
| 784 | |
| 12690 | 785 GaimSavedStatus * |
| 786 gaim_savedstatus_find_by_creation_time(time_t creation_time) | |
| 787 { | |
| 788 GList *iter; | |
| 789 GaimSavedStatus *status; | |
| 790 | |
| 791 for (iter = saved_statuses; iter != NULL; iter = iter->next) | |
| 792 { | |
| 793 status = (GaimSavedStatus *)iter->data; | |
| 794 if (status->creation_time == creation_time) | |
| 795 return status; | |
| 796 } | |
| 797 | |
| 798 return NULL; | |
| 799 } | |
| 800 | |
| 11651 | 801 gboolean |
| 802 gaim_savedstatus_is_transient(const GaimSavedStatus *saved_status) | |
| 803 { | |
| 12197 | 804 g_return_val_if_fail(saved_status != NULL, TRUE); |
| 805 | |
| 12125 | 806 return (saved_status->title == NULL); |
| 11651 | 807 } |
| 808 | |
| 10418 | 809 const char * |
| 10419 | 810 gaim_savedstatus_get_title(const GaimSavedStatus *saved_status) |
| 10418 | 811 { |
| 12690 | 812 const char *message; |
| 813 | |
| 12197 | 814 g_return_val_if_fail(saved_status != NULL, NULL); |
| 815 | |
| 12690 | 816 /* If we have a title then return it */ |
| 817 if (saved_status->title != NULL) | |
| 818 return saved_status->title; | |
| 819 | |
| 820 /* Otherwise, this is a transient status and we make up a title on the fly */ | |
| 821 message = gaim_savedstatus_get_message(saved_status); | |
| 12688 | 822 |
| 12781 | 823 if ((message == NULL) || (*message == '\0')) |
| 12690 | 824 { |
| 825 GaimStatusPrimitive primitive; | |
| 826 primitive = gaim_savedstatus_get_type(saved_status); | |
|
12814
f88f145884c0
[gaim-migrate @ 15162]
Richard Laager <rlaager@wiktel.com>
parents:
12781
diff
changeset
|
827 return gaim_primitive_get_name_from_type(primitive); |
| 12690 | 828 } |
| 829 else | |
| 830 { | |
| 831 char *stripped; | |
| 832 static char buf[64]; | |
| 833 stripped = gaim_markup_strip_html(message); | |
| 834 gaim_util_chrreplace(stripped, '\n', ' '); | |
| 835 strncpy(buf, stripped, sizeof(buf)); | |
| 836 buf[sizeof(buf) - 1] = '\0'; | |
| 837 if ((strlen(stripped) + 1) > sizeof(buf)) | |
| 12688 | 838 { |
| 12690 | 839 /* Truncate and ellipsize */ |
| 840 char *tmp = g_utf8_find_prev_char(buf, &buf[sizeof(buf) - 4]); | |
| 841 strcpy(tmp, "..."); | |
| 12688 | 842 } |
| 12690 | 843 g_free(stripped); |
| 844 return buf; | |
| 12688 | 845 } |
| 10418 | 846 } |
| 847 | |
| 848 GaimStatusPrimitive | |
| 10419 | 849 gaim_savedstatus_get_type(const GaimSavedStatus *saved_status) |
| 10418 | 850 { |
| 12197 | 851 g_return_val_if_fail(saved_status != NULL, GAIM_STATUS_OFFLINE); |
| 852 | |
| 10418 | 853 return saved_status->type; |
| 854 } | |
| 855 | |
| 856 const char * | |
| 10419 | 857 gaim_savedstatus_get_message(const GaimSavedStatus *saved_status) |
| 10418 | 858 { |
| 12197 | 859 g_return_val_if_fail(saved_status != NULL, NULL); |
| 860 | |
| 10418 | 861 return saved_status->message; |
| 862 } | |
| 863 | |
| 12125 | 864 time_t |
| 865 gaim_savedstatus_get_creation_time(const GaimSavedStatus *saved_status) | |
| 866 { | |
| 12197 | 867 g_return_val_if_fail(saved_status != NULL, 0); |
| 868 | |
| 12125 | 869 return saved_status->creation_time; |
| 870 } | |
| 871 | |
| 11651 | 872 gboolean |
| 873 gaim_savedstatus_has_substatuses(const GaimSavedStatus *saved_status) | |
| 874 { | |
| 12197 | 875 g_return_val_if_fail(saved_status != NULL, FALSE); |
| 876 | |
| 11651 | 877 return (saved_status->substatuses != NULL); |
| 878 } | |
| 879 | |
| 12056 | 880 GaimSavedStatusSub * |
| 12080 | 881 gaim_savedstatus_get_substatus(const GaimSavedStatus *saved_status, |
| 882 const GaimAccount *account) | |
| 12056 | 883 { |
| 884 GList *iter; | |
| 885 GaimSavedStatusSub *substatus; | |
| 886 | |
| 887 g_return_val_if_fail(saved_status != NULL, NULL); | |
| 888 g_return_val_if_fail(account != NULL, NULL); | |
| 889 | |
| 890 for (iter = saved_status->substatuses; iter != NULL; iter = iter->next) | |
| 891 { | |
| 892 substatus = iter->data; | |
| 893 if (substatus->account == account) | |
| 894 return substatus; | |
| 895 } | |
| 896 | |
| 897 return NULL; | |
| 898 } | |
| 899 | |
| 900 const GaimStatusType * | |
| 901 gaim_savedstatus_substatus_get_type(const GaimSavedStatusSub *substatus) | |
| 902 { | |
| 903 g_return_val_if_fail(substatus != NULL, NULL); | |
| 904 | |
| 905 return substatus->type; | |
| 906 } | |
| 907 | |
| 908 const char * | |
| 909 gaim_savedstatus_substatus_get_message(const GaimSavedStatusSub *substatus) | |
| 910 { | |
| 911 g_return_val_if_fail(substatus != NULL, NULL); | |
| 912 | |
| 913 return substatus->message; | |
| 914 } | |
| 915 | |
| 11724 | 916 void |
| 12125 | 917 gaim_savedstatus_activate(GaimSavedStatus *saved_status) |
| 11724 | 918 { |
| 11733 | 919 GList *accounts, *node; |
| 11724 | 920 |
| 11727 | 921 g_return_if_fail(saved_status != NULL); |
| 922 | |
| 12729 | 923 /* Make sure our list of saved statuses remains sorted */ |
| 924 saved_status->lastused = time(NULL); | |
| 925 saved_status->usage_count++; | |
| 926 saved_statuses = g_list_remove(saved_statuses, saved_status); | |
| 927 saved_statuses = g_list_insert_sorted(saved_statuses, saved_status, saved_statuses_sort_func); | |
| 928 | |
| 11724 | 929 accounts = gaim_accounts_get_all_active(); |
| 11733 | 930 for (node = accounts; node != NULL; node = node->next) |
| 11724 | 931 { |
| 932 GaimAccount *account; | |
| 933 | |
| 11733 | 934 account = node->data; |
|
12857
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
935 |
| 11724 | 936 gaim_savedstatus_activate_for_account(saved_status, account); |
| 937 } | |
| 11733 | 938 |
| 939 g_list_free(accounts); | |
| 11954 | 940 |
| 12125 | 941 gaim_prefs_set_int("/core/savedstatus/current", |
| 942 gaim_savedstatus_get_creation_time(saved_status)); | |
| 11724 | 943 } |
| 944 | |
| 945 void | |
| 946 gaim_savedstatus_activate_for_account(const GaimSavedStatus *saved_status, | |
| 947 GaimAccount *account) | |
| 948 { | |
| 12056 | 949 const GaimStatusType *status_type; |
| 950 const GaimSavedStatusSub *substatus; | |
| 951 const char *message = NULL; | |
| 11724 | 952 |
| 11727 | 953 g_return_if_fail(saved_status != NULL); |
| 954 g_return_if_fail(account != NULL); | |
| 955 | |
| 12080 | 956 substatus = gaim_savedstatus_get_substatus(saved_status, account); |
| 12056 | 957 if (substatus != NULL) |
| 958 { | |
| 959 status_type = substatus->type; | |
| 960 message = substatus->message; | |
| 961 } | |
| 962 else | |
| 11724 | 963 { |
| 12056 | 964 status_type = gaim_account_get_status_type_with_primitive(account, saved_status->type); |
| 965 if (status_type == NULL) | |
| 966 return; | |
| 967 message = saved_status->message; | |
| 968 } | |
| 969 | |
| 970 if ((message != NULL) && | |
| 971 (gaim_status_type_get_attr(status_type, "message"))) | |
| 972 { | |
| 973 gaim_account_set_status(account, gaim_status_type_get_id(status_type), | |
| 974 TRUE, "message", message, NULL); | |
| 975 } | |
| 976 else | |
| 977 { | |
| 978 gaim_account_set_status(account, gaim_status_type_get_id(status_type), | |
| 979 TRUE, NULL); | |
| 11724 | 980 } |
| 981 } | |
| 982 | |
| 11318 | 983 void * |
| 984 gaim_savedstatuses_get_handle(void) | |
| 985 { | |
| 986 static int handle; | |
| 987 | |
| 988 return &handle; | |
| 989 } | |
| 990 | |
| 10418 | 991 void |
| 992 gaim_savedstatuses_init(void) | |
| 993 { | |
| 12125 | 994 creation_times = g_hash_table_new(g_int_hash, g_int_equal); |
| 11975 | 995 |
| 12125 | 996 /* |
| 997 * Using 0 as the creation_time is a special case. | |
| 998 * If someone calls gaim_savedstatus_get_current() or | |
| 999 * gaim_savedstatus_get_idleaway() and either of those functions | |
| 1000 * sees a creation_time of 0, then it will create a default | |
| 1001 * saved status and return that to the user. | |
| 1002 */ | |
| 1003 gaim_prefs_add_none("/core/savedstatus"); | |
| 1004 gaim_prefs_add_int("/core/savedstatus/current", 0); | |
|
12857
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
1005 gaim_prefs_add_int("/core/savedstatus/startup", 0); |
|
e5f780a6137b
[gaim-migrate @ 15208]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
12814
diff
changeset
|
1006 gaim_prefs_add_bool("/core/savedstatus/startup_current_status", TRUE); |
| 12125 | 1007 gaim_prefs_add_int("/core/savedstatus/idleaway", 0); |
| 11975 | 1008 |
| 12125 | 1009 load_statuses(); |
| 10418 | 1010 } |
| 1011 | |
| 1012 void | |
| 1013 gaim_savedstatuses_uninit(void) | |
| 1014 { | |
| 12327 | 1015 remove_old_transient_statuses(); |
| 1016 | |
| 10428 | 1017 if (save_timer != 0) |
| 10418 | 1018 { |
| 10428 | 1019 gaim_timeout_remove(save_timer); |
| 1020 save_timer = 0; | |
| 10418 | 1021 sync_statuses(); |
| 1022 } | |
| 1023 | |
| 1024 while (saved_statuses != NULL) { | |
| 12056 | 1025 GaimSavedStatus *saved_status = saved_statuses->data; |
| 1026 saved_statuses = g_list_remove(saved_statuses, saved_status); | |
| 12724 | 1027 free_saved_status(saved_status); |
| 10418 | 1028 } |
| 12125 | 1029 |
| 1030 g_hash_table_destroy(creation_times); | |
| 10418 | 1031 } |
| 12056 | 1032 |
