Linkbase adds all the .desktops in the system, and updates them as they change.
authorDana Jansens <danakj@orodu.net>
Tue, 21 Sep 2010 00:46:37 +0000 (20:46 -0400)
committerDana Jansens <danakj@orodu.net>
Sun, 16 Oct 2011 22:54:04 +0000 (18:54 -0400)
Fixes to .desktop parsing.
Add language/locale support for .desktop files.
Fixes to inotify watching, change what information is passed through the notify handler.
Add linkbase.h to the public obt headers.

Makefile.am
obt/ddparse.c
obt/ddparse.h
obt/link.c
obt/link.h
obt/linkbase.c
obt/linkbase.h
obt/watch.c
obt/watch.h
obt/watch_inotify.c

index 11277b2a46b147d4b544788104feb463434bba3b..e63184e08a580d53de0b51def77f479bcb4c394c 100644 (file)
@@ -451,6 +451,7 @@ rrpubinclude_HEADERS = \
 
 obtpubinclude_HEADERS = \
        obt/link.h \
+       obt/linkbase.h \
        obt/display.h \
        obt/keyboard.h \
        obt/xml.h \
index 9c4424f1fc3e3c64f9f6307c4b7c764e4d8cf3e5..fc988499f706970a4ef024792f4d669118413bc2 100644 (file)
@@ -32,6 +32,7 @@ typedef struct _ObtDDParse ObtDDParse;
    Return TRUE if it is added to the hash table, and FALSE if not.
 */
 typedef gboolean (*ObtDDParseValueFunc)(gchar *key, const gchar *val,
+                                        ObtDDParseLangMatch match,
                                         ObtDDParse *parse, gboolean *error);
 
 
@@ -46,6 +47,9 @@ enum {
 
 struct _ObtDDParse {
     const gchar *filename;
+    const gchar *language;
+    const gchar *country;
+    const gchar *modifier;
     gulong lineno;
     gulong flags;
     ObtDDParseGroup *group;
@@ -180,7 +184,7 @@ static gchar* parse_value_string(const gchar *in,
         }
         else if (*i == '\\')
             backslash = TRUE;
-        else if ((guchar)*i >= 127 || (guchar)*i < 32) {
+        else if ((!locale && (guchar)*i >= 127) || (guchar)*i < 32) {
             /* avoid ascii control characters */
             parse_error("Found control character in string", parse, error);
             break;
@@ -396,7 +400,7 @@ static gboolean parse_file_line(FILE *f, gchar **buf,
         }
         else {
             /* read more */
-            size += BUFMUL;
+            *size += BUFMUL;
             *buf = g_renew(char, *buf, *size);
         }
     }
@@ -446,7 +450,7 @@ static void parse_group(const gchar *buf, gulong len,
 
         g->seen = TRUE;
         parse->group = g;
-        g_print("Found group %s\n", g->name);
+        /* g_print("Found group %s\n", g->name); */
     }
 }
 
@@ -454,7 +458,10 @@ static void parse_key_value(const gchar *buf, gulong len,
                             ObtDDParse *parse, gboolean *error)
 {
     gulong i, keyend, valstart, eq;
+    gulong langstart, langend, countrystart, countryend, modstart, modend;
     char *key;
+    ObtDDParseValue *val;
+    ObtDDParseLangMatch match;
 
     /* find the end of the key */
     for (i = 0; i < len; ++i)
@@ -471,15 +478,74 @@ static void parse_key_value(const gchar *buf, gulong len,
         parse_error("Empty key", parse, error);
         return;
     }
+
+    /* is there a language specifier? */
+    langstart = langend = countrystart = countryend = modstart = modend = 0;
+    if ((guchar)buf[i] == '[') {
+        langstart = i+1;
+        for (i = langstart; i < len; ++i)
+            if ((guchar)buf[i] == '.' || (guchar)buf[i] == '_' ||
+                (guchar)buf[i] == '@' || (guchar)buf[i] == ']')
+            {
+                langend = i-1;
+                break;
+            }
+            else if (!(((guchar)buf[i] >= 'A' && (guchar)buf[i] <= 'Z') ||
+                       ((guchar)buf[i] >= 'a' && (guchar)buf[i] <= 'z'))) {
+                parse_error("Invalid character in language", parse, error);
+                return;
+            }
+        if ((guchar)buf[i] == '_') {
+            countrystart = i+1;
+            for (i = i+1; i < len; ++i)
+                if ((guchar)buf[i] == '.' ||
+                    (guchar)buf[i] == '@' || (guchar)buf[i] == ']')
+                {
+                    langend = i-1;
+                    break;
+                }
+            else if (!(((guchar)buf[i] >= 'A' && (guchar)buf[i] <= 'Z') ||
+                       ((guchar)buf[i] >= 'a' && (guchar)buf[i] <= 'z'))) {
+                parse_error("Invalid character in country", parse, error);
+                return;
+            }
+        }
+        if ((guchar)buf[i] == '.') {
+            for (i = i+1; i < len; ++i)
+                if ((guchar)buf[i] == '@' || (guchar)buf[i] == ']')
+                    break;
+            else if (!(((guchar)buf[i] >= 'A' && (guchar)buf[i] <= 'Z') ||
+                       ((guchar)buf[i] >= 'a' && (guchar)buf[i] <= 'z'))) {
+                parse_error("Invalid character in encoding", parse, error);
+                return;
+            }
+        }
+        if ((guchar)buf[i] == '@') {
+            modstart = i+1;
+            for (i = i+1; i < len; ++i)
+                if ((guchar)buf[i] == ']') {
+                    modend = i-1;
+                    break;
+                }
+            else if (!(((guchar)buf[i] >= 'A' && (guchar)buf[i] <= 'Z') ||
+                       ((guchar)buf[i] >= 'a' && (guchar)buf[i] <= 'z'))) {
+                parse_error("Invalid character in locale modifier",
+                            parse, error);
+                return;
+            }
+        }
+        ++i;
+    }
+
     /* find the = character */
-    for (i = keyend; i < len; ++i) {
+    for (; i < len; ++i) {
         if (buf[i] == '=') {
             eq = i;
             break;
         }
         else if (buf[i] != ' ') {
             parse_error("Invalid character in key name", parse, error);
-            return ;
+            return;
         }
     }
     if (i == len) {
@@ -497,16 +563,38 @@ static void parse_key_value(const gchar *buf, gulong len,
         return;
     }
 
+    if (langend < langstart)
+        match = OBT_DDPARSE_MATCH_FAIL;
+    else
+        match = OBT_DDPARSE_MATCH_NONE;
+    if (parse->language && langend >= langstart &&
+        strncmp(parse->language, buf+langstart, langend-langstart+1) == 0)
+    {
+        match = OBT_DDPARSE_MATCH_LANG;
+        if (parse->country && countryend >= countrystart &&
+            strncmp(parse->country, buf+countrystart,
+                    countryend-countrystart+1) == 0)
+            match = OBT_DDPARSE_MATCH_LANG_COUNTRY;
+
+        if (parse->modifier && modend >= modstart &&
+            strncmp(parse->modifier, buf+modstart,
+                    modend-modstart+1) == 0)
+            match += 1; /* its one up for LANG and for LANG_COUNTY */
+    }
+
     key = g_strndup(buf, keyend);
-    if (g_hash_table_lookup(parse->group->key_hash, key)) {
-        parse_error("Duplicate key found", parse, error);
-        g_free(key);
-        return;
+    if ((val = g_hash_table_lookup(parse->group->key_hash, key))) {
+        if (val->language_match >= match) {
+            /* found a better match already */
+            g_free(key);
+            return;
+        }
     }
-    g_print("Found key/value %s=%s.\n", key, buf+valstart);
+    /* g_print("Found key/value %s=%s\n", key, buf+valstart); */
     if (parse->group->value_func)
-        if (!parse->group->value_func(key, buf+valstart, parse, error)) {
-            parse_error("Unknown key", parse, error);
+        if (!parse->group->value_func(key, buf+valstart, match, parse, error))
+        {
+            /*parse_error("Unknown key", parse, error);*/
             g_free(key);
         }
 }
@@ -537,10 +625,13 @@ static gboolean parse_file(FILE *f, ObtDDParse *parse)
 }
 
 static gboolean parse_desktop_entry_value(gchar *key, const gchar *val,
+                                          ObtDDParseLangMatch match,
                                           ObtDDParse *parse, gboolean *error)
 {
     ObtDDParseValue v, *pv;
 
+    v.language_match = match;
+
     switch (key[0]) {
     case 'C':
         switch (key[1]) {
@@ -634,6 +725,13 @@ static gboolean parse_desktop_entry_value(gchar *key, const gchar *val,
         return FALSE;
     }
 
+    if (v.language_match && !(v.type == OBT_DDPARSE_LOCALESTRING ||
+                              v.type == OBT_DDPARSE_LOCALESTRINGS))
+    {
+        parse_error("Invalid localization on key", parse, error);
+        return FALSE;
+    }
+
     /* parse the value */
     switch (v.type) {
     case OBT_DDPARSE_EXEC: {
@@ -747,7 +845,10 @@ static gboolean parse_desktop_entry_value(gchar *key, const gchar *val,
     return TRUE;
 }
 
-GHashTable* obt_ddparse_file(const gchar *filename)
+GHashTable* obt_ddparse_file(const gchar *filename,
+                             const gchar *language,
+                             const gchar *country,
+                             const gchar *modifier)
 {
     ObtDDParse parse;
     ObtDDParseGroup *desktop_entry;
@@ -760,6 +861,9 @@ GHashTable* obt_ddparse_file(const gchar *filename)
     }
 
     parse.filename = filename;
+    parse.language = language;
+    parse.country = country;
+    parse.modifier = modifier;
     parse.lineno = 0;
     parse.group = NULL;
     parse.group_hash = g_hash_table_new_full(g_str_hash,
@@ -818,7 +922,7 @@ gchar* obt_ddparse_file_to_id(const gchar *filename)
 {
     gint len;
     const gchar *in;
-    gchar *out;
+    gchar *out, *out_start;
     gboolean sep;
 
     if (!g_utf8_validate(filename, -1, NULL)) {
@@ -829,9 +933,9 @@ gchar* obt_ddparse_file_to_id(const gchar *filename)
     len = strlen(filename) - 8;  /* 8 = strlen(".desktop") */
     g_assert(strcmp(filename+len, ".desktop") == 0);
 
-    out = g_new(char, len+1);
+    out_start = out = g_new(char, len+1);
     sep = TRUE;
-    for (in = filename; *in; ++in) {
+    for (in = filename; in < filename + len; ) {
         gchar *next;
 
         if (*in == '/') {
@@ -841,6 +945,7 @@ gchar* obt_ddparse_file_to_id(const gchar *filename)
                 ++out;
             }
             sep = TRUE;
+            ++in;
         }
         else {
             /* everything else is copied as is */
@@ -850,7 +955,9 @@ gchar* obt_ddparse_file_to_id(const gchar *filename)
                 ++out;
                 ++in;
             }
+            sep = FALSE;
         }
     }
-    return out;
+    *out = '\0';
+    return out_start;
 }
index 050aaa9609c7c83748f6d2f8d960b43838e3dbf3..ce887f9df13e7ac09f7e8c6a964c6b79a73570b9 100644 (file)
@@ -33,6 +33,15 @@ typedef enum {
     OBT_DDPARSE_NUM_VALUE_TYPES
 } ObtDDParseValueType;
 
+typedef enum {
+    OBT_DDPARSE_MATCH_NONE = 0,
+    OBT_DDPARSE_MATCH_FAIL,
+    OBT_DDPARSE_MATCH_LANG,
+    OBT_DDPARSE_MATCH_LANG_MODIFIER,
+    OBT_DDPARSE_MATCH_LANG_COUNTRY,
+    OBT_DDPARSE_MATCH_LANG_COUNTRY_MODIFIER
+} ObtDDParseLangMatch;
+
 typedef struct _ObtDDParseValue {
     ObtDDParseValueType type;
     union _ObtDDParseValueValue {
@@ -46,13 +55,17 @@ typedef struct _ObtDDParseValue {
         guint enumerable;
         guint environments; /*!< A mask of flags from ObtLinkEnvMask */
     } value;
+    ObtDDParseLangMatch language_match;
 } ObtDDParseValue;
 
 /*! Parse a .desktop file.
   @param filename The full path to the .desktop file to be read.
   @return Returns a hash table where the keys are groups, and the values are
     ObtDDParseGroups */
-GHashTable* obt_ddparse_file(const gchar *filename);
+GHashTable* obt_ddparse_file(const gchar *filename,
+                             const gchar *language,
+                             const gchar *country,
+                             const gchar *modifier);
 
 /*! Get the keys in a group from a .desktop file.
   The group comes from the hash table returned by obt_ddparse_file.
index eb24a113fdb4684fb6ac40e21d72e7943888f100..c54b782675033efe85d146879dd16d8ed05d1496 100644 (file)
@@ -24,6 +24,8 @@
 struct _ObtLink {
     guint ref;
 
+    gchar *path; /*!< The path to the file where the link came from */
+
     ObtLinkType type;
     gchar *name; /*!< Specific name for the object (eg Firefox) */
     gboolean display; /*<! When false, do not display this link in menus or
@@ -62,19 +64,18 @@ struct _ObtLink {
     } d;
 };
 
-ObtLink* obt_link_from_ddfile(const gchar *basepath, const gchar *filename,
-                              ObtPaths *p)
+ObtLink* obt_link_from_ddfile(const gchar *path, ObtPaths *p,
+                              const gchar *language,
+                              const gchar *country,
+                              const gchar *modifier)
 {
     ObtLink *link;
     GHashTable *groups, *keys;
     ObtDDParseGroup *g;
     ObtDDParseValue *v;
-    gchar *path;
 
     /* parse the file, and get a hash table of the groups */
-    path = g_strconcat(basepath, filename, NULL);
-    groups = obt_ddparse_file(path);
-    g_free(path);
+    groups = obt_ddparse_file(path, language, country, modifier);
     if (!groups) return NULL; /* parsing failed */
 
     /* grab the Desktop Entry group */
@@ -86,6 +87,7 @@ ObtLink* obt_link_from_ddfile(const gchar *basepath, const gchar *filename,
     /* build the ObtLink (we steal all strings from the parser) */
     link = g_slice_new0(ObtLink);
     link->ref = 1;
+    link->path = g_strdup(path);
     link->display = TRUE;
 
     v = g_hash_table_lookup(keys, "Type");
@@ -240,3 +242,13 @@ const GQuark* obt_link_app_categories(ObtLink *e, gulong *n)
     *n = e->d.app.n_categories;
     return e->d.app.categories;
 }
+
+const gchar *obt_link_source_file(ObtLink *e)
+{
+    return e->path;
+}
+
+gchar* obt_link_id_from_ddfile(const gchar *filename)
+{
+    return obt_ddparse_file_to_id(filename);
+}
index dff11f3c580d0067a2bb4f5364d10ff87967f7e9..749514de5ff71b7fda4d9e873566eeb69bc9918f 100644 (file)
@@ -63,17 +63,29 @@ typedef enum {
 typedef struct _ObtLink     ObtLink;
 
 /*! Parse a .desktop (dd) file.
-  @param basepath The base directory in which to read the file.
-  @param filename The full path to the .desktop file _relative to_ basepath.
-    It must be in basepath or a subdirectory of it.
+  @param path The full path to the .desktop file.
   @param o An ObtPaths structure, which contains the executable paths.
 */
-ObtLink* obt_link_from_ddfile(const gchar *basepath, const gchar *filename,
-                              struct _ObtPaths *p);
+ObtLink* obt_link_from_ddfile(const gchar *path,
+                              struct _ObtPaths *p,
+                              const gchar *language,
+                              const gchar *country,
+                              const gchar *modifier);
+
+/*! Determine the identifier for a .desktop (dd) file.
+  @param filename The full path to the .desktop file _relative to_ some
+    basepath.  For instance, if the desktop file is
+    /usr/share/applications/foo/bar.desktop, and the basepath is
+    /usr/share/applications, then the filename would be 'foo/bar.desktop'.
+    The filename must end with ".desktop" and be encoded in utf8.
+*/
+gchar* obt_link_id_from_ddfile(const gchar *filename);
 
 void obt_link_ref(ObtLink *e);
 void obt_link_unref(ObtLink *e);
 
+const gchar *obt_link_source_file(ObtLink *e);
+
 /*! Returns TRUE if the file exists but says it should be ignored, with
     the Hidden flag.  No other functions can be used for the ObtLink
     in this case. */
index 1039b3483d6c606cbd12f403702fcde9d38fefec..9d913da1f81a7d1e1368b74411c3e7f683f02a98 100644 (file)
 #include "obt/paths.h"
 #include "obt/watch.h"
 
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif
+
+typedef struct _ObtLinkBaseEntry ObtLinkBaseEntry;
+
+struct _ObtLinkBaseEntry {
+    /*! Links come from a set of paths.  Links found in earlier paths get lower
+      priority values (higher precedence).  This is the index in that set of
+      paths of the base directory under which the link was found. */
+    gint priority;
+    ObtLink *link;
+};
+
 struct _ObtLinkBase {
     gint ref;
 
+    const gchar *language;
+    const gchar *country;
+    const gchar *modifier;
+
+    ObtPaths *paths;
     ObtWatch *watch;
+    /*! This holds a GSList of ObtLinkBaseEntrys sorted by priority in
+      increasing order (by precedence in decreasing order). */
     GHashTable *base;
+    /*! This holds the paths in which we look for links, and the data is an
+      integer that is the priority of that directory. */
+    GHashTable *path_to_priority;
 };
 
-static void func(ObtWatch *w, const gchar *subpath, ObtWatchNotifyType type,
-                 gpointer data)
+static void base_entry_free(ObtLinkBaseEntry *e)
+{
+    obt_link_unref(e->link);
+    g_slice_free(ObtLinkBaseEntry, e);
+}
+
+static void base_entry_list_free(GSList *list)
+{
+    GSList *it;
+    for (it = list; it; it = g_slist_next(it))
+        base_entry_free(it->data);
+    g_slist_free(list);
+}
+
+static GSList* find_base_entry_path(GSList *list, const gchar *full_path)
 {
+    GSList *it;
+    for (it = list; it; it = g_slist_next(it)) {
+        ObtLinkBaseEntry *e = it->data;
+        if (strcmp(obt_link_source_file(e->link), full_path) == 0)
+            break;
+    }
+    return it;
 }
 
-ObtLinkBase* obt_linkbase_new(ObtPaths *paths)
+/*! Finds the first entry in the list with a priority number >= @priority. */
+static GSList* find_base_entry_priority(GSList *list, gint priority)
 {
-    ObtLinkBase *b;
     GSList *it;
+    for (it = list; it; it = g_slist_next(it)) {
+        ObtLinkBaseEntry *e = it->data;
+        if (e->priority >= priority)
+            break;
+    }
+    return it;
+}
+
+static void update(ObtWatch *w, const gchar *base_path,
+                   const gchar *sub_path,
+                   const gchar *full_path,
+                   ObtWatchNotifyType type,
+                   gpointer data)
+{
+    ObtLinkBase *self = data;
+    gchar *id;
+    GSList *list, *it;
+    gint *priority;
+    gboolean add = FALSE;
+
+    if (!g_str_has_suffix(sub_path, ".desktop"))
+        return; /* ignore non-.desktop files */
+
+    id = obt_link_id_from_ddfile(sub_path);
+    list = g_hash_table_lookup(self->base, id);
+
+    switch (type) {
+    case OBT_WATCH_SELF_REMOVED:
+        break;
+    case OBT_WATCH_REMOVED:
+        it = find_base_entry_path(list, full_path);
+        list = g_slist_delete_link(list, it);
+        base_entry_free(it->data);
+
+        /* this will free 'id' */
+        g_hash_table_insert(self->base, id, list);
+        id = NULL;
+        break;
+    case OBT_WATCH_MODIFIED:
+        it = find_base_entry_path(list, full_path);
+        list = g_slist_delete_link(list, it);
+        base_entry_free(it->data);
+        add = TRUE; /* this will put the modified list into the hash table */
+        break;
+    case OBT_WATCH_ADDED:
+        priority = g_hash_table_lookup(self->path_to_priority, base_path);
+        add = TRUE;
+
+        /* find the first position in the list with a higher priority value */
+        if ((it = find_base_entry_priority(list, *priority))) {
+            const ObtLinkBaseEntry *e = it->data;
+            if (e->priority == *priority) {
+                /* already exists */
+                add = FALSE;
+            }
+        }
+        break;
+    }
+
+    if (add) {
+        ObtLinkBaseEntry *e = g_slice_new(ObtLinkBaseEntry);
+        e->priority = *priority;
+        e->link = obt_link_from_ddfile(full_path, self->paths,
+                                       self->language, self->country,
+                                       self->modifier);
+        list = g_slist_insert_before(list, it, e);
+
+        /* this will free 'id' */
+        g_hash_table_insert(self->base, id, list);
+        id = NULL;
+    }
+
+    g_free(id);
+}
+
+ObtLinkBase* obt_linkbase_new(ObtPaths *paths, const gchar *locale)
+{
+    ObtLinkBase *self;
+    GSList *it;
+    gint priority;
+    gint i;
     
-    b = g_slice_new(ObtLinkBase);
-    b->watch = obt_watch_new();
-    b->base = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
-                                    (GDestroyNotify)obt_link_unref);
+    self = g_slice_new0(ObtLinkBase);
+    self->watch = obt_watch_new();
+    self->base = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+                                    (GDestroyNotify)base_entry_list_free);
+    self->path_to_priority = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                                g_free, g_free);
+    self->paths = paths;
+    obt_paths_ref(paths);
+
+    for (i = 0; ; ++i)
+        if (!locale[i] || locale[i] == '_' || locale[i] == '.' ||
+            locale[i] == '@')
+        {
+            self->language = g_strndup(locale, i);
+            break;
+        }
+        else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
+                 ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
+            break;
+    if (self->language && locale[i] == '_') {
+        locale += i+1;
+        for (i = 0; ; ++i)
+            if (!locale[i] || locale[i] == '.' || locale[i] == '@') {
+                self->country = g_strndup(locale, i);
+                break;
+            }
+            else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
+                     ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
+                break;
+    }
+    if (self->country && locale[i] == '.')
+        for (; ; ++i)
+            if (!locale[i] || locale[i] == '@')
+                break;
+            else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
+                     ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
+                break;
+    if (self->country && locale[i] == '@') {
+        locale += i+1;
+        for (i = 0; ; ++i)
+            if (!locale[i]) {
+                self->modifier = g_strndup(locale, i);
+                break;
+            }
+            else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
+                     ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
+                break;
+    }
 
+    priority = 0;
     for (it = obt_paths_data_dirs(paths); it; it = g_slist_next(it)) {
-        gchar *p;
-        p = g_strconcat(it->data, "/applications", NULL);
-        obt_watch_add(b->watch, p, FALSE, func, b);
+        if (!g_hash_table_lookup(self->path_to_priority, it->data)) {
+            gchar *base_path;
+            gint *pri;
+
+            base_path = g_build_filename(it->data, "applications", NULL);
+
+            /* add to the hash table before adding the watch. the new watch
+               will be calling our update handler, immediately with any
+               files present in the directory */
+            pri = g_new(gint, 1);
+            *pri = priority;
+            g_hash_table_insert(self->path_to_priority,
+                                g_strdup(base_path), pri);
+
+            obt_watch_add(self->watch, base_path, FALSE, update, self);
+
+            ++priority;
+        }
     }
-    return b;
+    return self;
 }
 
-void obt_linkbase_ref(ObtLinkBase *lb)
+void obt_linkbase_ref(ObtLinkBase *self)
 {
-    ++lb->ref;
+    ++self->ref;
 }
 
-void obt_linkbase_unref(ObtLinkBase *lb)
+void obt_linkbase_unref(ObtLinkBase *self)
 {
-    if (--lb->ref < 1) {
-        obt_watch_unref(lb->watch);
-        g_hash_table_unref(lb->base);
-        g_slice_free(ObtLinkBase, lb);
+    if (--self->ref < 1) {
+        obt_watch_unref(self->watch);
+        g_hash_table_unref(self->path_to_priority);
+        g_hash_table_unref(self->base);
+        obt_paths_unref(self->paths);
+        g_slice_free(ObtLinkBase, self);
     }
 }
index 76bf4433b7bbac5a2ca5086d57381979d180b5e9..57602788afd42dc0ecc0f32e868c899f1129172d 100644 (file)
@@ -27,8 +27,11 @@ G_BEGIN_DECLS
 
 typedef struct _ObtLinkBase ObtLinkBase;
 
-/*! Create a new database of ObtLinks. */
-ObtLinkBase* obt_linkbase_new(struct _ObtPaths *paths);
+/*! Create a new database of ObtLinks.
+  @param paths An ObtPaths structure.
+  @param locale The value of LC_MESSAGES.
+*/
+ObtLinkBase* obt_linkbase_new(struct _ObtPaths *paths, const gchar *locale);
 void obt_linkbase_ref(ObtLinkBase *lb);
 void obt_linkbase_unref(ObtLinkBase *lb);
 
index 63a2ccef7771091fcef36e27ae2950e4fe0f205b..6e1971ac44339a9fb841cf081365322b80fcf322 100644 (file)
@@ -28,7 +28,8 @@ typedef struct _ObtWatchTarget ObtWatchTarget;
 
 /*! Callback function for the system-specific GSource to alert us to changes.
 */
-typedef void (*ObtWatchNotifyFunc)(const gchar *path, gpointer target,
+typedef void (*ObtWatchNotifyFunc)(const gchar *sub_path,
+                                   const gchar *full_path, gpointer target,
                                    ObtWatchNotifyType type);
 
 
@@ -71,8 +72,8 @@ struct _ObtWatchTarget {
 };
 
 static void target_free(ObtWatchTarget *t);
-static void target_notify(const gchar *const path, gpointer target,
-                          ObtWatchNotifyType type);
+static void target_notify(const gchar *sub_path, const gchar *full_path,
+                          gpointer target, ObtWatchNotifyType type);
 
 ObtWatch* obt_watch_new()
 {
@@ -150,13 +151,13 @@ void obt_watch_remove(ObtWatch *w, const gchar *path)
     g_hash_table_remove(w->targets_by_path, path);
 }
 
-static void target_notify(const gchar *const path, gpointer target,
-                          ObtWatchNotifyType type)
+static void target_notify(const gchar *sub_path, const gchar *full_path,
+                          gpointer target, ObtWatchNotifyType type)
 {
     ObtWatchTarget *t = target;
     if (type == OBT_WATCH_SELF_REMOVED) {
         /* this also calls target_free */
         g_hash_table_remove(t->w->targets_by_path, t->base_path);
     }
-    t->func(t->w, path, type, t->data);
+    t->func(t->w, t->base_path, sub_path, full_path, type, t->data);
 }
index d46e4f82a376211ebdf689cfb42ab0fe87e862f4..4df994dae26f1dec5816a9715b3f6559cb0e9240 100644 (file)
@@ -26,7 +26,14 @@ G_BEGIN_DECLS
 typedef struct _ObtWatch ObtWatch;
 typedef enum _ObtWatchNotifyType ObtWatchNotifyType;
 
-typedef void (*ObtWatchFunc)(ObtWatch *w, const gchar *subpath,
+/*! Notification function for changes in a watch file/directory.
+  @param base_path is the path to the watch target (file or directory).
+  @param sub_path is a path relative to the watched directory.  If the
+    notification is about the watch target itself, the subpath will be
+    an empty string.
+*/
+typedef void (*ObtWatchFunc)(ObtWatch *w, const gchar *base_path,
+                             const gchar *sub_path, const gchar *full_path,
                              ObtWatchNotifyType type, gpointer data);
 
 enum _ObtWatchNotifyType {
index 3a7d3da47494885fe46fcc65a384c52bf6aa6c42..f9a5649afa7a350ebd11bc4c0576ea11fe8c36b9 100644 (file)
@@ -39,7 +39,8 @@ typedef struct _InoTarget InoTarget;
 /*! Callback function in the watch general system.
   Matches definition in watch.c
 */
-typedef void (*ObtWatchNotifyFunc)(const gchar *path, gpointer target,
+typedef void (*ObtWatchNotifyFunc)(const gchar *sub_path,
+                                   const gchar *full_path, gpointer target,
                                    ObtWatchNotifyType type);
 
 struct _InoSource {
@@ -54,6 +55,8 @@ struct _InoSource {
 struct _InoTarget {
     gint key;
     gchar *path;
+    guint base_len; /* the length of the prefix of path which is the
+                       target's path */
     gpointer watch_target;
     gboolean is_dir;
     gboolean watch_hidden;
@@ -68,6 +71,8 @@ static gint add_target(GSource *source, InoTarget *parent,
                        gpointer target);
 static void remove_target(GSource *source, InoTarget *target);
 static void target_free(InoTarget *target);
+static void notify_target(GSource *source, InoTarget *ino_target,
+                          const gchar *path, ObtWatchNotifyType type);
 
 static GSourceFuncs source_funcs = {
     source_prepare,
@@ -285,7 +290,7 @@ static gboolean source_read(GSource *source, GSourceFunc cb, gpointer data)
                     if (cb) cb(data);
 
                     /* call the WatchNotify callback */
-                    ino_source->notify(full_path, t->watch_target, type);
+                    notify_target(source, t, full_path, type);
                 }
 
                 g_free(full_path);
@@ -307,8 +312,8 @@ static void source_finalize(GSource *source)
 }
 
 static gint add_target(GSource *source, InoTarget *parent,
-                       const gchar *path, gboolean watch_hidden,
-                       gpointer target)
+                       const gchar *path,
+                       gboolean watch_hidden, gpointer target)
 {
     InoSource *ino_source;
     InoTarget *ino_target;
@@ -339,6 +344,7 @@ static gint add_target(GSource *source, InoTarget *parent,
         ino_target = g_slice_new(InoTarget);
         ino_target->key = key;
         ino_target->path = g_strdup(path);
+        ino_target->base_len = (parent ? parent->base_len : strlen(path));
         ino_target->is_dir = is_dir;
         ino_target->watch_hidden = watch_hidden;
         ino_target->watch_target = target;
@@ -362,12 +368,12 @@ static gint add_target(GSource *source, InoTarget *parent,
 
                     subpath = g_build_filename(path, name, NULL);
                     if (g_file_test(subpath, G_FILE_TEST_IS_DIR))
-                        add_target(source, ino_target, subpath, watch_hidden,
-                                   target);
+                        add_target(source, ino_target, subpath,
+                                   watch_hidden, target);
                     else
                         /* notify for each file in the directory on startup */
-                        ino_source->notify(subpath, ino_target->watch_target,
-                                           OBT_WATCH_ADDED);
+                        notify_target(source, ino_target, subpath,
+                                      OBT_WATCH_ADDED);
                     g_free(subpath);
                 }
             }
@@ -393,4 +399,14 @@ static void target_free(InoTarget *target)
     g_slice_free(InoTarget, target);
 }
 
+static void notify_target(GSource *source, InoTarget *ino_target,
+                          const gchar *path, ObtWatchNotifyType type)
+{
+    InoSource *ino_source = (InoSource*)source;
+    ino_source->notify(path + ino_target->base_len,
+                       path,
+                       ino_target->watch_target,
+                       type);
+}
+
 #endif