]> icculus.org git repositories - dana/openbox.git/blob - obt/linkbase.c
don't follow pointers after freeing them
[dana/openbox.git] / obt / linkbase.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2  
3    obt/linkbase.c for the Openbox window manager
4    Copyright (c) 2010        Dana Jansens
5  
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10  
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15  
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "obt/linkbase.h"
20 #include "obt/link.h"
21 #include "obt/paths.h"
22 #include "obt/watch.h"
23
24 #ifdef HAVE_STRING_H
25 # include <string.h>
26 #endif
27
28 typedef struct _ObtLinkBaseEntry    ObtLinkBaseEntry;
29 typedef struct _ObtLinkBaseCategory ObtLinkBaseCategory;
30
31 struct _ObtLinkBaseEntry {
32     /*! Links come from a set of paths.  Links found in earlier paths get lower
33       priority values (higher precedence).  This is the index in that set of
34       paths of the base directory under which the link was found. */
35     gint priority;
36     ObtLink *link;
37 };
38
39 struct _ObtLinkBaseCategory {
40     GQuark cat;
41     GSList *links;
42 };
43
44 struct _ObtLinkBase {
45     gint ref;
46
47     /*! A bitflag of values from ObtLinkEnvFlags indicating which environments
48       are to be considered active. */
49     guint environments;
50
51     const gchar *language;
52     const gchar *country;
53     const gchar *modifier;
54
55     ObtPaths *paths;
56     ObtWatch *watch;
57     /*! This holds a GSList of ObtLinkBaseEntrys sorted by priority in
58       increasing order (by precedence in decreasing order). */
59     GHashTable *base;
60     /*! This holds the paths in which we look for links, and the data is an
61       integer that is the priority of that directory. */
62     GHashTable *path_to_priority;
63
64     /*! This maps GQuark main categories to ObtLinkBaseCategory objects,
65       containing lists of ObtLink objects found in
66       the category. The ObtLink objects are not reffed to be placed in this
67       structure since they will always be in the base hash table as well. So
68       they are not unreffed when they are removed. */
69     GHashTable *categories;
70
71     ObtLinkBaseUpdateFunc update_func;
72     gpointer update_data;
73 };
74
75 static void base_entry_free(ObtLinkBaseEntry *e)
76 {
77     obt_link_unref(e->link);
78     g_slice_free(ObtLinkBaseEntry, e);
79 }
80
81 static void base_entry_list_free(gpointer key, gpointer value, gpointer data)
82 {
83     GSList *it, *list; (void)key; (void)data;
84
85     list = value;
86     for (it = list; it; it = g_slist_next(it))
87         base_entry_free(it->data);
88     g_slist_free(list);
89 }
90
91 static GSList* find_base_entry_path(GSList *list, const gchar *full_path)
92 {
93     GSList *it;
94     for (it = list; it; it = g_slist_next(it)) {
95         ObtLinkBaseEntry *e = it->data;
96         if (strcmp(obt_link_source_file(e->link), full_path) == 0)
97             break;
98     }
99     return it;
100 }
101
102 /*! Finds the first entry in the list with a priority number >= @priority. */
103 static GSList* find_base_entry_priority(GSList *list, gint priority)
104 {
105     GSList *it;
106     for (it = list; it; it = g_slist_next(it)) {
107         ObtLinkBaseEntry *e = it->data;
108         if (e->priority >= priority)
109             break;
110     }
111     return it;
112 }
113
114 static void category_add(ObtLinkBase *lb, GQuark cat, ObtLink *link)
115 {
116     ObtLinkBaseCategory *lc;
117
118     lc = g_hash_table_lookup(lb->categories, &cat);
119     if (!lc) {
120         lc = g_slice_new(ObtLinkBaseCategory);
121         lc->cat = cat;
122         lc->links = NULL;
123         g_hash_table_insert(lb->categories, &lc->cat, lc);
124     }
125     lc->links = g_slist_prepend(lc->links, link);
126 }
127
128 static void category_remove(ObtLinkBase *lb, GQuark cat, ObtLink *link)
129 {
130     ObtLinkBaseCategory *lc;
131     GSList *it;
132
133     lc = g_hash_table_lookup(lb->categories, &cat);
134
135     it = lc->links;
136     while (it->data != link)
137         it = g_slist_next(it);
138     lc->links = g_slist_delete_link(lc->links, it);
139     if (!lc->links)
140         g_hash_table_remove(lb->categories, &cat);
141 }
142
143 static void category_free(ObtLinkBaseCategory *lc)
144 {
145     g_slist_free(lc->links);
146     g_slice_free(ObtLinkBaseCategory, lc);
147 }
148
149 /*! Called when a change happens in the filesystem. */
150 static void update(ObtWatch *w, const gchar *base_path,
151                    const gchar *sub_path,
152                    const gchar *full_path,
153                    ObtWatchNotifyType type,
154                    gpointer data)
155 {
156     ObtLinkBase *self = data;
157     gchar *id;
158     GSList *list, *it;
159     gint *priority;
160     gboolean add = FALSE;
161
162     if (!g_str_has_suffix(sub_path, ".desktop"))
163         return; /* ignore non-.desktop files */
164
165     id = obt_link_id_from_ddfile(sub_path);
166     list = g_hash_table_lookup(self->base, id);
167
168     switch (type) {
169     case OBT_WATCH_SELF_REMOVED:
170         break;
171     case OBT_WATCH_REMOVED:
172         it = find_base_entry_path(list, full_path);
173         if (it) {
174             /* it may be false if the link was skipped during the add because
175                it did not want to be displayed */
176
177             ObtLink *link = it->data;
178
179             if (self->update_func)
180                 self->update_func(
181                     self, OBT_LINKBASE_REMOVED, link, self->update_data);
182
183             if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
184                 const GQuark *cats;
185                 gulong i, n;
186
187                 cats = obt_link_app_categories(link, &n);
188                 for (i = 0; i < n; ++i)
189                     category_remove(self, cats[i], link);
190             }
191
192             base_entry_free(it->data);
193             list = g_slist_delete_link(list, it);
194
195             if (list) {
196                 /* this will free 'id' */
197                 g_hash_table_insert(self->base, id, list);
198                 id = NULL;
199             }
200             else {
201                 /* the value is already freed by deleting it above so we don't
202                    need to free it here. id will still need to be freed tho. */
203                 g_hash_table_remove(self->base, id);
204             }
205         }
206         break;
207     case OBT_WATCH_MODIFIED:
208         it = find_base_entry_path(list, full_path);
209         if (it) {
210             /* it may be false if the link was skipped during the add because
211                it did not want to be displayed */
212
213             ObtLink *link = it->data;
214
215             if (self->update_func)
216                 self->update_func(
217                     self, OBT_LINKBASE_REMOVED, link, self->update_data);
218
219             if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
220                 const GQuark *cats;
221                 gulong i, n;
222
223                 cats = obt_link_app_categories(link, &n);
224                 for (i = 0; i < n; ++i)
225                     category_remove(self, cats[i], link);
226             }
227
228             base_entry_free(it->data);
229             list = g_slist_delete_link(list, it);
230             /* this will put the modified list into the hash table */
231             add = TRUE;
232         }
233         break;
234     case OBT_WATCH_ADDED:
235         priority = g_hash_table_lookup(self->path_to_priority, base_path);
236         add = TRUE;
237
238         /* find the first position in the list with a higher priority value */
239         if ((it = find_base_entry_priority(list, *priority))) {
240             const ObtLinkBaseEntry *e = it->data;
241             if (e->priority == *priority) {
242                 /* already exists */
243                 add = FALSE;
244             }
245         }
246         break;
247     }
248
249     if (add) {
250         ObtLink *link;
251
252         link = obt_link_from_ddfile(full_path, self->paths,
253                                     self->language, self->country,
254                                     self->modifier);
255         if (link) {
256             if (!obt_link_display(link, self->environments)) {
257                 obt_link_unref(link);
258             }
259             else {
260                 ObtLinkBaseEntry *e = g_slice_new(ObtLinkBaseEntry);
261                 e->priority = *priority;
262                 e->link = link;
263
264                 if (self->update_func)
265                     self->update_func(
266                         self, OBT_LINKBASE_ADDED, link, self->update_data);
267
268                 list = g_slist_insert_before(list, it, e);
269
270                 /* this will free 'id' */
271                 g_hash_table_insert(self->base, id, list);
272                 id = NULL;
273
274                 if (obt_link_type(link) == OBT_LINK_TYPE_APPLICATION) {
275                     const GQuark *cats;
276                     gulong i, n;
277
278                     cats = obt_link_app_categories(link, &n);
279                     for (i = 0; i < n; ++i)
280                         category_add(self, cats[i], link);
281                 }
282             }
283         }
284     }
285
286     g_free(id);
287 }
288
289 ObtLinkBase* obt_linkbase_new(ObtPaths *paths, const gchar *locale,
290                               guint environments)
291 {
292     ObtLinkBase *self;
293     GSList *it;
294     gint priority;
295     gint i;
296     
297     self = g_slice_new0(ObtLinkBase);
298     self->environments = environments;
299     self->watch = obt_watch_new();
300     self->base = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
301     self->path_to_priority = g_hash_table_new_full(
302         g_str_hash, g_str_equal, g_free, g_free);
303     self->categories = g_hash_table_new_full(
304         g_int_hash, g_int_equal, NULL, (GDestroyNotify)category_free);
305     self->paths = paths;
306     obt_paths_ref(paths);
307
308     /* parse the locale string to determine the language, country, and
309        modifier settings */
310     for (i = 0; ; ++i)
311         if (!locale[i] || locale[i] == '_' || locale[i] == '.' ||
312             locale[i] == '@')
313         {
314             self->language = g_strndup(locale, i);
315             break;
316         }
317         else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
318                  ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
319             break;
320     if (self->language && locale[i] == '_') {
321         locale += i+1;
322         for (i = 0; ; ++i)
323             if (!locale[i] || locale[i] == '.' || locale[i] == '@') {
324                 self->country = g_strndup(locale, i);
325                 break;
326             }
327             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
328                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
329                 break;
330     }
331     if (self->country && locale[i] == '.')
332         for (; ; ++i)
333             if (!locale[i] || locale[i] == '@')
334                 break;
335             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
336                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
337                 break;
338     if (self->country && locale[i] == '@') {
339         locale += i+1;
340         for (i = 0; ; ++i)
341             if (!locale[i]) {
342                 self->modifier = g_strndup(locale, i);
343                 break;
344             }
345             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
346                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
347                 break;
348     }
349
350     /* run through each directory, foo, in the XDG_DATA_DIRS, and add
351        foo/applications to the list of directories to watch here, with
352        increasing priority (decreasing importance). */
353     priority = 0;
354     for (it = obt_paths_data_dirs(paths); it; it = g_slist_next(it)) {
355         if (!g_hash_table_lookup(self->path_to_priority, it->data)) {
356             gchar *base_path;
357             gint *pri;
358
359             base_path = g_build_filename(it->data, "applications", NULL);
360
361             /* add to the hash table before adding the watch. the new watch
362                will be calling our update handler, immediately with any
363                files present in the directory */
364             pri = g_new(gint, 1);
365             *pri = priority;
366             g_hash_table_insert(self->path_to_priority,
367                                 g_strdup(base_path), pri);
368
369             obt_watch_add(self->watch, base_path, FALSE, update, self);
370
371             ++priority;
372         }
373     }
374     return self;
375 }
376
377 void obt_linkbase_ref(ObtLinkBase *self)
378 {
379     ++self->ref;
380 }
381
382 void obt_linkbase_unref(ObtLinkBase *self)
383 {
384     if (--self->ref < 1) {
385         /* free all the values in the hash table
386            we can't do this with a value_destroy_function in the hash table,
387            because when we replace values, we are doing so with the same list
388            (modified), and that would cause it to free the list we are putting
389            back in.
390          */
391         g_hash_table_foreach(self->base, base_entry_list_free, NULL);
392
393         obt_watch_unref(self->watch);
394         g_hash_table_unref(self->categories);
395         g_hash_table_unref(self->path_to_priority);
396         g_hash_table_unref(self->base);
397         obt_paths_unref(self->paths);
398         g_slice_free(ObtLinkBase, self);
399     }
400 }
401
402 void obt_linkbase_set_update_func(ObtLinkBase *lb, ObtLinkBaseUpdateFunc func,
403                                   gpointer data)
404 {
405     lb->update_func = func;
406     lb->update_data = data;
407 }
408
409 GSList *obt_linkbase_category(ObtLinkBase *lb, GQuark category)
410 {
411     ObtLinkBaseCategory *cat;
412
413     cat = g_hash_table_lookup(lb->categories, &category);
414     if (!cat) return NULL;
415     else      return cat->links;
416 }