Mercurial > pidgin.yaz
annotate src/desktopitem.c @ 14163:c3167a1dd817
[gaim-migrate @ 16811]
Split the DNS query stuff out into it's own file. Eventually
we should move the dnssrv code into this same file. Maybe
even share some code?
Also the first steps toward cancelable DNS queries.
committer: Tailor Script <tailor@pidgin.im>
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Thu, 17 Aug 2006 07:44:52 +0000 |
parents | 5de8bf62b8ef |
children |
rev | line source |
---|---|
10229 | 1 /** |
2 * @file gaim-desktop-item.c Functions for managing .desktop files | |
3 * @ingroup core | |
4 * | |
5 * Gaim is the legal property of its developers, whose names are too numerous | |
6 * to list here. Please refer to the COPYRIGHT file distributed with this | |
7 * source distribution. | |
8 * | |
9 * This program is free software; you can redistribute it and/or modify | |
10 * it under the terms of the GNU General Public License as published by | |
11 * the Free Software Foundation; either version 2 of the License, or | |
12 * (at your option) any later version. | |
13 * | |
14 * This program is distributed in the hope that it will be useful, | |
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 * GNU General Public License for more details. | |
18 * | |
19 * You should have received a copy of the GNU General Public License | |
20 * along with this program; if not, write to the Free Software | |
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
22 * | |
23 */ | |
24 | |
25 /* | |
26 * The following code has been adapted from gnome-desktop-item.[ch], | |
27 * as found on gnome-desktop-2.8.1. | |
28 * | |
29 * Copyright (C) 2004 by Alceste Scalas <alceste.scalas@gmx.net>. | |
30 * | |
31 * Original copyright notice: | |
32 * | |
33 * Copyright (C) 1999, 2000 Red Hat Inc. | |
34 * Copyright (C) 2001 Sid Vicious | |
35 * All rights reserved. | |
36 * | |
37 * This file is part of the Gnome Library. | |
38 * | |
39 * The Gnome Library is free software; you can redistribute it and/or | |
40 * modify it under the terms of the GNU Library General Public License as | |
41 * published by the Free Software Foundation; either version 2 of the | |
42 * License, or (at your option) any later version. | |
43 * | |
44 * The Gnome Library is distributed in the hope that it will be useful, | |
45 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
46 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
47 * Library General Public License for more details. | |
48 * | |
49 * You should have received a copy of the GNU Library General Public | |
50 * License along with the Gnome Library; see the file COPYING.LIB. If not, | |
51 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, | |
52 * Boston, MA 02111-1307, USA. | |
53 */ | |
54 | |
55 #include <errno.h> | |
56 #include <stdio.h> | |
57 #include <string.h> | |
58 #include <time.h> | |
59 #include "desktopitem.h" | |
60 #include "internal.h" | |
61 | |
62 struct _GaimDesktopItem { | |
63 int refcount; | |
64 | |
65 /* all languages used */ | |
66 GList *languages; | |
67 | |
68 GaimDesktopItemType type; | |
69 | |
70 /* `modified' means that the ditem has been | |
71 * modified since the last save. */ | |
72 gboolean modified; | |
73 | |
74 /* Keys of the main section only */ | |
75 GList *keys; | |
76 | |
77 GList *sections; | |
78 | |
79 /* This includes ALL keys, including | |
80 * other sections, separated by '/' */ | |
81 GHashTable *main_hash; | |
82 | |
83 char *location; | |
84 | |
85 time_t mtime; | |
86 }; | |
87 | |
88 typedef struct { | |
89 char *name; | |
90 GList *keys; | |
91 } Section; | |
92 | |
93 typedef enum { | |
94 ENCODING_UNKNOWN, | |
95 ENCODING_UTF8, | |
96 ENCODING_LEGACY_MIXED | |
97 } Encoding; | |
98 | |
99 /************************************************************************** | |
100 * Private utility functions | |
101 **************************************************************************/ | |
102 static GaimDesktopItemType | |
103 type_from_string (const char *type) | |
104 { | |
105 if (!type) | |
106 return GAIM_DESKTOP_ITEM_TYPE_NULL; | |
107 | |
108 switch (type [0]) { | |
109 case 'A': | |
110 if (!strcmp (type, "Application")) | |
111 return GAIM_DESKTOP_ITEM_TYPE_APPLICATION; | |
112 break; | |
113 case 'L': | |
114 if (!strcmp (type, "Link")) | |
115 return GAIM_DESKTOP_ITEM_TYPE_LINK; | |
116 break; | |
117 case 'F': | |
118 if (!strcmp (type, "FSDevice")) | |
119 return GAIM_DESKTOP_ITEM_TYPE_FSDEVICE; | |
120 break; | |
121 case 'M': | |
122 if (!strcmp (type, "MimeType")) | |
123 return GAIM_DESKTOP_ITEM_TYPE_MIME_TYPE; | |
124 break; | |
125 case 'D': | |
126 if (!strcmp (type, "Directory")) | |
127 return GAIM_DESKTOP_ITEM_TYPE_DIRECTORY; | |
128 break; | |
129 case 'S': | |
130 if (!strcmp (type, "Service")) | |
131 return GAIM_DESKTOP_ITEM_TYPE_SERVICE; | |
132 | |
133 else if (!strcmp (type, "ServiceType")) | |
134 return GAIM_DESKTOP_ITEM_TYPE_SERVICE_TYPE; | |
135 break; | |
136 default: | |
137 break; | |
138 } | |
139 | |
140 return GAIM_DESKTOP_ITEM_TYPE_OTHER; | |
141 } | |
142 | |
143 static Section * | |
144 find_section (GaimDesktopItem *item, const char *section) | |
145 { | |
146 GList *li; | |
147 Section *sec; | |
148 | |
149 if (section == NULL) | |
150 return NULL; | |
151 if (strcmp (section, "Desktop Entry") == 0) | |
152 return NULL; | |
153 | |
154 for (li = item->sections; li != NULL; li = li->next) { | |
155 sec = li->data; | |
156 if (strcmp (sec->name, section) == 0) | |
157 return sec; | |
158 } | |
159 | |
160 sec = g_new0 (Section, 1); | |
161 sec->name = g_strdup (section); | |
162 sec->keys = NULL; | |
163 | |
164 item->sections = g_list_append (item->sections, sec); | |
165 | |
166 /* Don't mark the item modified, this is just an empty section, | |
167 * it won't be saved even */ | |
168 | |
169 return sec; | |
170 } | |
171 | |
172 static Section * | |
173 section_from_key (GaimDesktopItem *item, const char *key) | |
174 { | |
175 char *p; | |
176 char *name; | |
177 Section *sec; | |
178 | |
179 if (key == NULL) | |
180 return NULL; | |
181 | |
182 p = strchr (key, '/'); | |
183 if (p == NULL) | |
184 return NULL; | |
185 | |
186 name = g_strndup (key, p - key); | |
187 | |
188 sec = find_section (item, name); | |
189 | |
190 g_free (name); | |
191 | |
192 return sec; | |
193 } | |
194 | |
195 static const char * | |
196 key_basename (const char *key) | |
197 { | |
198 char *p = strrchr (key, '/'); | |
199 if (p != NULL) | |
200 return p+1; | |
201 else | |
202 return key; | |
203 } | |
204 | |
205 static void | |
206 set (GaimDesktopItem *item, const char *key, const char *value) | |
207 { | |
208 Section *sec = section_from_key (item, key); | |
209 | |
210 if (sec != NULL) { | |
211 if (value != NULL) { | |
212 if (g_hash_table_lookup (item->main_hash, key) == NULL) | |
213 sec->keys = g_list_append | |
214 (sec->keys, | |
215 g_strdup (key_basename (key))); | |
216 | |
217 g_hash_table_replace (item->main_hash, | |
218 g_strdup (key), | |
219 g_strdup (value)); | |
220 } else { | |
221 GList *list = g_list_find_custom | |
222 (sec->keys, key_basename (key), | |
223 (GCompareFunc)strcmp); | |
224 if (list != NULL) { | |
225 g_free (list->data); | |
226 sec->keys = | |
227 g_list_delete_link (sec->keys, list); | |
228 } | |
229 g_hash_table_remove (item->main_hash, key); | |
230 } | |
231 } else { | |
232 if (value != NULL) { | |
233 if (g_hash_table_lookup (item->main_hash, key) == NULL) | |
234 item->keys = g_list_append (item->keys, | |
235 g_strdup (key)); | |
236 | |
237 g_hash_table_replace (item->main_hash, | |
238 g_strdup (key), | |
239 g_strdup (value)); | |
240 } else { | |
241 GList *list = g_list_find_custom | |
242 (item->keys, key, (GCompareFunc)strcmp); | |
243 if (list != NULL) { | |
244 g_free (list->data); | |
245 item->keys = | |
246 g_list_delete_link (item->keys, list); | |
247 } | |
248 g_hash_table_remove (item->main_hash, key); | |
249 } | |
250 } | |
251 item->modified = TRUE; | |
252 } | |
253 | |
254 | |
255 static void | |
256 _gaim_desktop_item_set_string (GaimDesktopItem *item, | |
257 const char *attr, | |
258 const char *value) | |
259 { | |
260 g_return_if_fail (item != NULL); | |
261 g_return_if_fail (item->refcount > 0); | |
262 g_return_if_fail (attr != NULL); | |
263 | |
264 set (item, attr, value); | |
265 | |
266 if (strcmp (attr, GAIM_DESKTOP_ITEM_TYPE) == 0) | |
267 item->type = type_from_string (value); | |
268 } | |
269 | |
270 static GaimDesktopItem * | |
271 _gaim_desktop_item_new (void) | |
272 { | |
273 GaimDesktopItem *retval; | |
274 | |
275 retval = g_new0 (GaimDesktopItem, 1); | |
276 | |
277 retval->refcount++; | |
278 | |
279 retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal, | |
280 (GDestroyNotify) g_free, | |
281 (GDestroyNotify) g_free); | |
282 | |
283 /* These are guaranteed to be set */ | |
284 _gaim_desktop_item_set_string (retval, | |
285 GAIM_DESKTOP_ITEM_NAME, | |
286 _("No name")); | |
287 _gaim_desktop_item_set_string (retval, | |
288 GAIM_DESKTOP_ITEM_ENCODING, | |
289 "UTF-8"); | |
290 _gaim_desktop_item_set_string (retval, | |
291 GAIM_DESKTOP_ITEM_VERSION, | |
292 "1.0"); | |
293 | |
294 return retval; | |
295 } | |
296 | |
297 static gpointer | |
298 _gaim_desktop_item_copy (gpointer boxed) | |
299 { | |
300 return gaim_desktop_item_copy (boxed); | |
301 } | |
302 | |
303 static void | |
304 _gaim_desktop_item_free (gpointer boxed) | |
305 { | |
306 gaim_desktop_item_unref (boxed); | |
307 } | |
308 | |
309 /* Note, does not include the trailing \n */ | |
310 static char * | |
311 my_fgets (char *buf, gsize bufsize, FILE *df) | |
312 { | |
313 int c; | |
314 gsize pos; | |
315 | |
316 g_return_val_if_fail (buf != NULL, NULL); | |
317 g_return_val_if_fail (df != NULL, NULL); | |
318 | |
319 pos = 0; | |
320 buf[0] = '\0'; | |
321 | |
322 do { | |
323 c = getc (df); | |
324 if (c == EOF || c == '\n') | |
325 break; | |
326 buf[pos++] = c; | |
327 } while (pos < bufsize-1); | |
328 | |
329 if (c == EOF && pos == 0) | |
330 return NULL; | |
331 | |
332 buf[pos++] = '\0'; | |
333 | |
334 return buf; | |
335 } | |
336 | |
337 static Encoding | |
338 get_encoding (FILE *df) | |
339 { | |
340 gboolean old_kde = FALSE; | |
341 char buf [BUFSIZ]; | |
342 gboolean all_valid_utf8 = TRUE; | |
343 | |
344 while (my_fgets (buf, sizeof (buf), df) != NULL) { | |
345 if (strncmp (GAIM_DESKTOP_ITEM_ENCODING, | |
346 buf, | |
347 strlen (GAIM_DESKTOP_ITEM_ENCODING)) == 0) { | |
348 char *p = &buf[strlen (GAIM_DESKTOP_ITEM_ENCODING)]; | |
349 if (*p == ' ') | |
350 p++; | |
351 if (*p != '=') | |
352 continue; | |
353 p++; | |
354 if (*p == ' ') | |
355 p++; | |
356 if (strcmp (p, "UTF-8") == 0) { | |
357 return ENCODING_UTF8; | |
358 } else if (strcmp (p, "Legacy-Mixed") == 0) { | |
359 return ENCODING_LEGACY_MIXED; | |
360 } else { | |
361 /* According to the spec we're not supposed | |
362 * to read a file like this */ | |
363 return ENCODING_UNKNOWN; | |
364 } | |
365 } else if (strcmp ("[KDE Desktop Entry]", buf) == 0) { | |
366 old_kde = TRUE; | |
367 /* don't break yet, we still want to support | |
368 * Encoding even here */ | |
369 } | |
370 if (all_valid_utf8 && ! g_utf8_validate (buf, -1, NULL)) | |
371 all_valid_utf8 = FALSE; | |
372 } | |
373 | |
374 if (old_kde) | |
375 return ENCODING_LEGACY_MIXED; | |
376 | |
377 /* A dilemma, new KDE files are in UTF-8 but have no Encoding | |
378 * info, at this time we really can't tell. The best thing to | |
379 * do right now is to just assume UTF-8 if the whole file | |
380 * validates as utf8 I suppose */ | |
381 | |
382 if (all_valid_utf8) | |
383 return ENCODING_UTF8; | |
384 else | |
385 return ENCODING_LEGACY_MIXED; | |
386 } | |
387 | |
388 static char * | |
389 snarf_locale_from_key (const char *key) | |
390 { | |
391 const char *brace; | |
392 char *locale, *p; | |
393 | |
394 brace = strchr (key, '['); | |
395 if (brace == NULL) | |
396 return NULL; | |
397 | |
398 locale = g_strdup (brace + 1); | |
399 if (*locale == '\0') { | |
400 g_free (locale); | |
401 return NULL; | |
402 } | |
403 p = strchr (locale, ']'); | |
404 if (p == NULL) { | |
405 g_free (locale); | |
406 return NULL; | |
407 } | |
408 *p = '\0'; | |
409 return locale; | |
410 } | |
411 | |
412 static gboolean | |
413 check_locale (const char *locale) | |
414 { | |
415 GIConv cd = g_iconv_open ("UTF-8", locale); | |
416 if ((GIConv)-1 == cd) | |
417 return FALSE; | |
418 g_iconv_close (cd); | |
419 return TRUE; | |
420 } | |
421 | |
422 static void | |
423 insert_locales (GHashTable *encodings, char *enc, ...) | |
424 { | |
425 va_list args; | |
426 char *s; | |
427 | |
428 va_start (args, enc); | |
429 for (;;) { | |
430 s = va_arg (args, char *); | |
431 if (s == NULL) | |
432 break; | |
433 g_hash_table_insert (encodings, s, enc); | |
434 } | |
435 va_end (args); | |
436 } | |
437 | |
438 /* make a standard conversion table from the desktop standard spec */ | |
439 static GHashTable * | |
440 init_encodings (void) | |
441 { | |
442 GHashTable *encodings = g_hash_table_new (g_str_hash, g_str_equal); | |
443 | |
444 /* "C" is plain ascii */ | |
445 insert_locales (encodings, "ASCII", "C", NULL); | |
446 | |
447 insert_locales (encodings, "ARMSCII-8", "by", NULL); | |
448 insert_locales (encodings, "BIG5", "zh_TW", NULL); | |
449 insert_locales (encodings, "CP1251", "be", "bg", NULL); | |
450 if (check_locale ("EUC-CN")) { | |
451 insert_locales (encodings, "EUC-CN", "zh_CN", NULL); | |
452 } else { | |
453 insert_locales (encodings, "GB2312", "zh_CN", NULL); | |
454 } | |
455 insert_locales (encodings, "EUC-JP", "ja", NULL); | |
456 insert_locales (encodings, "EUC-KR", "ko", NULL); | |
457 /*insert_locales (encodings, "GEORGIAN-ACADEMY", NULL);*/ | |
458 insert_locales (encodings, "GEORGIAN-PS", "ka", NULL); | |
459 insert_locales (encodings, "ISO-8859-1", "br", "ca", "da", "de", "en", "es", "eu", "fi", "fr", "gl", "it", "nl", "wa", "no", "pt", "pt", "sv", NULL); | |
460 insert_locales (encodings, "ISO-8859-2", "cs", "hr", "hu", "pl", "ro", "sk", "sl", "sq", "sr", NULL); | |
461 insert_locales (encodings, "ISO-8859-3", "eo", NULL); | |
462 insert_locales (encodings, "ISO-8859-5", "mk", "sp", NULL); | |
463 insert_locales (encodings, "ISO-8859-7", "el", NULL); | |
464 insert_locales (encodings, "ISO-8859-9", "tr", NULL); | |
465 insert_locales (encodings, "ISO-8859-13", "lt", "lv", "mi", NULL); | |
466 insert_locales (encodings, "ISO-8859-14", "ga", "cy", NULL); | |
467 insert_locales (encodings, "ISO-8859-15", "et", NULL); | |
468 insert_locales (encodings, "KOI8-R", "ru", NULL); | |
469 insert_locales (encodings, "KOI8-U", "uk", NULL); | |
470 if (check_locale ("TCVN-5712")) { | |
471 insert_locales (encodings, "TCVN-5712", "vi", NULL); | |
472 } else { | |
473 insert_locales (encodings, "TCVN", "vi", NULL); | |
474 } | |
475 insert_locales (encodings, "TIS-620", "th", NULL); | |
476 /*insert_locales (encodings, "VISCII", NULL);*/ | |
477 | |
478 return encodings; | |
479 } | |
480 | |
481 static const char * | |
482 get_encoding_from_locale (const char *locale) | |
483 { | |
484 char lang[3]; | |
485 const char *encoding; | |
486 static GHashTable *encodings = NULL; | |
487 | |
488 if (locale == NULL) | |
489 return NULL; | |
490 | |
491 /* if locale includes encoding, use it */ | |
492 encoding = strchr (locale, '.'); | |
493 if (encoding != NULL) { | |
494 return encoding+1; | |
495 } | |
496 | |
497 if (encodings == NULL) | |
498 encodings = init_encodings (); | |
499 | |
500 /* first try the entire locale (at this point ll_CC) */ | |
501 encoding = g_hash_table_lookup (encodings, locale); | |
502 if (encoding != NULL) | |
503 return encoding; | |
504 | |
505 /* Try just the language */ | |
506 strncpy (lang, locale, 2); | |
507 lang[2] = '\0'; | |
508 return g_hash_table_lookup (encodings, lang); | |
509 } | |
510 | |
511 static char * | |
512 decode_string_and_dup (const char *s) | |
513 { | |
514 char *p = g_malloc (strlen (s) + 1); | |
515 char *q = p; | |
516 | |
517 do { | |
518 if (*s == '\\'){ | |
519 switch (*(++s)){ | |
520 case 's': | |
521 *p++ = ' '; | |
522 break; | |
523 case 't': | |
524 *p++ = '\t'; | |
525 break; | |
526 case 'n': | |
527 *p++ = '\n'; | |
528 break; | |
529 case '\\': | |
530 *p++ = '\\'; | |
531 break; | |
532 case 'r': | |
533 *p++ = '\r'; | |
534 break; | |
535 default: | |
536 *p++ = '\\'; | |
537 *p++ = *s; | |
538 break; | |
539 } | |
540 } else { | |
541 *p++ = *s; | |
542 } | |
543 } while (*s++); | |
544 | |
545 return q; | |
546 } | |
547 | |
548 static char * | |
549 decode_string (const char *value, Encoding encoding, const char *locale) | |
550 { | |
551 char *retval = NULL; | |
552 | |
553 /* if legacy mixed, then convert */ | |
554 if (locale != NULL && encoding == ENCODING_LEGACY_MIXED) { | |
555 const char *char_encoding = get_encoding_from_locale (locale); | |
556 char *utf8_string; | |
557 if (char_encoding == NULL) | |
558 return NULL; | |
559 if (strcmp (char_encoding, "ASCII") == 0) { | |
560 return decode_string_and_dup (value); | |
561 } | |
562 utf8_string = g_convert (value, -1, "UTF-8", char_encoding, | |
563 NULL, NULL, NULL); | |
564 if (utf8_string == NULL) | |
565 return NULL; | |
566 retval = decode_string_and_dup (utf8_string); | |
567 g_free (utf8_string); | |
568 return retval; | |
569 /* if utf8, then validate */ | |
570 } else if (locale != NULL && encoding == ENCODING_UTF8) { | |
571 if ( ! g_utf8_validate (value, -1, NULL)) | |
572 /* invalid utf8, ignore this key */ | |
573 return NULL; | |
574 return decode_string_and_dup (value); | |
575 } else { | |
576 /* Meaning this is not a localized string */ | |
577 return decode_string_and_dup (value); | |
578 } | |
579 } | |
580 | |
581 /************************************************************ | |
582 * Parser: * | |
583 ************************************************************/ | |
584 | |
585 static gboolean G_GNUC_CONST | |
586 standard_is_boolean (const char * key) | |
587 { | |
588 static GHashTable *bools = NULL; | |
589 | |
590 if (bools == NULL) { | |
591 bools = g_hash_table_new (g_str_hash, g_str_equal); | |
592 g_hash_table_insert (bools, | |
593 GAIM_DESKTOP_ITEM_NO_DISPLAY, | |
594 GAIM_DESKTOP_ITEM_NO_DISPLAY); | |
595 g_hash_table_insert (bools, | |
596 GAIM_DESKTOP_ITEM_HIDDEN, | |
597 GAIM_DESKTOP_ITEM_HIDDEN); | |
598 g_hash_table_insert (bools, | |
599 GAIM_DESKTOP_ITEM_TERMINAL, | |
600 GAIM_DESKTOP_ITEM_TERMINAL); | |
601 g_hash_table_insert (bools, | |
602 GAIM_DESKTOP_ITEM_READ_ONLY, | |
603 GAIM_DESKTOP_ITEM_READ_ONLY); | |
604 } | |
605 | |
606 return g_hash_table_lookup (bools, key) != NULL; | |
607 } | |
608 | |
609 static gboolean G_GNUC_CONST | |
610 standard_is_strings (const char *key) | |
611 { | |
612 static GHashTable *strings = NULL; | |
613 | |
614 if (strings == NULL) { | |
615 strings = g_hash_table_new (g_str_hash, g_str_equal); | |
616 g_hash_table_insert (strings, | |
617 GAIM_DESKTOP_ITEM_FILE_PATTERN, | |
618 GAIM_DESKTOP_ITEM_FILE_PATTERN); | |
619 g_hash_table_insert (strings, | |
620 GAIM_DESKTOP_ITEM_ACTIONS, | |
621 GAIM_DESKTOP_ITEM_ACTIONS); | |
622 g_hash_table_insert (strings, | |
623 GAIM_DESKTOP_ITEM_MIME_TYPE, | |
624 GAIM_DESKTOP_ITEM_MIME_TYPE); | |
625 g_hash_table_insert (strings, | |
626 GAIM_DESKTOP_ITEM_PATTERNS, | |
627 GAIM_DESKTOP_ITEM_PATTERNS); | |
628 g_hash_table_insert (strings, | |
629 GAIM_DESKTOP_ITEM_SORT_ORDER, | |
630 GAIM_DESKTOP_ITEM_SORT_ORDER); | |
631 } | |
632 | |
633 return g_hash_table_lookup (strings, key) != NULL; | |
634 } | |
635 | |
636 /* If no need to cannonize, returns NULL */ | |
637 static char * | |
638 cannonize (const char *key, const char *value) | |
639 { | |
640 if (standard_is_boolean (key)) { | |
641 if (value[0] == 'T' || | |
642 value[0] == 't' || | |
643 value[0] == 'Y' || | |
644 value[0] == 'y' || | |
645 atoi (value) != 0) { | |
646 return g_strdup ("true"); | |
647 } else { | |
648 return g_strdup ("false"); | |
649 } | |
650 } else if (standard_is_strings (key)) { | |
651 int len = strlen (value); | |
652 if (len == 0 || value[len-1] != ';') { | |
653 return g_strconcat (value, ";", NULL); | |
654 } | |
655 } | |
656 /* XXX: Perhaps we should canonize numeric values as well, but this | |
657 * has caused some subtle problems before so it needs to be done | |
658 * carefully if at all */ | |
659 return NULL; | |
660 } | |
661 | |
662 static void | |
663 insert_key (GaimDesktopItem *item, | |
664 Section *cur_section, | |
665 Encoding encoding, | |
666 const char *key, | |
667 const char *value, | |
668 gboolean old_kde, | |
669 gboolean no_translations) | |
670 { | |
671 char *k; | |
672 char *val; | |
673 /* we always store everything in UTF-8 */ | |
674 if (cur_section == NULL && | |
675 strcmp (key, GAIM_DESKTOP_ITEM_ENCODING) == 0) { | |
676 k = g_strdup (key); | |
677 val = g_strdup ("UTF-8"); | |
678 } else { | |
679 char *locale = snarf_locale_from_key (key); | |
680 /* If we're ignoring translations */ | |
681 if (no_translations && locale != NULL) { | |
682 g_free (locale); | |
683 return; | |
684 } | |
685 val = decode_string (value, encoding, locale); | |
686 | |
687 /* Ignore this key, it's whacked */ | |
688 if (val == NULL) { | |
689 g_free (locale); | |
690 return; | |
691 } | |
692 | |
693 g_strchomp (val); | |
694 | |
695 /* For old KDE entries, we can also split by a comma | |
696 * on sort order, so convert to semicolons */ | |
697 if (old_kde && | |
698 cur_section == NULL && | |
699 strcmp (key, GAIM_DESKTOP_ITEM_SORT_ORDER) == 0 && | |
700 strchr (val, ';') == NULL) { | |
701 int i; | |
702 for (i = 0; val[i] != '\0'; i++) { | |
703 if (val[i] == ',') | |
704 val[i] = ';'; | |
705 } | |
706 } | |
707 | |
708 /* Check some types, not perfect, but catches a lot | |
709 * of things */ | |
710 if (cur_section == NULL) { | |
711 char *cannon = cannonize (key, val); | |
712 if (cannon != NULL) { | |
713 g_free (val); | |
714 val = cannon; | |
715 } | |
716 } | |
717 | |
718 k = g_strdup (key); | |
719 | |
720 /* Take care of the language part */ | |
721 if (locale != NULL && | |
722 strcmp (locale, "C") == 0) { | |
723 char *p; | |
724 /* Whack C locale */ | |
725 p = strchr (k, '['); | |
13442 | 726 if(p) *p = '\0'; |
10229 | 727 g_free (locale); |
728 } else if (locale != NULL) { | |
729 char *p, *brace; | |
730 | |
731 /* Whack the encoding part */ | |
732 p = strchr (locale, '.'); | |
733 if (p != NULL) | |
734 *p = '\0'; | |
13441 | 735 |
10229 | 736 if (g_list_find_custom (item->languages, locale, |
737 (GCompareFunc)strcmp) == NULL) { | |
738 item->languages = g_list_prepend | |
739 (item->languages, locale); | |
740 } else { | |
741 g_free (locale); | |
742 } | |
743 | |
13441 | 744 /* Whack encoding from encoding in the key */ |
10229 | 745 brace = strchr (k, '['); |
13441 | 746 if(brace != NULL) { |
747 p = strchr (brace, '.'); | |
748 if (p != NULL) { | |
749 *p = ']'; | |
750 *(p+1) = '\0'; | |
751 } | |
10229 | 752 } |
753 } | |
754 } | |
755 | |
756 | |
757 if (cur_section == NULL) { | |
758 /* only add to list if we haven't seen it before */ | |
759 if (g_hash_table_lookup (item->main_hash, k) == NULL) { | |
760 item->keys = g_list_prepend (item->keys, | |
761 g_strdup (k)); | |
762 } | |
763 /* later duplicates override earlier ones */ | |
764 g_hash_table_replace (item->main_hash, k, val); | |
765 } else { | |
766 char *full = g_strdup_printf | |
767 ("%s/%s", | |
768 cur_section->name, k); | |
769 /* only add to list if we haven't seen it before */ | |
770 if (g_hash_table_lookup (item->main_hash, full) == NULL) { | |
771 cur_section->keys = | |
772 g_list_prepend (cur_section->keys, k); | |
773 } | |
774 /* later duplicates override earlier ones */ | |
775 g_hash_table_replace (item->main_hash, | |
776 full, val); | |
777 } | |
778 } | |
779 | |
780 static const char * | |
781 lookup (const GaimDesktopItem *item, const char *key) | |
782 { | |
783 return g_hash_table_lookup (item->main_hash, key); | |
784 } | |
785 | |
786 static void | |
787 setup_type (GaimDesktopItem *item, const char *uri) | |
788 { | |
789 const char *type = g_hash_table_lookup (item->main_hash, | |
790 GAIM_DESKTOP_ITEM_TYPE); | |
791 if (type == NULL && uri != NULL) { | |
792 char *base = g_path_get_basename (uri); | |
793 if (base != NULL && | |
794 strcmp (base, ".directory") == 0) { | |
795 /* This gotta be a directory */ | |
796 g_hash_table_replace (item->main_hash, | |
797 g_strdup (GAIM_DESKTOP_ITEM_TYPE), | |
798 g_strdup ("Directory")); | |
799 item->keys = g_list_prepend | |
800 (item->keys, g_strdup (GAIM_DESKTOP_ITEM_TYPE)); | |
801 item->type = GAIM_DESKTOP_ITEM_TYPE_DIRECTORY; | |
802 } else { | |
803 item->type = GAIM_DESKTOP_ITEM_TYPE_NULL; | |
804 } | |
805 g_free (base); | |
806 } else { | |
807 item->type = type_from_string (type); | |
808 } | |
809 } | |
810 | |
811 static const char * | |
812 lookup_locale (const GaimDesktopItem *item, const char *key, const char *locale) | |
813 { | |
814 if (locale == NULL || | |
815 strcmp (locale, "C") == 0) { | |
816 return lookup (item, key); | |
817 } else { | |
818 const char *ret; | |
819 char *full = g_strdup_printf ("%s[%s]", key, locale); | |
820 ret = lookup (item, full); | |
821 g_free (full); | |
822 return ret; | |
823 } | |
824 } | |
825 | |
826 /* fallback to find something suitable for C locale */ | |
827 static char * | |
828 try_english_key (GaimDesktopItem *item, const char *key) | |
829 { | |
830 char *str; | |
831 char *locales[] = { "en_US", "en_GB", "en_AU", "en", NULL }; | |
832 int i; | |
833 | |
834 str = NULL; | |
835 for (i = 0; locales[i] != NULL && str == NULL; i++) { | |
836 str = g_strdup (lookup_locale (item, key, locales[i])); | |
837 } | |
838 if (str != NULL) { | |
839 /* We need a 7-bit ascii string, so whack all | |
840 * above 127 chars */ | |
841 guchar *p; | |
842 for (p = (guchar *)str; *p != '\0'; p++) { | |
843 if (*p > 127) | |
844 *p = '?'; | |
845 } | |
846 } | |
847 return str; | |
848 } | |
849 | |
850 | |
851 static void | |
852 sanitize (GaimDesktopItem *item, const char *uri) | |
853 { | |
854 const char *type; | |
855 | |
856 type = lookup (item, GAIM_DESKTOP_ITEM_TYPE); | |
857 | |
858 /* understand old gnome style url exec thingies */ | |
859 if (type != NULL && strcmp (type, "URL") == 0) { | |
860 const char *exec = lookup (item, GAIM_DESKTOP_ITEM_EXEC); | |
861 set (item, GAIM_DESKTOP_ITEM_TYPE, "Link"); | |
862 if (exec != NULL) { | |
863 /* Note, this must be in this order */ | |
864 set (item, GAIM_DESKTOP_ITEM_URL, exec); | |
865 set (item, GAIM_DESKTOP_ITEM_EXEC, NULL); | |
866 } | |
867 } | |
868 | |
869 /* we make sure we have Name, Encoding and Version */ | |
870 if (lookup (item, GAIM_DESKTOP_ITEM_NAME) == NULL) { | |
871 char *name = try_english_key (item, GAIM_DESKTOP_ITEM_NAME); | |
872 /* If no name, use the basename */ | |
873 if (name == NULL && uri != NULL) | |
874 name = g_path_get_basename (uri); | |
875 /* If no uri either, use same default as gnome_desktop_item_new */ | |
876 if (name == NULL) | |
877 name = g_strdup (_("No name")); | |
878 g_hash_table_replace (item->main_hash, | |
879 g_strdup (GAIM_DESKTOP_ITEM_NAME), | |
880 name); | |
881 item->keys = g_list_prepend | |
882 (item->keys, g_strdup (GAIM_DESKTOP_ITEM_NAME)); | |
883 } | |
884 if (lookup (item, GAIM_DESKTOP_ITEM_ENCODING) == NULL) { | |
885 /* We store everything in UTF-8 so write that down */ | |
886 g_hash_table_replace (item->main_hash, | |
887 g_strdup (GAIM_DESKTOP_ITEM_ENCODING), | |
888 g_strdup ("UTF-8")); | |
889 item->keys = g_list_prepend | |
890 (item->keys, g_strdup (GAIM_DESKTOP_ITEM_ENCODING)); | |
891 } | |
892 if (lookup (item, GAIM_DESKTOP_ITEM_VERSION) == NULL) { | |
893 /* this is the version that we follow, so write it down */ | |
894 g_hash_table_replace (item->main_hash, | |
895 g_strdup (GAIM_DESKTOP_ITEM_VERSION), | |
896 g_strdup ("1.0")); | |
897 item->keys = g_list_prepend | |
898 (item->keys, g_strdup (GAIM_DESKTOP_ITEM_VERSION)); | |
899 } | |
900 } | |
901 | |
902 enum { | |
903 FirstBrace, | |
904 OnSecHeader, | |
905 IgnoreToEOL, | |
906 IgnoreToEOLFirst, | |
907 KeyDef, | |
908 KeyDefOnKey, | |
909 KeyValue | |
910 }; | |
911 | |
912 static GaimDesktopItem * | |
913 ditem_load (FILE *df, | |
914 gboolean no_translations, | |
915 const char *uri) | |
916 { | |
917 int state; | |
918 char CharBuffer [1024]; | |
919 char *next = CharBuffer; | |
920 int c; | |
921 Encoding encoding; | |
922 GaimDesktopItem *item; | |
923 Section *cur_section = NULL; | |
924 char *key = NULL; | |
925 gboolean old_kde = FALSE; | |
926 | |
927 encoding = get_encoding (df); | |
928 if (encoding == ENCODING_UNKNOWN) { | |
929 fclose(df); | |
930 /* spec says, don't read this file */ | |
931 printf ("Unknown encoding of .desktop file"); | |
932 | |
933 return NULL; | |
934 } | |
935 | |
936 /* Rewind since get_encoding goes through the file */ | |
937 if (fseek(df, 0L, SEEK_SET)) { | |
938 fclose(df); | |
939 /* spec says, don't read this file */ | |
940 printf ("fseek() error on .desktop file"); | |
941 return NULL; | |
942 } | |
943 | |
944 item = _gaim_desktop_item_new (); | |
945 item->modified = FALSE; | |
946 | |
947 /* Note: location and mtime are filled in by the new_from_file | |
948 * function since it has those values */ | |
949 | |
950 #define GAIM_DESKTOP_ITEM_OVERFLOW (next == &CharBuffer [sizeof(CharBuffer)-1]) | |
951 | |
952 state = FirstBrace; | |
953 while ((c = getc (df)) != EOF) { | |
954 if (c == '\r') /* Ignore Carriage Return */ | |
955 continue; | |
956 | |
957 switch (state) { | |
958 | |
959 case OnSecHeader: | |
960 if (c == ']' || GAIM_DESKTOP_ITEM_OVERFLOW) { | |
961 *next = '\0'; | |
962 next = CharBuffer; | |
963 | |
964 /* keys were inserted in reverse */ | |
965 if (cur_section != NULL && | |
966 cur_section->keys != NULL) { | |
967 cur_section->keys = g_list_reverse | |
968 (cur_section->keys); | |
969 } | |
970 if (strcmp (CharBuffer, | |
971 "KDE Desktop Entry") == 0) { | |
972 /* Main section */ | |
973 cur_section = NULL; | |
974 old_kde = TRUE; | |
975 } else if (strcmp (CharBuffer, | |
976 "Desktop Entry") == 0) { | |
977 /* Main section */ | |
978 cur_section = NULL; | |
979 } else { | |
980 cur_section = g_new0 (Section, 1); | |
981 cur_section->name = | |
982 g_strdup (CharBuffer); | |
983 cur_section->keys = NULL; | |
984 item->sections = g_list_prepend | |
985 (item->sections, cur_section); | |
986 } | |
987 state = IgnoreToEOL; | |
988 } else if (c == '[') { | |
989 /* FIXME: probably error out instead of ignoring this */ | |
990 } else { | |
991 *next++ = c; | |
992 } | |
993 break; | |
994 | |
995 case IgnoreToEOL: | |
996 case IgnoreToEOLFirst: | |
997 if (c == '\n'){ | |
998 if (state == IgnoreToEOLFirst) | |
999 state = FirstBrace; | |
1000 else | |
1001 state = KeyDef; | |
1002 next = CharBuffer; | |
1003 } | |
1004 break; | |
1005 | |
1006 case FirstBrace: | |
1007 case KeyDef: | |
1008 case KeyDefOnKey: | |
1009 if (c == '#') { | |
1010 if (state == FirstBrace) | |
1011 state = IgnoreToEOLFirst; | |
1012 else | |
1013 state = IgnoreToEOL; | |
1014 break; | |
1015 } | |
1016 | |
1017 if (c == '[' && state != KeyDefOnKey){ | |
1018 state = OnSecHeader; | |
1019 next = CharBuffer; | |
1020 g_free (key); | |
1021 key = NULL; | |
1022 break; | |
1023 } | |
1024 /* On first pass, don't allow dangling keys */ | |
1025 if (state == FirstBrace) | |
1026 break; | |
1027 | |
1028 if ((c == ' ' && state != KeyDefOnKey) || c == '\t') | |
1029 break; | |
1030 | |
1031 if (c == '\n' || GAIM_DESKTOP_ITEM_OVERFLOW) { /* Abort Definition */ | |
1032 next = CharBuffer; | |
1033 state = KeyDef; | |
1034 break; | |
1035 } | |
1036 | |
1037 if (c == '=' || GAIM_DESKTOP_ITEM_OVERFLOW){ | |
1038 *next = '\0'; | |
1039 | |
1040 g_free (key); | |
1041 key = g_strdup (CharBuffer); | |
1042 state = KeyValue; | |
1043 next = CharBuffer; | |
1044 } else { | |
1045 *next++ = c; | |
1046 state = KeyDefOnKey; | |
1047 } | |
1048 break; | |
1049 | |
1050 case KeyValue: | |
1051 if (GAIM_DESKTOP_ITEM_OVERFLOW || c == '\n'){ | |
1052 *next = '\0'; | |
1053 | |
1054 insert_key (item, cur_section, encoding, | |
1055 key, CharBuffer, old_kde, | |
1056 no_translations); | |
1057 | |
1058 g_free (key); | |
1059 key = NULL; | |
1060 | |
1061 state = (c == '\n') ? KeyDef : IgnoreToEOL; | |
1062 next = CharBuffer; | |
1063 } else { | |
1064 *next++ = c; | |
1065 } | |
1066 break; | |
1067 | |
1068 } /* switch */ | |
1069 | |
1070 } /* while ((c = getc_unlocked (f)) != EOF) */ | |
1071 if (c == EOF && state == KeyValue) { | |
1072 *next = '\0'; | |
1073 | |
1074 insert_key (item, cur_section, encoding, | |
1075 key, CharBuffer, old_kde, | |
1076 no_translations); | |
1077 | |
1078 g_free (key); | |
1079 key = NULL; | |
1080 } | |
1081 | |
1082 #undef GAIM_DESKTOP_ITEM_OVERFLOW | |
1083 | |
1084 /* keys were inserted in reverse */ | |
1085 if (cur_section != NULL && | |
1086 cur_section->keys != NULL) { | |
1087 cur_section->keys = g_list_reverse (cur_section->keys); | |
1088 } | |
1089 /* keys were inserted in reverse */ | |
1090 item->keys = g_list_reverse (item->keys); | |
1091 /* sections were inserted in reverse */ | |
1092 item->sections = g_list_reverse (item->sections); | |
1093 | |
1094 /* sanitize some things */ | |
1095 sanitize (item, uri); | |
1096 | |
1097 /* make sure that we set up the type */ | |
1098 setup_type (item, uri); | |
1099 | |
1100 fclose (df); | |
1101 | |
1102 return item; | |
1103 } | |
1104 | |
1105 static void | |
1106 copy_string_hash (gpointer key, gpointer value, gpointer user_data) | |
1107 { | |
1108 GHashTable *copy = user_data; | |
1109 g_hash_table_replace (copy, | |
1110 g_strdup (key), | |
1111 g_strdup (value)); | |
1112 } | |
1113 | |
1114 static void | |
1115 free_section (gpointer data, gpointer user_data) | |
1116 { | |
1117 Section *section = data; | |
1118 | |
1119 g_free (section->name); | |
1120 section->name = NULL; | |
1121 | |
1122 g_list_foreach (section->keys, (GFunc)g_free, NULL); | |
1123 g_list_free (section->keys); | |
1124 section->keys = NULL; | |
1125 | |
1126 g_free (section); | |
1127 } | |
1128 | |
1129 static Section * | |
1130 dup_section (Section *sec) | |
1131 { | |
1132 GList *li; | |
1133 Section *retval = g_new0 (Section, 1); | |
1134 | |
1135 retval->name = g_strdup (sec->name); | |
1136 | |
1137 retval->keys = g_list_copy (sec->keys); | |
1138 for (li = retval->keys; li != NULL; li = li->next) | |
1139 li->data = g_strdup (li->data); | |
1140 | |
1141 return retval; | |
1142 } | |
1143 | |
1144 /************************************************************************** | |
1145 * Public functions | |
1146 **************************************************************************/ | |
1147 GaimDesktopItem * | |
1148 gaim_desktop_item_new_from_file (const char *filename) | |
1149 { | |
1150 GaimDesktopItem *retval; | |
1151 FILE *dfile; | |
1152 | |
1153 g_return_val_if_fail (filename != NULL, NULL); | |
1154 | |
10589
0f7452b1f777
[gaim-migrate @ 11994]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
10229
diff
changeset
|
1155 dfile = g_fopen(filename, "r"); |
10229 | 1156 if (!dfile) { |
1157 printf ("Can't open %s: %s", filename, strerror(errno)); | |
1158 return NULL; | |
1159 } | |
1160 | |
1161 retval = ditem_load(dfile, FALSE, filename); | |
1162 | |
1163 return retval; | |
1164 } | |
1165 | |
1166 GaimDesktopItemType | |
1167 gaim_desktop_item_get_entry_type (const GaimDesktopItem *item) | |
1168 { | |
1169 g_return_val_if_fail (item != NULL, 0); | |
1170 g_return_val_if_fail (item->refcount > 0, 0); | |
1171 | |
1172 return item->type; | |
1173 } | |
1174 | |
1175 const char * | |
1176 gaim_desktop_item_get_string (const GaimDesktopItem *item, | |
1177 const char *attr) | |
1178 { | |
1179 g_return_val_if_fail (item != NULL, NULL); | |
1180 g_return_val_if_fail (item->refcount > 0, NULL); | |
1181 g_return_val_if_fail (attr != NULL, NULL); | |
1182 | |
1183 return lookup (item, attr); | |
1184 } | |
1185 | |
1186 GaimDesktopItem * | |
1187 gaim_desktop_item_copy (const GaimDesktopItem *item) | |
1188 { | |
1189 GList *li; | |
1190 GaimDesktopItem *retval; | |
1191 | |
1192 g_return_val_if_fail (item != NULL, NULL); | |
1193 g_return_val_if_fail (item->refcount > 0, NULL); | |
1194 | |
1195 retval = _gaim_desktop_item_new (); | |
1196 | |
1197 retval->type = item->type; | |
1198 retval->modified = item->modified; | |
1199 retval->location = g_strdup (item->location); | |
1200 retval->mtime = item->mtime; | |
1201 | |
1202 /* Languages */ | |
1203 retval->languages = g_list_copy (item->languages); | |
1204 for (li = retval->languages; li != NULL; li = li->next) | |
1205 li->data = g_strdup (li->data); | |
1206 | |
1207 /* Keys */ | |
1208 retval->keys = g_list_copy (item->keys); | |
1209 for (li = retval->keys; li != NULL; li = li->next) | |
1210 li->data = g_strdup (li->data); | |
1211 | |
1212 /* Sections */ | |
1213 retval->sections = g_list_copy (item->sections); | |
1214 for (li = retval->sections; li != NULL; li = li->next) | |
1215 li->data = dup_section (li->data); | |
1216 | |
1217 retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal, | |
1218 (GDestroyNotify) g_free, | |
1219 (GDestroyNotify) g_free); | |
1220 | |
1221 g_hash_table_foreach (item->main_hash, | |
1222 copy_string_hash, | |
1223 retval->main_hash); | |
1224 | |
1225 return retval; | |
1226 } | |
1227 | |
1228 void | |
1229 gaim_desktop_item_unref (GaimDesktopItem *item) | |
1230 { | |
1231 g_return_if_fail (item != NULL); | |
1232 g_return_if_fail (item->refcount > 0); | |
1233 | |
1234 item->refcount--; | |
1235 | |
1236 if(item->refcount != 0) | |
1237 return; | |
1238 | |
1239 g_list_foreach (item->languages, (GFunc)g_free, NULL); | |
1240 g_list_free (item->languages); | |
1241 item->languages = NULL; | |
1242 | |
1243 g_list_foreach (item->keys, (GFunc)g_free, NULL); | |
1244 g_list_free (item->keys); | |
1245 item->keys = NULL; | |
1246 | |
1247 g_list_foreach (item->sections, free_section, NULL); | |
1248 g_list_free (item->sections); | |
1249 item->sections = NULL; | |
1250 | |
1251 g_hash_table_destroy (item->main_hash); | |
1252 item->main_hash = NULL; | |
1253 | |
1254 g_free (item->location); | |
1255 item->location = NULL; | |
1256 | |
1257 g_free (item); | |
1258 } | |
1259 | |
1260 GType | |
1261 gaim_desktop_item_get_type (void) | |
1262 { | |
1263 static GType type = 0; | |
1264 | |
1265 if (type == 0) { | |
1266 type = g_boxed_type_register_static ("GaimDesktopItem", | |
1267 _gaim_desktop_item_copy, | |
1268 _gaim_desktop_item_free); | |
1269 } | |
1270 | |
1271 return type; | |
1272 } |