add a comment
[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
30 struct _ObtLinkBaseEntry {
31     /*! Links come from a set of paths.  Links found in earlier paths get lower
32       priority values (higher precedence).  This is the index in that set of
33       paths of the base directory under which the link was found. */
34     gint priority;
35     ObtLink *link;
36 };
37
38 struct _ObtLinkBase {
39     gint ref;
40
41     const gchar *language;
42     const gchar *country;
43     const gchar *modifier;
44
45     ObtPaths *paths;
46     ObtWatch *watch;
47     /*! This holds a GSList of ObtLinkBaseEntrys sorted by priority in
48       increasing order (by precedence in decreasing order). */
49     GHashTable *base;
50     /*! This holds the paths in which we look for links, and the data is an
51       integer that is the priority of that directory. */
52     GHashTable *path_to_priority;
53 };
54
55 static void base_entry_free(ObtLinkBaseEntry *e)
56 {
57     obt_link_unref(e->link);
58     g_slice_free(ObtLinkBaseEntry, e);
59 }
60
61 static void base_entry_list_free(GSList *list)
62 {
63     GSList *it;
64     for (it = list; it; it = g_slist_next(it))
65         base_entry_free(it->data);
66     g_slist_free(list);
67 }
68
69 static GSList* find_base_entry_path(GSList *list, const gchar *full_path)
70 {
71     GSList *it;
72     for (it = list; it; it = g_slist_next(it)) {
73         ObtLinkBaseEntry *e = it->data;
74         if (strcmp(obt_link_source_file(e->link), full_path) == 0)
75             break;
76     }
77     return it;
78 }
79
80 /*! Finds the first entry in the list with a priority number >= @priority. */
81 static GSList* find_base_entry_priority(GSList *list, gint priority)
82 {
83     GSList *it;
84     for (it = list; it; it = g_slist_next(it)) {
85         ObtLinkBaseEntry *e = it->data;
86         if (e->priority >= priority)
87             break;
88     }
89     return it;
90 }
91
92 /*! Called when a change happens in the filesystem. */
93 static void update(ObtWatch *w, const gchar *base_path,
94                    const gchar *sub_path,
95                    const gchar *full_path,
96                    ObtWatchNotifyType type,
97                    gpointer data)
98 {
99     ObtLinkBase *self = data;
100     gchar *id;
101     GSList *list, *it;
102     gint *priority;
103     gboolean add = FALSE;
104
105     if (!g_str_has_suffix(sub_path, ".desktop"))
106         return; /* ignore non-.desktop files */
107
108     id = obt_link_id_from_ddfile(sub_path);
109     list = g_hash_table_lookup(self->base, id);
110
111     switch (type) {
112     case OBT_WATCH_SELF_REMOVED:
113         break;
114     case OBT_WATCH_REMOVED:
115         it = find_base_entry_path(list, full_path);
116         list = g_slist_delete_link(list, it);
117         base_entry_free(it->data);
118
119         /* this will free 'id' */
120         g_hash_table_insert(self->base, id, list);
121         id = NULL;
122         break;
123     case OBT_WATCH_MODIFIED:
124         it = find_base_entry_path(list, full_path);
125         list = g_slist_delete_link(list, it);
126         base_entry_free(it->data);
127         add = TRUE; /* this will put the modified list into the hash table */
128         break;
129     case OBT_WATCH_ADDED:
130         priority = g_hash_table_lookup(self->path_to_priority, base_path);
131         add = TRUE;
132
133         /* find the first position in the list with a higher priority value */
134         if ((it = find_base_entry_priority(list, *priority))) {
135             const ObtLinkBaseEntry *e = it->data;
136             if (e->priority == *priority) {
137                 /* already exists */
138                 add = FALSE;
139             }
140         }
141         break;
142     }
143
144     if (add) {
145         ObtLinkBaseEntry *e = g_slice_new(ObtLinkBaseEntry);
146         e->priority = *priority;
147         e->link = obt_link_from_ddfile(full_path, self->paths,
148                                        self->language, self->country,
149                                        self->modifier);
150         list = g_slist_insert_before(list, it, e);
151
152         /* this will free 'id' */
153         g_hash_table_insert(self->base, id, list);
154         id = NULL;
155     }
156
157     g_free(id);
158 }
159
160 ObtLinkBase* obt_linkbase_new(ObtPaths *paths, const gchar *locale)
161 {
162     ObtLinkBase *self;
163     GSList *it;
164     gint priority;
165     gint i;
166     
167     self = g_slice_new0(ObtLinkBase);
168     self->watch = obt_watch_new();
169     self->base = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
170                                     (GDestroyNotify)base_entry_list_free);
171     self->path_to_priority = g_hash_table_new_full(g_str_hash, g_str_equal,
172                                                 g_free, g_free);
173     self->paths = paths;
174     obt_paths_ref(paths);
175
176     for (i = 0; ; ++i)
177         if (!locale[i] || locale[i] == '_' || locale[i] == '.' ||
178             locale[i] == '@')
179         {
180             self->language = g_strndup(locale, i);
181             break;
182         }
183         else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
184                  ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
185             break;
186     if (self->language && locale[i] == '_') {
187         locale += i+1;
188         for (i = 0; ; ++i)
189             if (!locale[i] || locale[i] == '.' || locale[i] == '@') {
190                 self->country = g_strndup(locale, i);
191                 break;
192             }
193             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
194                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
195                 break;
196     }
197     if (self->country && locale[i] == '.')
198         for (; ; ++i)
199             if (!locale[i] || locale[i] == '@')
200                 break;
201             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
202                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
203                 break;
204     if (self->country && locale[i] == '@') {
205         locale += i+1;
206         for (i = 0; ; ++i)
207             if (!locale[i]) {
208                 self->modifier = g_strndup(locale, i);
209                 break;
210             }
211             else if (((guchar)locale[i] < 'A' || (guchar)locale[i] > 'Z') &&
212                      ((guchar)locale[i] < 'a' || (guchar)locale[i] > 'z'))
213                 break;
214     }
215
216     priority = 0;
217     for (it = obt_paths_data_dirs(paths); it; it = g_slist_next(it)) {
218         if (!g_hash_table_lookup(self->path_to_priority, it->data)) {
219             gchar *base_path;
220             gint *pri;
221
222             base_path = g_build_filename(it->data, "applications", NULL);
223
224             /* add to the hash table before adding the watch. the new watch
225                will be calling our update handler, immediately with any
226                files present in the directory */
227             pri = g_new(gint, 1);
228             *pri = priority;
229             g_hash_table_insert(self->path_to_priority,
230                                 g_strdup(base_path), pri);
231
232             obt_watch_add(self->watch, base_path, FALSE, update, self);
233
234             ++priority;
235         }
236     }
237     return self;
238 }
239
240 void obt_linkbase_ref(ObtLinkBase *self)
241 {
242     ++self->ref;
243 }
244
245 void obt_linkbase_unref(ObtLinkBase *self)
246 {
247     if (--self->ref < 1) {
248         obt_watch_unref(self->watch);
249         g_hash_table_unref(self->path_to_priority);
250         g_hash_table_unref(self->base);
251         obt_paths_unref(self->paths);
252         g_slice_free(ObtLinkBase, self);
253     }
254 }