1/* $Id$ */
2
3/***
4  This file is part of avahi.
5
6  avahi is free software; you can redistribute it and/or modify it
7  under the terms of the GNU Lesser General Public License as
8  published by the Free Software Foundation; either version 2.1 of the
9  License, or (at your option) any later version.
10
11  avahi is distributed in the hope that it will be useful, but WITHOUT
12  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
14  Public License for more details.
15
16  You should have received a copy of the GNU Lesser General Public
17  License along with avahi; if not, write to the Free Software
18  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19  USA.
20***/
21
22#ifdef HAVE_CONFIG_H
23#include <config.h>
24#endif
25
26#include <avahi-common/llist.h>
27#include <avahi-common/malloc.h>
28#include <avahi-common/timeval.h>
29
30#include "glib-watch.h"
31
32struct AvahiWatch {
33    AvahiGLibPoll *glib_poll;
34    int dead;
35
36    GPollFD pollfd;
37    int pollfd_added;
38
39    AvahiWatchCallback callback;
40    void *userdata;
41
42    AVAHI_LLIST_FIELDS(AvahiWatch, watches);
43};
44
45struct AvahiTimeout {
46    AvahiGLibPoll *glib_poll;
47    gboolean dead;
48
49    gboolean enabled;
50    struct timeval expiry;
51
52    AvahiTimeoutCallback callback;
53    void  *userdata;
54
55    AVAHI_LLIST_FIELDS(AvahiTimeout, timeouts);
56};
57
58struct AvahiGLibPoll {
59    GSource source;
60    AvahiPoll api;
61    GMainContext *context;
62
63    gboolean timeout_req_cleanup;
64    gboolean watch_req_cleanup;
65
66    AVAHI_LLIST_HEAD(AvahiWatch, watches);
67    AVAHI_LLIST_HEAD(AvahiTimeout, timeouts);
68};
69
70static void destroy_watch(AvahiWatch *w) {
71    assert(w);
72
73    if (w->pollfd_added)
74        g_source_remove_poll(&w->glib_poll->source, &w->pollfd);
75
76    AVAHI_LLIST_REMOVE(AvahiWatch, watches, w->glib_poll->watches, w);
77
78    avahi_free(w);
79}
80
81static void cleanup_watches(AvahiGLibPoll *g, int all) {
82    AvahiWatch *w, *next;
83    assert(g);
84
85    for (w = g->watches; w; w = next) {
86        next = w->watches_next;
87
88        if (all || w->dead)
89            destroy_watch(w);
90    }
91
92    g->watch_req_cleanup = 0;
93}
94
95static gushort map_events_to_glib(AvahiWatchEvent events) {
96    return
97        (events & AVAHI_WATCH_IN ? G_IO_IN : 0) |
98        (events & AVAHI_WATCH_OUT ? G_IO_OUT : 0) |
99        (events & AVAHI_WATCH_ERR ? G_IO_ERR : 0) |
100        (events & AVAHI_WATCH_HUP ? G_IO_HUP : 0);
101}
102
103static AvahiWatchEvent map_events_from_glib(gushort events) {
104    return
105        (events & G_IO_IN ? AVAHI_WATCH_IN : 0) |
106        (events & G_IO_OUT ? AVAHI_WATCH_OUT : 0) |
107        (events & G_IO_ERR ? AVAHI_WATCH_ERR : 0) |
108        (events & G_IO_HUP ? AVAHI_WATCH_HUP : 0);
109}
110
111static AvahiWatch* watch_new(const AvahiPoll *api, int fd, AvahiWatchEvent events, AvahiWatchCallback callback, void *userdata) {
112    AvahiWatch *w;
113    AvahiGLibPoll *g;
114
115    assert(api);
116    assert(fd >= 0);
117    assert(callback);
118
119    g = api->userdata;
120    assert(g);
121
122    if (!(w = avahi_new(AvahiWatch, 1)))
123        return NULL;
124
125    w->glib_poll = g;
126    w->pollfd.fd = fd;
127    w->pollfd.events = map_events_to_glib(events);
128    w->pollfd.revents = 0;
129    w->callback = callback;
130    w->userdata = userdata;
131    w->dead = FALSE;
132
133    g_source_add_poll(&g->source, &w->pollfd);
134    w->pollfd_added = TRUE;
135
136    AVAHI_LLIST_PREPEND(AvahiWatch, watches, g->watches, w);
137
138    return w;
139}
140
141static void watch_update(AvahiWatch *w, AvahiWatchEvent events) {
142    assert(w);
143    assert(!w->dead);
144
145    w->pollfd.events = map_events_to_glib(events);
146}
147
148static AvahiWatchEvent watch_get_events(AvahiWatch *w) {
149    assert(w);
150    assert(!w->dead);
151
152    return map_events_from_glib(w->pollfd.revents);
153}
154
155static void watch_free(AvahiWatch *w) {
156    assert(w);
157    assert(!w->dead);
158
159    if (w->pollfd_added) {
160        g_source_remove_poll(&w->glib_poll->source, &w->pollfd);
161        w->pollfd_added = FALSE;
162    }
163
164    w->dead = TRUE;
165    w->glib_poll->timeout_req_cleanup = TRUE;
166}
167
168static AvahiTimeout* timeout_new(const AvahiPoll *api, const struct timeval *tv, AvahiTimeoutCallback callback, void *userdata) {
169    AvahiTimeout *t;
170    AvahiGLibPoll *g;
171
172    assert(api);
173    assert(callback);
174
175    g = api->userdata;
176    assert(g);
177
178    if (!(t = avahi_new(AvahiTimeout, 1)))
179        return NULL;
180
181    t->glib_poll = g;
182    t->dead = FALSE;
183
184    if ((t->enabled = !!tv))
185        t->expiry = *tv;
186
187    t->callback = callback;
188    t->userdata = userdata;
189
190    AVAHI_LLIST_PREPEND(AvahiTimeout, timeouts, g->timeouts, t);
191
192    return t;
193}
194
195static void timeout_update(AvahiTimeout *t, const struct timeval *tv) {
196    assert(t);
197    assert(!t->dead);
198
199    if ((t->enabled = !!tv))
200        t->expiry = *tv;
201}
202
203static void timeout_free(AvahiTimeout *t) {
204    assert(t);
205    assert(!t->dead);
206
207    t->dead = TRUE;
208    t->glib_poll->timeout_req_cleanup = TRUE;
209}
210
211static void destroy_timeout(AvahiTimeout *t) {
212    assert(t);
213
214    AVAHI_LLIST_REMOVE(AvahiTimeout, timeouts, t->glib_poll->timeouts, t);
215    avahi_free(t);
216}
217
218static void cleanup_timeouts(AvahiGLibPoll *g, int all) {
219    AvahiTimeout *t, *next;
220    assert(g);
221
222    for (t = g->timeouts; t; t = next) {
223        next = t->timeouts_next;
224
225        if (all || t->dead)
226            destroy_timeout(t);
227    }
228
229    g->timeout_req_cleanup = FALSE;
230}
231
232static AvahiTimeout* find_next_timeout(AvahiGLibPoll *g) {
233    AvahiTimeout *t, *n = NULL;
234    assert(g);
235
236    for (t = g->timeouts; t; t = t->timeouts_next) {
237
238        if (t->dead || !t->enabled)
239            continue;
240
241        if (!n || avahi_timeval_compare(&t->expiry, &n->expiry) < 0)
242            n = t;
243    }
244
245    return n;
246}
247
248static void start_timeout_callback(AvahiTimeout *t) {
249    assert(t);
250    assert(!t->dead);
251    assert(t->enabled);
252
253    t->enabled = 0;
254    t->callback(t, t->userdata);
255}
256
257static gboolean prepare_func(GSource *source, gint *timeout) {
258    AvahiGLibPoll *g = (AvahiGLibPoll*) source;
259    AvahiTimeout *next_timeout;
260
261    g_assert(g);
262    g_assert(timeout);
263
264    if (g->watch_req_cleanup)
265        cleanup_watches(g, 0);
266
267    if (g->timeout_req_cleanup)
268        cleanup_timeouts(g, 0);
269
270    if ((next_timeout = find_next_timeout(g))) {
271        GTimeVal now;
272        struct timeval tvnow;
273        AvahiUsec usec;
274
275        g_source_get_current_time(source, &now);
276        tvnow.tv_sec = now.tv_sec;
277        tvnow.tv_usec = now.tv_usec;
278
279        usec = avahi_timeval_diff(&next_timeout->expiry, &tvnow);
280
281        if (usec <= 0) {
282	   *timeout = 0;
283            return TRUE;
284	}
285
286        *timeout = (gint) (usec / 1000);
287    } else
288        *timeout = -1;
289
290    return FALSE;
291}
292
293static gboolean check_func(GSource *source) {
294    AvahiGLibPoll *g = (AvahiGLibPoll*) source;
295    AvahiWatch *w;
296    AvahiTimeout *next_timeout;
297
298    g_assert(g);
299
300    if ((next_timeout = find_next_timeout(g))) {
301        GTimeVal now;
302        struct timeval tvnow;
303        g_source_get_current_time(source, &now);
304        tvnow.tv_sec = now.tv_sec;
305        tvnow.tv_usec = now.tv_usec;
306
307        if (avahi_timeval_compare(&next_timeout->expiry, &tvnow) <= 0)
308            return TRUE;
309    }
310
311    for (w = g->watches; w; w = w->watches_next)
312        if (w->pollfd.revents > 0)
313            return TRUE;
314
315    return FALSE;
316}
317
318static gboolean dispatch_func(GSource *source, AVAHI_GCC_UNUSED GSourceFunc callback, AVAHI_GCC_UNUSED gpointer userdata) {
319    AvahiGLibPoll* g = (AvahiGLibPoll*) source;
320    AvahiWatch *w;
321    AvahiTimeout *next_timeout;
322
323    g_assert(g);
324
325    if ((next_timeout = find_next_timeout(g))) {
326        GTimeVal now;
327        struct timeval tvnow;
328        g_source_get_current_time(source, &now);
329        tvnow.tv_sec = now.tv_sec;
330        tvnow.tv_usec = now.tv_usec;
331
332        if (avahi_timeval_compare(&next_timeout->expiry, &tvnow) < 0) {
333            start_timeout_callback(next_timeout);
334            return TRUE;
335        }
336    }
337
338    for (w = g->watches; w; w = w->watches_next)
339        if (w->pollfd.revents > 0) {
340            assert(w->callback);
341            w->callback(w, w->pollfd.fd, map_events_from_glib(w->pollfd.revents), w->userdata);
342            w->pollfd.revents = 0;
343            return TRUE;
344        }
345
346    return TRUE;
347}
348
349AvahiGLibPoll *avahi_glib_poll_new(GMainContext *context, gint priority) {
350    AvahiGLibPoll *g;
351
352    static GSourceFuncs source_funcs = {
353        prepare_func,
354        check_func,
355        dispatch_func,
356        NULL,
357        NULL,
358        NULL
359    };
360
361    g = (AvahiGLibPoll*) g_source_new(&source_funcs, sizeof(AvahiGLibPoll));
362    g_main_context_ref(g->context = context ? context : g_main_context_default());
363
364    g->api.userdata = g;
365
366    g->api.watch_new = watch_new;
367    g->api.watch_free = watch_free;
368    g->api.watch_update = watch_update;
369    g->api.watch_get_events = watch_get_events;
370
371    g->api.timeout_new = timeout_new;
372    g->api.timeout_free = timeout_free;
373    g->api.timeout_update = timeout_update;
374
375    g->watch_req_cleanup = FALSE;
376    g->timeout_req_cleanup = FALSE;
377
378    AVAHI_LLIST_HEAD_INIT(AvahiWatch, g->watches);
379    AVAHI_LLIST_HEAD_INIT(AvahiTimeout, g->timeouts);
380
381    g_source_attach(&g->source, g->context);
382    g_source_set_priority(&g->source, priority);
383    g_source_set_can_recurse(&g->source, FALSE);
384
385    return g;
386}
387
388void avahi_glib_poll_free(AvahiGLibPoll *g) {
389    GSource *s = &g->source;
390    assert(g);
391
392    cleanup_watches(g, 1);
393    cleanup_timeouts(g, 1);
394
395    g_main_context_unref(g->context);
396    g_source_destroy(s);
397    g_source_unref(s);
398}
399
400const AvahiPoll* avahi_glib_poll_get(AvahiGLibPoll *g) {
401    assert(g);
402
403    return &g->api;
404}
405