Mercurial > pidgin
diff libpurple/desktopitem.c @ 15373:5fe8042783c1
Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Sat, 20 Jan 2007 02:32:10 +0000 |
parents | |
children | 32c366eeeb99 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/desktopitem.c Sat Jan 20 02:32:10 2007 +0000 @@ -0,0 +1,1272 @@ +/** + * @file gaim-desktop-item.c Functions for managing .desktop files + * @ingroup core + * + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * The following code has been adapted from gnome-desktop-item.[ch], + * as found on gnome-desktop-2.8.1. + * + * Copyright (C) 2004 by Alceste Scalas <alceste.scalas@gmx.net>. + * + * Original copyright notice: + * + * Copyright (C) 1999, 2000 Red Hat Inc. + * Copyright (C) 2001 Sid Vicious + * All rights reserved. + * + * This file is part of the Gnome Library. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include "desktopitem.h" +#include "internal.h" + +struct _GaimDesktopItem { + int refcount; + + /* all languages used */ + GList *languages; + + GaimDesktopItemType type; + + /* `modified' means that the ditem has been + * modified since the last save. */ + gboolean modified; + + /* Keys of the main section only */ + GList *keys; + + GList *sections; + + /* This includes ALL keys, including + * other sections, separated by '/' */ + GHashTable *main_hash; + + char *location; + + time_t mtime; +}; + +typedef struct { + char *name; + GList *keys; +} Section; + +typedef enum { + ENCODING_UNKNOWN, + ENCODING_UTF8, + ENCODING_LEGACY_MIXED +} Encoding; + +/************************************************************************** + * Private utility functions + **************************************************************************/ +static GaimDesktopItemType +type_from_string (const char *type) +{ + if (!type) + return GAIM_DESKTOP_ITEM_TYPE_NULL; + + switch (type [0]) { + case 'A': + if (!strcmp (type, "Application")) + return GAIM_DESKTOP_ITEM_TYPE_APPLICATION; + break; + case 'L': + if (!strcmp (type, "Link")) + return GAIM_DESKTOP_ITEM_TYPE_LINK; + break; + case 'F': + if (!strcmp (type, "FSDevice")) + return GAIM_DESKTOP_ITEM_TYPE_FSDEVICE; + break; + case 'M': + if (!strcmp (type, "MimeType")) + return GAIM_DESKTOP_ITEM_TYPE_MIME_TYPE; + break; + case 'D': + if (!strcmp (type, "Directory")) + return GAIM_DESKTOP_ITEM_TYPE_DIRECTORY; + break; + case 'S': + if (!strcmp (type, "Service")) + return GAIM_DESKTOP_ITEM_TYPE_SERVICE; + + else if (!strcmp (type, "ServiceType")) + return GAIM_DESKTOP_ITEM_TYPE_SERVICE_TYPE; + break; + default: + break; + } + + return GAIM_DESKTOP_ITEM_TYPE_OTHER; +} + +static Section * +find_section (GaimDesktopItem *item, const char *section) +{ + GList *li; + Section *sec; + + if (section == NULL) + return NULL; + if (strcmp (section, "Desktop Entry") == 0) + return NULL; + + for (li = item->sections; li != NULL; li = li->next) { + sec = li->data; + if (strcmp (sec->name, section) == 0) + return sec; + } + + sec = g_new0 (Section, 1); + sec->name = g_strdup (section); + sec->keys = NULL; + + item->sections = g_list_append (item->sections, sec); + + /* Don't mark the item modified, this is just an empty section, + * it won't be saved even */ + + return sec; +} + +static Section * +section_from_key (GaimDesktopItem *item, const char *key) +{ + char *p; + char *name; + Section *sec; + + if (key == NULL) + return NULL; + + p = strchr (key, '/'); + if (p == NULL) + return NULL; + + name = g_strndup (key, p - key); + + sec = find_section (item, name); + + g_free (name); + + return sec; +} + +static const char * +key_basename (const char *key) +{ + char *p = strrchr (key, '/'); + if (p != NULL) + return p+1; + else + return key; +} + +static void +set (GaimDesktopItem *item, const char *key, const char *value) +{ + Section *sec = section_from_key (item, key); + + if (sec != NULL) { + if (value != NULL) { + if (g_hash_table_lookup (item->main_hash, key) == NULL) + sec->keys = g_list_append + (sec->keys, + g_strdup (key_basename (key))); + + g_hash_table_replace (item->main_hash, + g_strdup (key), + g_strdup (value)); + } else { + GList *list = g_list_find_custom + (sec->keys, key_basename (key), + (GCompareFunc)strcmp); + if (list != NULL) { + g_free (list->data); + sec->keys = + g_list_delete_link (sec->keys, list); + } + g_hash_table_remove (item->main_hash, key); + } + } else { + if (value != NULL) { + if (g_hash_table_lookup (item->main_hash, key) == NULL) + item->keys = g_list_append (item->keys, + g_strdup (key)); + + g_hash_table_replace (item->main_hash, + g_strdup (key), + g_strdup (value)); + } else { + GList *list = g_list_find_custom + (item->keys, key, (GCompareFunc)strcmp); + if (list != NULL) { + g_free (list->data); + item->keys = + g_list_delete_link (item->keys, list); + } + g_hash_table_remove (item->main_hash, key); + } + } + item->modified = TRUE; +} + + +static void +_gaim_desktop_item_set_string (GaimDesktopItem *item, + const char *attr, + const char *value) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + g_return_if_fail (attr != NULL); + + set (item, attr, value); + + if (strcmp (attr, GAIM_DESKTOP_ITEM_TYPE) == 0) + item->type = type_from_string (value); +} + +static GaimDesktopItem * +_gaim_desktop_item_new (void) +{ + GaimDesktopItem *retval; + + retval = g_new0 (GaimDesktopItem, 1); + + retval->refcount++; + + retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + /* These are guaranteed to be set */ + _gaim_desktop_item_set_string (retval, + GAIM_DESKTOP_ITEM_NAME, + _("No name")); + _gaim_desktop_item_set_string (retval, + GAIM_DESKTOP_ITEM_ENCODING, + "UTF-8"); + _gaim_desktop_item_set_string (retval, + GAIM_DESKTOP_ITEM_VERSION, + "1.0"); + + return retval; +} + +static gpointer +_gaim_desktop_item_copy (gpointer boxed) +{ + return gaim_desktop_item_copy (boxed); +} + +static void +_gaim_desktop_item_free (gpointer boxed) +{ + gaim_desktop_item_unref (boxed); +} + +/* Note, does not include the trailing \n */ +static char * +my_fgets (char *buf, gsize bufsize, FILE *df) +{ + int c; + gsize pos; + + g_return_val_if_fail (buf != NULL, NULL); + g_return_val_if_fail (df != NULL, NULL); + + pos = 0; + buf[0] = '\0'; + + do { + c = getc (df); + if (c == EOF || c == '\n') + break; + buf[pos++] = c; + } while (pos < bufsize-1); + + if (c == EOF && pos == 0) + return NULL; + + buf[pos++] = '\0'; + + return buf; +} + +static Encoding +get_encoding (FILE *df) +{ + gboolean old_kde = FALSE; + char buf [BUFSIZ]; + gboolean all_valid_utf8 = TRUE; + + while (my_fgets (buf, sizeof (buf), df) != NULL) { + if (strncmp (GAIM_DESKTOP_ITEM_ENCODING, + buf, + strlen (GAIM_DESKTOP_ITEM_ENCODING)) == 0) { + char *p = &buf[strlen (GAIM_DESKTOP_ITEM_ENCODING)]; + if (*p == ' ') + p++; + if (*p != '=') + continue; + p++; + if (*p == ' ') + p++; + if (strcmp (p, "UTF-8") == 0) { + return ENCODING_UTF8; + } else if (strcmp (p, "Legacy-Mixed") == 0) { + return ENCODING_LEGACY_MIXED; + } else { + /* According to the spec we're not supposed + * to read a file like this */ + return ENCODING_UNKNOWN; + } + } else if (strcmp ("[KDE Desktop Entry]", buf) == 0) { + old_kde = TRUE; + /* don't break yet, we still want to support + * Encoding even here */ + } + if (all_valid_utf8 && ! g_utf8_validate (buf, -1, NULL)) + all_valid_utf8 = FALSE; + } + + if (old_kde) + return ENCODING_LEGACY_MIXED; + + /* A dilemma, new KDE files are in UTF-8 but have no Encoding + * info, at this time we really can't tell. The best thing to + * do right now is to just assume UTF-8 if the whole file + * validates as utf8 I suppose */ + + if (all_valid_utf8) + return ENCODING_UTF8; + else + return ENCODING_LEGACY_MIXED; +} + +static char * +snarf_locale_from_key (const char *key) +{ + const char *brace; + char *locale, *p; + + brace = strchr (key, '['); + if (brace == NULL) + return NULL; + + locale = g_strdup (brace + 1); + if (*locale == '\0') { + g_free (locale); + return NULL; + } + p = strchr (locale, ']'); + if (p == NULL) { + g_free (locale); + return NULL; + } + *p = '\0'; + return locale; +} + +static gboolean +check_locale (const char *locale) +{ + GIConv cd = g_iconv_open ("UTF-8", locale); + if ((GIConv)-1 == cd) + return FALSE; + g_iconv_close (cd); + return TRUE; +} + +static void +insert_locales (GHashTable *encodings, char *enc, ...) +{ + va_list args; + char *s; + + va_start (args, enc); + for (;;) { + s = va_arg (args, char *); + if (s == NULL) + break; + g_hash_table_insert (encodings, s, enc); + } + va_end (args); +} + +/* make a standard conversion table from the desktop standard spec */ +static GHashTable * +init_encodings (void) +{ + GHashTable *encodings = g_hash_table_new (g_str_hash, g_str_equal); + + /* "C" is plain ascii */ + insert_locales (encodings, "ASCII", "C", NULL); + + insert_locales (encodings, "ARMSCII-8", "by", NULL); + insert_locales (encodings, "BIG5", "zh_TW", NULL); + insert_locales (encodings, "CP1251", "be", "bg", NULL); + if (check_locale ("EUC-CN")) { + insert_locales (encodings, "EUC-CN", "zh_CN", NULL); + } else { + insert_locales (encodings, "GB2312", "zh_CN", NULL); + } + insert_locales (encodings, "EUC-JP", "ja", NULL); + insert_locales (encodings, "EUC-KR", "ko", NULL); + /*insert_locales (encodings, "GEORGIAN-ACADEMY", NULL);*/ + insert_locales (encodings, "GEORGIAN-PS", "ka", NULL); + insert_locales (encodings, "ISO-8859-1", "br", "ca", "da", "de", "en", "es", "eu", "fi", "fr", "gl", "it", "nl", "wa", "no", "pt", "pt", "sv", NULL); + insert_locales (encodings, "ISO-8859-2", "cs", "hr", "hu", "pl", "ro", "sk", "sl", "sq", "sr", NULL); + insert_locales (encodings, "ISO-8859-3", "eo", NULL); + insert_locales (encodings, "ISO-8859-5", "mk", "sp", NULL); + insert_locales (encodings, "ISO-8859-7", "el", NULL); + insert_locales (encodings, "ISO-8859-9", "tr", NULL); + insert_locales (encodings, "ISO-8859-13", "lt", "lv", "mi", NULL); + insert_locales (encodings, "ISO-8859-14", "ga", "cy", NULL); + insert_locales (encodings, "ISO-8859-15", "et", NULL); + insert_locales (encodings, "KOI8-R", "ru", NULL); + insert_locales (encodings, "KOI8-U", "uk", NULL); + if (check_locale ("TCVN-5712")) { + insert_locales (encodings, "TCVN-5712", "vi", NULL); + } else { + insert_locales (encodings, "TCVN", "vi", NULL); + } + insert_locales (encodings, "TIS-620", "th", NULL); + /*insert_locales (encodings, "VISCII", NULL);*/ + + return encodings; +} + +static const char * +get_encoding_from_locale (const char *locale) +{ + char lang[3]; + const char *encoding; + static GHashTable *encodings = NULL; + + if (locale == NULL) + return NULL; + + /* if locale includes encoding, use it */ + encoding = strchr (locale, '.'); + if (encoding != NULL) { + return encoding+1; + } + + if (encodings == NULL) + encodings = init_encodings (); + + /* first try the entire locale (at this point ll_CC) */ + encoding = g_hash_table_lookup (encodings, locale); + if (encoding != NULL) + return encoding; + + /* Try just the language */ + strncpy (lang, locale, 2); + lang[2] = '\0'; + return g_hash_table_lookup (encodings, lang); +} + +static char * +decode_string_and_dup (const char *s) +{ + char *p = g_malloc (strlen (s) + 1); + char *q = p; + + do { + if (*s == '\\'){ + switch (*(++s)){ + case 's': + *p++ = ' '; + break; + case 't': + *p++ = '\t'; + break; + case 'n': + *p++ = '\n'; + break; + case '\\': + *p++ = '\\'; + break; + case 'r': + *p++ = '\r'; + break; + default: + *p++ = '\\'; + *p++ = *s; + break; + } + } else { + *p++ = *s; + } + } while (*s++); + + return q; +} + +static char * +decode_string (const char *value, Encoding encoding, const char *locale) +{ + char *retval = NULL; + + /* if legacy mixed, then convert */ + if (locale != NULL && encoding == ENCODING_LEGACY_MIXED) { + const char *char_encoding = get_encoding_from_locale (locale); + char *utf8_string; + if (char_encoding == NULL) + return NULL; + if (strcmp (char_encoding, "ASCII") == 0) { + return decode_string_and_dup (value); + } + utf8_string = g_convert (value, -1, "UTF-8", char_encoding, + NULL, NULL, NULL); + if (utf8_string == NULL) + return NULL; + retval = decode_string_and_dup (utf8_string); + g_free (utf8_string); + return retval; + /* if utf8, then validate */ + } else if (locale != NULL && encoding == ENCODING_UTF8) { + if ( ! g_utf8_validate (value, -1, NULL)) + /* invalid utf8, ignore this key */ + return NULL; + return decode_string_and_dup (value); + } else { + /* Meaning this is not a localized string */ + return decode_string_and_dup (value); + } +} + +/************************************************************ + * Parser: * + ************************************************************/ + +static gboolean G_GNUC_CONST +standard_is_boolean (const char * key) +{ + static GHashTable *bools = NULL; + + if (bools == NULL) { + bools = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (bools, + GAIM_DESKTOP_ITEM_NO_DISPLAY, + GAIM_DESKTOP_ITEM_NO_DISPLAY); + g_hash_table_insert (bools, + GAIM_DESKTOP_ITEM_HIDDEN, + GAIM_DESKTOP_ITEM_HIDDEN); + g_hash_table_insert (bools, + GAIM_DESKTOP_ITEM_TERMINAL, + GAIM_DESKTOP_ITEM_TERMINAL); + g_hash_table_insert (bools, + GAIM_DESKTOP_ITEM_READ_ONLY, + GAIM_DESKTOP_ITEM_READ_ONLY); + } + + return g_hash_table_lookup (bools, key) != NULL; +} + +static gboolean G_GNUC_CONST +standard_is_strings (const char *key) +{ + static GHashTable *strings = NULL; + + if (strings == NULL) { + strings = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (strings, + GAIM_DESKTOP_ITEM_FILE_PATTERN, + GAIM_DESKTOP_ITEM_FILE_PATTERN); + g_hash_table_insert (strings, + GAIM_DESKTOP_ITEM_ACTIONS, + GAIM_DESKTOP_ITEM_ACTIONS); + g_hash_table_insert (strings, + GAIM_DESKTOP_ITEM_MIME_TYPE, + GAIM_DESKTOP_ITEM_MIME_TYPE); + g_hash_table_insert (strings, + GAIM_DESKTOP_ITEM_PATTERNS, + GAIM_DESKTOP_ITEM_PATTERNS); + g_hash_table_insert (strings, + GAIM_DESKTOP_ITEM_SORT_ORDER, + GAIM_DESKTOP_ITEM_SORT_ORDER); + } + + return g_hash_table_lookup (strings, key) != NULL; +} + +/* If no need to cannonize, returns NULL */ +static char * +cannonize (const char *key, const char *value) +{ + if (standard_is_boolean (key)) { + if (value[0] == 'T' || + value[0] == 't' || + value[0] == 'Y' || + value[0] == 'y' || + atoi (value) != 0) { + return g_strdup ("true"); + } else { + return g_strdup ("false"); + } + } else if (standard_is_strings (key)) { + int len = strlen (value); + if (len == 0 || value[len-1] != ';') { + return g_strconcat (value, ";", NULL); + } + } + /* XXX: Perhaps we should canonize numeric values as well, but this + * has caused some subtle problems before so it needs to be done + * carefully if at all */ + return NULL; +} + +static void +insert_key (GaimDesktopItem *item, + Section *cur_section, + Encoding encoding, + const char *key, + const char *value, + gboolean old_kde, + gboolean no_translations) +{ + char *k; + char *val; + /* we always store everything in UTF-8 */ + if (cur_section == NULL && + strcmp (key, GAIM_DESKTOP_ITEM_ENCODING) == 0) { + k = g_strdup (key); + val = g_strdup ("UTF-8"); + } else { + char *locale = snarf_locale_from_key (key); + /* If we're ignoring translations */ + if (no_translations && locale != NULL) { + g_free (locale); + return; + } + val = decode_string (value, encoding, locale); + + /* Ignore this key, it's whacked */ + if (val == NULL) { + g_free (locale); + return; + } + + g_strchomp (val); + + /* For old KDE entries, we can also split by a comma + * on sort order, so convert to semicolons */ + if (old_kde && + cur_section == NULL && + strcmp (key, GAIM_DESKTOP_ITEM_SORT_ORDER) == 0 && + strchr (val, ';') == NULL) { + int i; + for (i = 0; val[i] != '\0'; i++) { + if (val[i] == ',') + val[i] = ';'; + } + } + + /* Check some types, not perfect, but catches a lot + * of things */ + if (cur_section == NULL) { + char *cannon = cannonize (key, val); + if (cannon != NULL) { + g_free (val); + val = cannon; + } + } + + k = g_strdup (key); + + /* Take care of the language part */ + if (locale != NULL && + strcmp (locale, "C") == 0) { + char *p; + /* Whack C locale */ + p = strchr (k, '['); + if(p) *p = '\0'; + g_free (locale); + } else if (locale != NULL) { + char *p, *brace; + + /* Whack the encoding part */ + p = strchr (locale, '.'); + if (p != NULL) + *p = '\0'; + + if (g_list_find_custom (item->languages, locale, + (GCompareFunc)strcmp) == NULL) { + item->languages = g_list_prepend + (item->languages, locale); + } else { + g_free (locale); + } + + /* Whack encoding from encoding in the key */ + brace = strchr (k, '['); + if(brace != NULL) { + p = strchr (brace, '.'); + if (p != NULL) { + *p = ']'; + *(p+1) = '\0'; + } + } + } + } + + + if (cur_section == NULL) { + /* only add to list if we haven't seen it before */ + if (g_hash_table_lookup (item->main_hash, k) == NULL) { + item->keys = g_list_prepend (item->keys, + g_strdup (k)); + } + /* later duplicates override earlier ones */ + g_hash_table_replace (item->main_hash, k, val); + } else { + char *full = g_strdup_printf + ("%s/%s", + cur_section->name, k); + /* only add to list if we haven't seen it before */ + if (g_hash_table_lookup (item->main_hash, full) == NULL) { + cur_section->keys = + g_list_prepend (cur_section->keys, k); + } + /* later duplicates override earlier ones */ + g_hash_table_replace (item->main_hash, + full, val); + } +} + +static const char * +lookup (const GaimDesktopItem *item, const char *key) +{ + return g_hash_table_lookup (item->main_hash, key); +} + +static void +setup_type (GaimDesktopItem *item, const char *uri) +{ + const char *type = g_hash_table_lookup (item->main_hash, + GAIM_DESKTOP_ITEM_TYPE); + if (type == NULL && uri != NULL) { + char *base = g_path_get_basename (uri); + if (base != NULL && + strcmp (base, ".directory") == 0) { + /* This gotta be a directory */ + g_hash_table_replace (item->main_hash, + g_strdup (GAIM_DESKTOP_ITEM_TYPE), + g_strdup ("Directory")); + item->keys = g_list_prepend + (item->keys, g_strdup (GAIM_DESKTOP_ITEM_TYPE)); + item->type = GAIM_DESKTOP_ITEM_TYPE_DIRECTORY; + } else { + item->type = GAIM_DESKTOP_ITEM_TYPE_NULL; + } + g_free (base); + } else { + item->type = type_from_string (type); + } +} + +static const char * +lookup_locale (const GaimDesktopItem *item, const char *key, const char *locale) +{ + if (locale == NULL || + strcmp (locale, "C") == 0) { + return lookup (item, key); + } else { + const char *ret; + char *full = g_strdup_printf ("%s[%s]", key, locale); + ret = lookup (item, full); + g_free (full); + return ret; + } +} + +/* fallback to find something suitable for C locale */ +static char * +try_english_key (GaimDesktopItem *item, const char *key) +{ + char *str; + char *locales[] = { "en_US", "en_GB", "en_AU", "en", NULL }; + int i; + + str = NULL; + for (i = 0; locales[i] != NULL && str == NULL; i++) { + str = g_strdup (lookup_locale (item, key, locales[i])); + } + if (str != NULL) { + /* We need a 7-bit ascii string, so whack all + * above 127 chars */ + guchar *p; + for (p = (guchar *)str; *p != '\0'; p++) { + if (*p > 127) + *p = '?'; + } + } + return str; +} + + +static void +sanitize (GaimDesktopItem *item, const char *uri) +{ + const char *type; + + type = lookup (item, GAIM_DESKTOP_ITEM_TYPE); + + /* understand old gnome style url exec thingies */ + if (type != NULL && strcmp (type, "URL") == 0) { + const char *exec = lookup (item, GAIM_DESKTOP_ITEM_EXEC); + set (item, GAIM_DESKTOP_ITEM_TYPE, "Link"); + if (exec != NULL) { + /* Note, this must be in this order */ + set (item, GAIM_DESKTOP_ITEM_URL, exec); + set (item, GAIM_DESKTOP_ITEM_EXEC, NULL); + } + } + + /* we make sure we have Name, Encoding and Version */ + if (lookup (item, GAIM_DESKTOP_ITEM_NAME) == NULL) { + char *name = try_english_key (item, GAIM_DESKTOP_ITEM_NAME); + /* If no name, use the basename */ + if (name == NULL && uri != NULL) + name = g_path_get_basename (uri); + /* If no uri either, use same default as gnome_desktop_item_new */ + if (name == NULL) + name = g_strdup (_("No name")); + g_hash_table_replace (item->main_hash, + g_strdup (GAIM_DESKTOP_ITEM_NAME), + name); + item->keys = g_list_prepend + (item->keys, g_strdup (GAIM_DESKTOP_ITEM_NAME)); + } + if (lookup (item, GAIM_DESKTOP_ITEM_ENCODING) == NULL) { + /* We store everything in UTF-8 so write that down */ + g_hash_table_replace (item->main_hash, + g_strdup (GAIM_DESKTOP_ITEM_ENCODING), + g_strdup ("UTF-8")); + item->keys = g_list_prepend + (item->keys, g_strdup (GAIM_DESKTOP_ITEM_ENCODING)); + } + if (lookup (item, GAIM_DESKTOP_ITEM_VERSION) == NULL) { + /* this is the version that we follow, so write it down */ + g_hash_table_replace (item->main_hash, + g_strdup (GAIM_DESKTOP_ITEM_VERSION), + g_strdup ("1.0")); + item->keys = g_list_prepend + (item->keys, g_strdup (GAIM_DESKTOP_ITEM_VERSION)); + } +} + +enum { + FirstBrace, + OnSecHeader, + IgnoreToEOL, + IgnoreToEOLFirst, + KeyDef, + KeyDefOnKey, + KeyValue +}; + +static GaimDesktopItem * +ditem_load (FILE *df, + gboolean no_translations, + const char *uri) +{ + int state; + char CharBuffer [1024]; + char *next = CharBuffer; + int c; + Encoding encoding; + GaimDesktopItem *item; + Section *cur_section = NULL; + char *key = NULL; + gboolean old_kde = FALSE; + + encoding = get_encoding (df); + if (encoding == ENCODING_UNKNOWN) { + fclose(df); + /* spec says, don't read this file */ + printf ("Unknown encoding of .desktop file"); + + return NULL; + } + + /* Rewind since get_encoding goes through the file */ + if (fseek(df, 0L, SEEK_SET)) { + fclose(df); + /* spec says, don't read this file */ + printf ("fseek() error on .desktop file"); + return NULL; + } + + item = _gaim_desktop_item_new (); + item->modified = FALSE; + + /* Note: location and mtime are filled in by the new_from_file + * function since it has those values */ + +#define GAIM_DESKTOP_ITEM_OVERFLOW (next == &CharBuffer [sizeof(CharBuffer)-1]) + + state = FirstBrace; + while ((c = getc (df)) != EOF) { + if (c == '\r') /* Ignore Carriage Return */ + continue; + + switch (state) { + + case OnSecHeader: + if (c == ']' || GAIM_DESKTOP_ITEM_OVERFLOW) { + *next = '\0'; + next = CharBuffer; + + /* keys were inserted in reverse */ + if (cur_section != NULL && + cur_section->keys != NULL) { + cur_section->keys = g_list_reverse + (cur_section->keys); + } + if (strcmp (CharBuffer, + "KDE Desktop Entry") == 0) { + /* Main section */ + cur_section = NULL; + old_kde = TRUE; + } else if (strcmp (CharBuffer, + "Desktop Entry") == 0) { + /* Main section */ + cur_section = NULL; + } else { + cur_section = g_new0 (Section, 1); + cur_section->name = + g_strdup (CharBuffer); + cur_section->keys = NULL; + item->sections = g_list_prepend + (item->sections, cur_section); + } + state = IgnoreToEOL; + } else if (c == '[') { + /* FIXME: probably error out instead of ignoring this */ + } else { + *next++ = c; + } + break; + + case IgnoreToEOL: + case IgnoreToEOLFirst: + if (c == '\n'){ + if (state == IgnoreToEOLFirst) + state = FirstBrace; + else + state = KeyDef; + next = CharBuffer; + } + break; + + case FirstBrace: + case KeyDef: + case KeyDefOnKey: + if (c == '#') { + if (state == FirstBrace) + state = IgnoreToEOLFirst; + else + state = IgnoreToEOL; + break; + } + + if (c == '[' && state != KeyDefOnKey){ + state = OnSecHeader; + next = CharBuffer; + g_free (key); + key = NULL; + break; + } + /* On first pass, don't allow dangling keys */ + if (state == FirstBrace) + break; + + if ((c == ' ' && state != KeyDefOnKey) || c == '\t') + break; + + if (c == '\n' || GAIM_DESKTOP_ITEM_OVERFLOW) { /* Abort Definition */ + next = CharBuffer; + state = KeyDef; + break; + } + + if (c == '=' || GAIM_DESKTOP_ITEM_OVERFLOW){ + *next = '\0'; + + g_free (key); + key = g_strdup (CharBuffer); + state = KeyValue; + next = CharBuffer; + } else { + *next++ = c; + state = KeyDefOnKey; + } + break; + + case KeyValue: + if (GAIM_DESKTOP_ITEM_OVERFLOW || c == '\n'){ + *next = '\0'; + + insert_key (item, cur_section, encoding, + key, CharBuffer, old_kde, + no_translations); + + g_free (key); + key = NULL; + + state = (c == '\n') ? KeyDef : IgnoreToEOL; + next = CharBuffer; + } else { + *next++ = c; + } + break; + + } /* switch */ + + } /* while ((c = getc_unlocked (f)) != EOF) */ + if (c == EOF && state == KeyValue) { + *next = '\0'; + + insert_key (item, cur_section, encoding, + key, CharBuffer, old_kde, + no_translations); + + g_free (key); + key = NULL; + } + +#undef GAIM_DESKTOP_ITEM_OVERFLOW + + /* keys were inserted in reverse */ + if (cur_section != NULL && + cur_section->keys != NULL) { + cur_section->keys = g_list_reverse (cur_section->keys); + } + /* keys were inserted in reverse */ + item->keys = g_list_reverse (item->keys); + /* sections were inserted in reverse */ + item->sections = g_list_reverse (item->sections); + + /* sanitize some things */ + sanitize (item, uri); + + /* make sure that we set up the type */ + setup_type (item, uri); + + fclose (df); + + return item; +} + +static void +copy_string_hash (gpointer key, gpointer value, gpointer user_data) +{ + GHashTable *copy = user_data; + g_hash_table_replace (copy, + g_strdup (key), + g_strdup (value)); +} + +static void +free_section (gpointer data, gpointer user_data) +{ + Section *section = data; + + g_free (section->name); + section->name = NULL; + + g_list_foreach (section->keys, (GFunc)g_free, NULL); + g_list_free (section->keys); + section->keys = NULL; + + g_free (section); +} + +static Section * +dup_section (Section *sec) +{ + GList *li; + Section *retval = g_new0 (Section, 1); + + retval->name = g_strdup (sec->name); + + retval->keys = g_list_copy (sec->keys); + for (li = retval->keys; li != NULL; li = li->next) + li->data = g_strdup (li->data); + + return retval; +} + +/************************************************************************** + * Public functions + **************************************************************************/ +GaimDesktopItem * +gaim_desktop_item_new_from_file (const char *filename) +{ + GaimDesktopItem *retval; + FILE *dfile; + + g_return_val_if_fail (filename != NULL, NULL); + + dfile = g_fopen(filename, "r"); + if (!dfile) { + printf ("Can't open %s: %s", filename, strerror(errno)); + return NULL; + } + + retval = ditem_load(dfile, FALSE, filename); + + return retval; +} + +GaimDesktopItemType +gaim_desktop_item_get_entry_type (const GaimDesktopItem *item) +{ + g_return_val_if_fail (item != NULL, 0); + g_return_val_if_fail (item->refcount > 0, 0); + + return item->type; +} + +const char * +gaim_desktop_item_get_string (const GaimDesktopItem *item, + const char *attr) +{ + g_return_val_if_fail (item != NULL, NULL); + g_return_val_if_fail (item->refcount > 0, NULL); + g_return_val_if_fail (attr != NULL, NULL); + + return lookup (item, attr); +} + +GaimDesktopItem * +gaim_desktop_item_copy (const GaimDesktopItem *item) +{ + GList *li; + GaimDesktopItem *retval; + + g_return_val_if_fail (item != NULL, NULL); + g_return_val_if_fail (item->refcount > 0, NULL); + + retval = _gaim_desktop_item_new (); + + retval->type = item->type; + retval->modified = item->modified; + retval->location = g_strdup (item->location); + retval->mtime = item->mtime; + + /* Languages */ + retval->languages = g_list_copy (item->languages); + for (li = retval->languages; li != NULL; li = li->next) + li->data = g_strdup (li->data); + + /* Keys */ + retval->keys = g_list_copy (item->keys); + for (li = retval->keys; li != NULL; li = li->next) + li->data = g_strdup (li->data); + + /* Sections */ + retval->sections = g_list_copy (item->sections); + for (li = retval->sections; li != NULL; li = li->next) + li->data = dup_section (li->data); + + retval->main_hash = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + g_hash_table_foreach (item->main_hash, + copy_string_hash, + retval->main_hash); + + return retval; +} + +void +gaim_desktop_item_unref (GaimDesktopItem *item) +{ + g_return_if_fail (item != NULL); + g_return_if_fail (item->refcount > 0); + + item->refcount--; + + if(item->refcount != 0) + return; + + g_list_foreach (item->languages, (GFunc)g_free, NULL); + g_list_free (item->languages); + item->languages = NULL; + + g_list_foreach (item->keys, (GFunc)g_free, NULL); + g_list_free (item->keys); + item->keys = NULL; + + g_list_foreach (item->sections, free_section, NULL); + g_list_free (item->sections); + item->sections = NULL; + + g_hash_table_destroy (item->main_hash); + item->main_hash = NULL; + + g_free (item->location); + item->location = NULL; + + g_free (item); +} + +GType +gaim_desktop_item_get_type (void) +{ + static GType type = 0; + + if (type == 0) { + type = g_boxed_type_register_static ("GaimDesktopItem", + _gaim_desktop_item_copy, + _gaim_desktop_item_free); + } + + return type; +}