Poll.cxx 4.62 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * http://www.musicpd.org
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

20
#include "Poll.hxx"
21
#include "event/SocketEvent.hxx"
22
#include "event/FineTimerEvent.hxx"
23
#include "time/Convert.hxx"
24

25
static constexpr unsigned
26
FromAvahiWatchEvent(AvahiWatchEvent e) noexcept
27
{
28 29
	return (e & AVAHI_WATCH_IN ? SocketEvent::READ : 0) |
		(e & AVAHI_WATCH_OUT ? SocketEvent::WRITE : 0);
30 31
}

32
static constexpr AvahiWatchEvent
33
ToAvahiWatchEvent(unsigned e) noexcept
34
{
35 36 37 38
	return AvahiWatchEvent((e & SocketEvent::READ ? AVAHI_WATCH_IN : 0) |
			       (e & SocketEvent::WRITE ? AVAHI_WATCH_OUT : 0) |
			       (e & SocketEvent::ERROR ? AVAHI_WATCH_ERR : 0) |
			       (e & SocketEvent::HANGUP ? AVAHI_WATCH_HUP : 0));
39 40
}

41
struct AvahiWatch final {
42
private:
43 44
	SocketEvent event;

45 46 47
	const AvahiWatchCallback callback;
	void *const userdata;

48
	AvahiWatchEvent received = AvahiWatchEvent(0);
49 50

public:
51 52 53
	AvahiWatch(EventLoop &_loop,
		   SocketDescriptor _fd, AvahiWatchEvent _event,
		   AvahiWatchCallback _callback, void *_userdata) noexcept
54
		:event(_loop, BIND_THIS_METHOD(OnSocketReady), _fd),
55
		 callback(_callback), userdata(_userdata) {
56
		event.Schedule(FromAvahiWatchEvent(_event));
57 58
	}

59
	static void WatchUpdate(AvahiWatch *w,
60 61
				AvahiWatchEvent _event) noexcept {
		w->event.Schedule(FromAvahiWatchEvent(_event));
62 63
	}

64
	static AvahiWatchEvent WatchGetEvents(AvahiWatch *w) noexcept {
65 66 67
		return w->received;
	}

68
	static void WatchFree(AvahiWatch *w) noexcept {
69 70 71
		delete w;
	}

72
private:
73 74
	void OnSocketReady(unsigned events) noexcept {
		received = ToAvahiWatchEvent(events);
75
		callback(this, event.GetSocket().Get(), received, userdata);
76 77 78 79
		received = AvahiWatchEvent(0);
	}
};

80
struct AvahiTimeout final {
81 82 83 84 85
	/* note: cannot use CoarseTimerEvent because libavahi-client
	   sometimes schedules events immediately, and
	   CoarseTimerEvent may delay the timer callback for too
	   long, causing timeouts */
	FineTimerEvent event;
86

87 88 89 90
	const AvahiTimeoutCallback callback;
	void *const userdata;

public:
91 92
	AvahiTimeout(EventLoop &_loop, const struct timeval *tv,
		     AvahiTimeoutCallback _callback, void *_userdata) noexcept
93
		:event(_loop, BIND_THIS_METHOD(OnTimeout)),
94 95
		 callback(_callback), userdata(_userdata) {
		if (tv != nullptr)
96
			Schedule(*tv);
97 98
	}

99 100
	static void TimeoutUpdate(AvahiTimeout *t,
				  const struct timeval *tv) noexcept {
101
		if (tv != nullptr)
102
			t->Schedule(*tv);
103
		else
104
			t->event.Cancel();
105 106
	}

107
	static void TimeoutFree(AvahiTimeout *t) noexcept {
108 109 110
		delete t;
	}

111
private:
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
	[[gnu::pure]]
	Event::Duration AbsoluteToDuration(const struct timeval &tv) noexcept {
		if (tv.tv_sec == 0)
			/* schedule immediately */
			return {};

		struct timeval now;
		if (gettimeofday(&now, nullptr) < 0)
			/* shouldn't ever fail, but if it does, do
			   something reasonable */
			return std::chrono::seconds(1);

		auto d = ToSteadyClockDuration(tv)
			- ToSteadyClockDuration(now);
		if (d.count() < 0)
			return {};

		return d;
	}

132
	void Schedule(const struct timeval &tv) noexcept {
133
		event.Schedule(AbsoluteToDuration(tv));
134 135
	}

136
	void OnTimeout() noexcept {
137 138 139 140
		callback(this, userdata);
	}
};

141 142 143
namespace Avahi {

Poll::Poll(EventLoop &_loop) noexcept
144
	:event_loop(_loop)
145 146 147 148 149 150 151 152 153 154 155
{
	watch_new = WatchNew;
	watch_update = AvahiWatch::WatchUpdate;
	watch_get_events = AvahiWatch::WatchGetEvents;
	watch_free = AvahiWatch::WatchFree;
	timeout_new = TimeoutNew;
	timeout_update = AvahiTimeout::TimeoutUpdate;
	timeout_free = AvahiTimeout::TimeoutFree;
}

AvahiWatch *
156 157
Poll::WatchNew(const AvahiPoll *api, int fd, AvahiWatchEvent event,
	       AvahiWatchCallback callback, void *userdata) noexcept
158
{
159
	const Poll &poll = *(const Poll *)api;
160

161 162
	return new AvahiWatch(poll.event_loop, SocketDescriptor(fd), event,
			      callback, userdata);
163 164 165
}

AvahiTimeout *
166 167
Poll::TimeoutNew(const AvahiPoll *api, const struct timeval *tv,
		 AvahiTimeoutCallback callback, void *userdata) noexcept
168
{
169
	const Poll &poll = *(const Poll *)api;
170

171
	return new AvahiTimeout(poll.event_loop, tv, callback, userdata);
172
}
173 174

} // namespace Avahi