1/*
2 * This file is part of gitg
3 *
4 * Copyright (C) 2012 - Jesse van den Kieboom
5 *
6 * gitg 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 * gitg 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 * You should have received a copy of the GNU General Public License
17 * along with gitg. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20namespace GitgExt
21{
22
23public delegate void MessageCallback(GitgExt.Message message);
24
25public class MessageBus : Object
26{
27	class Listener
28	{
29		public uint id;
30		public bool blocked;
31
32		public MessageCallback callback;
33
34		public Listener(uint id, owned MessageCallback callback)
35		{
36			this.id = id;
37
38			// TODO: destroy notify is lost...
39			this.callback = (owned)callback;
40			this.blocked = false;
41		}
42	}
43
44	class Message
45	{
46		public MessageId id;
47		public List<Listener> listeners;
48
49		public Message(MessageId id)
50		{
51			this.id = id.copy();
52			this.listeners = new List<Listener>();
53		}
54	}
55
56	class IdMap
57	{
58		public Message message;
59		public unowned List<Listener> listener;
60
61		public IdMap(Message message)
62		{
63			this.message = message;
64		}
65	}
66
67	private HashTable<MessageId, Message> d_messages;
68	private HashTable<uint, IdMap> d_idmap;
69	private HashTable<MessageId, Type> d_types;
70	private static MessageBus? s_instance;
71	private static uint s_next_id;
72
73	public signal void registered(MessageId id);
74	public signal void unregistered(MessageId id);
75
76	public virtual signal void dispatch(GitgExt.Message message)
77	{
78		Message? msg = lookup_message(message.id, false);
79
80		if (msg != null)
81		{
82			dispatch_message_real(msg, message);
83		}
84	}
85
86	public MessageBus()
87	{
88		d_messages = new HashTable<MessageId, Message>(MessageId.hash, MessageId.equal);
89		d_idmap = new HashTable<uint, IdMap>(direct_hash, direct_equal);
90		d_types = new HashTable<MessageId, Type>(MessageId.hash, MessageId.equal);
91	}
92
93	public static MessageBus get_default()
94	{
95		if (s_instance == null)
96		{
97			s_instance = new MessageBus();
98			s_instance.add_weak_pointer(&s_instance);
99		}
100
101		return s_instance;
102	}
103
104	private void dispatch_message_real(Message msg, GitgExt.Message message)
105	{
106		foreach (Listener l in msg.listeners)
107		{
108			if (!l.blocked)
109			{
110				l.callback(message);
111			}
112		}
113	}
114
115	public Type lookup(MessageId id)
116	{
117		Type ret;
118
119		if (!d_types.lookup_extended(id, null, out ret))
120		{
121			return Type.INVALID;
122		}
123		else
124		{
125			return ret;
126		}
127	}
128
129	public void register(Type message_type, MessageId id)
130	{
131		if (is_registered(id))
132		{
133			warning("Message type for `%s' is already registered", id.id);
134			return;
135		}
136
137		var cp = id.copy();
138
139		d_types.insert(cp, message_type);
140
141		registered(cp);
142	}
143
144	private void unregister_real(MessageId id, bool remove_from_store)
145	{
146		var cp = id;
147
148		if (!remove_from_store || d_types.remove(cp))
149		{
150			unregistered(cp);
151		}
152	}
153
154	public void unregister(MessageId id)
155	{
156		unregister_real(id, true);
157	}
158
159	public void unregister_all(string object_path)
160	{
161		d_types.foreach_remove((key, val) => {
162			if (key.object_path == object_path)
163			{
164				unregister_real(key, true);
165				return true;
166			}
167			else
168			{
169				return false;
170			}
171		});
172	}
173
174	public bool is_registered(MessageId id)
175	{
176		return d_types.lookup_extended(id, null, null);
177	}
178
179	private Message new_message(MessageId id)
180	{
181		var ret = new Message(id);
182
183		d_messages.insert(id, ret);
184		return ret;
185	}
186
187	private Message? lookup_message(MessageId id, bool create)
188	{
189		var message = d_messages.lookup(id);
190
191		if (message == null && !create)
192		{
193			return null;
194		}
195
196		if (message == null)
197		{
198			message = new_message(id);
199		}
200
201		return message;
202	}
203
204	private uint add_listener(Message message, owned MessageCallback callback)
205	{
206		var listener = new Listener(++s_next_id, (owned)callback);
207
208		message.listeners.append(listener);
209
210		var idmap = new IdMap(message);
211		idmap.listener = message.listeners.last();
212
213		d_idmap.insert(listener.id, idmap);
214		return listener.id;
215	}
216
217	private void remove_listener(Message message, List<Listener> listener)
218	{
219		unowned Listener lst = listener.data;
220
221		d_idmap.remove(lst.id);
222
223		message.listeners.delete_link(listener);
224
225		if (message.listeners == null)
226		{
227			d_messages.remove(message.id);
228		}
229	}
230
231	private void block_listener(Message message, List<Listener> listener)
232	{
233		listener.data.blocked = true;
234	}
235
236	private void unblock_listener(Message message, List<Listener> listener)
237	{
238		listener.data.blocked = false;
239	}
240
241	public new uint connect(MessageId id, owned MessageCallback callback)
242	{
243		var message = lookup_message(id, true);
244
245		return add_listener(message, (owned)callback);
246	}
247
248	private delegate void MatchCallback(Message message, List<Listener> listeners);
249
250	private void process_by_id(uint id, MatchCallback processor)
251	{
252		IdMap? idmap = d_idmap.lookup(id);
253
254		if (idmap == null)
255		{
256			return;
257		}
258
259		processor(idmap.message, idmap.listener);
260	}
261
262	public new void disconnect(uint id)
263	{
264		process_by_id(id, remove_listener);
265	}
266
267	public void block(uint id)
268	{
269		process_by_id(id, block_listener);
270	}
271
272	public void unblock(uint id)
273	{
274		process_by_id(id, unblock_listener);
275	}
276
277	private void dispatch_message(GitgExt.Message message)
278	{
279		dispatch(message);
280	}
281
282	public GitgExt.Message send_message(GitgExt.Message message)
283	{
284		dispatch_message(message);
285		return message;
286	}
287
288	public GitgExt.Message? send(MessageId id, string? firstprop, ...)
289	{
290		Type type = lookup(id);
291
292		if (type == Type.INVALID)
293		{
294			warning("Could not find message type for `%s'", id.id);
295			return null;
296		}
297
298		GitgExt.Message? msg = (GitgExt.Message?)Object.new_valist(type, firstprop, va_list());
299
300		if (msg != null)
301		{
302			msg.id = id;
303		}
304
305		dispatch_message(msg);
306
307		return msg;
308	}
309}
310
311}
312
313// ex:set ts=4 noet:
314