1 /* libobby - Network text editing library
2  * Copyright (C) 2005, 2006 0x539 dev group
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, write to the Free
16  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18 
19 #ifndef _OBBY_BUFFER_HPP_
20 #define _OBBY_BUFFER_HPP_
21 
22 #include <set>
23 #include <list>
24 
25 #include <net6/main.hpp>
26 #include <net6/object.hpp>
27 
28 #include "serialise/parser.hpp"
29 #include "common.hpp"
30 #include "format_string.hpp"
31 #include "user_table.hpp"
32 #include "document.hpp"
33 #include "chat.hpp"
34 #include "document_info.hpp"
35 
36 namespace obby
37 {
38 
39 extern const unsigned long PROTOCOL_VERSION;
40 
41 /** Abstract base class for obby buffers. A buffer contains multiple documents
42  * that are synchronised through many users and a user list.
43  */
44 template<typename Document, typename Selector>
45 class basic_buffer: private net6::non_copyable, public sigc::trackable
46 {
47 public:
48 	typedef Document document_type;
49 	typedef Selector selector_type;
50 	typedef typename document_type::template_type document_template_type;
51 
52 	// base_document_info_type is needed to support GCC-3.3 which does
53 	// not support covariant returns
54 	typedef basic_document_info<document_type, selector_type>
55 		base_document_info_type;
56 
57 	typedef basic_document_info<document_type, selector_type>
58 		document_info_type;
59 
60 	// Same as above for net type
61 	typedef net6::basic_object<selector_type> base_net_type;
62 	typedef net6::basic_object<selector_type> net_type;
63 
64 	// Document list
65 	// TODO: Outsource this to document_list<document_info_type> class
66 	typedef std::list<document_info_type*> document_list;
67 	typedef typename document_list::size_type document_size_type;
68 
69 	typedef ptr_iterator<
70 		document_info_type,
71 		document_list,
72 		typename document_list::const_iterator
73 	> document_iterator;
74 
75 	// Signal types
76 	typedef sigc::signal<void, unsigned int>
77 		signal_sync_init_type;
78 	typedef sigc::signal<void>
79 		signal_sync_final_type;
80 	typedef sigc::signal<void, const user&>
81 		signal_user_join_type;
82 	typedef sigc::signal<void, const user&>
83 		signal_user_part_type;
84 	typedef sigc::signal<void, const user&>
85 		signal_user_colour_type;
86 	typedef sigc::signal<void, document_info_type&>
87 		signal_document_insert_type;
88 	typedef sigc::signal<void, document_info_type&>
89 		signal_document_rename_type;
90 	typedef sigc::signal<void, document_info_type&>
91 		signal_document_remove_type;
92 
93 	basic_buffer();
94 	virtual ~basic_buffer();
95 
96 	/** @brief Returns whether the session is open.
97 	 */
98 	virtual bool is_open() const;
99 
100 	/** Returns the user table associated with the buffer.
101 	 */
102 	const user_table& get_user_table() const;
103 
104 	/** Returns the obby::chat for this buffer.
105 	 */
106 	const chat& get_chat() const;
107 
108 	/** Returns the selector of the underlaying net6 network object.
109 	 */
110 	selector_type& get_selector();
111 
112 	/** Returns the selector of the underlaying net6 network object.
113 	 */
114 	const selector_type& get_selector() const;
115 
116 	/** Serialises the complete obby session into <em>file</em>.
117 	 */
118 	void serialise(const std::string& file) const;
119 
120 	/* Creates a new document with predefined content.
121 	 * signal_document_insert will be emitted if it has been created.
122 	 */
123 	virtual void document_create(const std::string& title,
124 	                             const std::string& encoding,
125 	                             const std::string& content) = 0;
126 
127 	/** Removes an existing document. signal_document_remove will be
128 	 * emitted if the document has been removed.
129 	 */
130 	virtual void document_remove(base_document_info_type& doc) = 0;
131 
132 	/** Looks for a document with the given ID which belongs to the user
133 	 * with the given owner ID. Note that we do not take a real user object
134 	 * here because the ID is enough and one might not have a user object
135 	 * to the corresponding ID. So a time-consuming lookup is obsolete.
136 	 */
137 	document_info_type* document_find(unsigned int owner_id,
138 	                                  unsigned int id) const;
139 
140 	/** Returns the begin of the document list.
141 	 */
142 	document_iterator document_begin() const;
143 
144 	/** Returns the end of the document list.
145 	 */
146 	document_iterator document_end() const;
147 
148 	/** Returns the size of the document list.
149 	 */
150 	document_size_type document_count() const;
151 
152 	/** Sends a global chat message to all users.
153 	 */
154 	virtual void send_message(const std::string& message) = 0;
155 
156 	/** Checks if given colour components match an other
157 	 * already present one too closely.
158 	 * TODO: Move this function to user table?
159 	 */
160 	bool check_colour(const colour& colour,
161 	                  const user* ignore = NULL) const;
162 
163 	/** @brief Returns the current document template that is used
164 	 * to instanciate a document.
165 	 */
166 	const document_template_type& get_document_template() const;
167 
168 	/** Sets a new template that is used to instanciate a document.
169 	 */
170 	void set_document_template(const document_template_type& tmpl);
171 
172 	/** @brief Looks for a free suffix in the buffer.
173 	 */
174 	unsigned int find_free_suffix(const std::string& for_title,
175 	                              const document_info_type* ignore) const;
176 
177 	/** Signal which will be emitted when the initial syncrhonisation
178 	 * begins, thus if the client has logged in successfully.
179 	 */
180 	signal_sync_init_type sync_init_event() const;
181 
182 	/** Signal which will be emitted when the initial synchronisation of
183 	 * the user list and the document list has been completed.
184 	 */
185 	signal_sync_final_type sync_final_event() const;
186 
187 	/** Signal which will be emitted if a new user has joined the obby
188 	 * session.
189 	 */
190 	signal_user_join_type user_join_event() const;
191 
192 	/** Signal which will be emitted if a user has quit.
193 	 */
194 	signal_user_part_type user_part_event() const;
195 
196 	/** Signal which will be emitted if a user changes his colour.
197 	 */
198 	signal_user_colour_type user_colour_event() const;
199 
200 	/** Signal which will be emitted when another participant in the
201 	 * obby session has created a new document.
202 	 */
203 	signal_document_insert_type document_insert_event() const;
204 
205 	/** Signal which will be emitted when another participant in the
206 	 * obby session renames one document.
207 	 */
208 	signal_document_rename_type document_rename_event() const;
209 
210 	/** Signal which will be emitted when another participant in the
211 	 * obby session has removed an existing document.
212 	 */
213 	signal_document_remove_type document_remove_event() const;
214 
215 protected:
216         /** Internal function to add a document to the buffer.
217 	 */
218 	void document_add(document_info_type& document);
219 
220 	/** Internal function to delete a document from the buffer.
221 	 */
222 	void document_delete(document_info_type& document);
223 
224 	/** Internal function to clear the whole document list.
225 	 */
226 	void document_clear();
227 
228 	/** @brief Internal function to add a user to the buffer.
229 	 */
230 	void user_join(const user& user);
231 
232 	/** @brief Internal function that clears up when a user has gone.
233 	 */
234 	void user_part(const user& user);
235 
236 	/** @brief Closes the session.
237 	 */
238 	virtual void session_close();
239 
240 	/** @brief Implementation of session_close() that does not call
241 	 * a base function.
242 	 */
243 	void session_close_impl();
244 
245 	signal_sync_init_type m_signal_sync_init;
246 	signal_sync_final_type m_signal_sync_final;
247 
248 	signal_user_join_type m_signal_user_join;
249 	signal_user_part_type m_signal_user_part;
250 	signal_user_colour_type m_signal_user_colour;
251 
252 	signal_document_insert_type m_signal_document_insert;
253 	//signal_document_rename_type m_signal_document_rename;
254 	signal_document_remove_type m_signal_document_remove;
255 
256 	net6::main m_netkit;
257 	std::auto_ptr<net_type> m_net;
258 
259 	user_table m_user_table;
260 	chat m_chat;
261 
262 	document_list m_docs;
263 	document_template_type m_document_template;
264 	unsigned int m_doc_counter;
265 
266 	net6::gettext_package m_package;
267 };
268 
269 typedef basic_buffer<obby::document, net6::selector> buffer;
270 
271 template<typename Document, typename Selector>
basic_buffer()272 basic_buffer<Document, Selector>::basic_buffer():
273 	m_chat(*this, 0xff),
274 	m_doc_counter(0), m_package(obby_package(), obby_localedir())
275 {
276 	// Initialize gettext
277 	init_gettext(m_package);
278 }
279 
280 template<typename Document, typename Selector>
~basic_buffer()281 basic_buffer<Document, Selector>::~basic_buffer()
282 {
283 	document_clear();
284 }
285 
286 template<typename Document, typename Selector>
is_open() const287 bool basic_buffer<Document, Selector>::is_open() const
288 {
289 	return m_net.get() != NULL;
290 }
291 
292 template<typename Document, typename Selector>
get_user_table() const293 const user_table& basic_buffer<Document, Selector>::get_user_table() const
294 {
295 	return m_user_table;
296 }
297 
298 template<typename Document, typename Selector>
get_chat() const299 const chat& basic_buffer<Document, Selector>::get_chat() const
300 {
301 	return m_chat;
302 }
303 
304 template<typename Document, typename Selector>
305 typename basic_buffer<Document, Selector>::selector_type&
get_selector()306 	basic_buffer<Document, Selector>::get_selector()
307 {
308 	if(m_net.get() == NULL)
309 	{
310 		throw std::logic_error(
311 			"obby::basic_buffer::get_selector:\n"
312 			"Net object not yet initialized"
313 		);
314 	}
315 
316 	return m_net->get_selector();
317 }
318 
319 template<typename Document, typename Selector>
320 const typename basic_buffer<Document, Selector>::selector_type&
get_selector() const321 	basic_buffer<Document, Selector>::get_selector() const
322 {
323 	if(m_net.get() == NULL)
324 	{
325 		throw std::logic_error(
326 			"obby::basic_buffer::get_selector:\n"
327 			"Net object not yet initialized"
328 		);
329 	}
330 
331 	return m_net->get_selector();
332 }
333 
334 template<typename Document, typename Selector>
335 void basic_buffer<Document, Selector>::
serialise(const std::string & session) const336 	serialise(const std::string& session) const
337 {
338 	serialise::parser parser;
339 	parser.set_type("obby");
340 
341 	serialise::object& root = parser.get_root();
342 	root.set_name("session");
343 	root.add_attribute("version").set_value(obby_version() );
344 
345 	serialise::object& user_table = root.add_child();
346 	user_table.set_name("user_table");
347 	m_user_table.serialise(user_table);
348 
349 	serialise::object& chat = root.add_child();
350 	chat.set_name("chat");
351 	m_chat.serialise(chat);
352 
353 	for(document_iterator iter = document_begin();
354 	    iter != document_end();
355 	    ++ iter)
356 	{
357 		// Do not serialise this document if we do not have its content
358 		try { iter->get_content(); } catch(...) { continue; }
359 
360 		serialise::object& doc = root.add_child();
361 		doc.set_name("document");
362 		iter->serialise(doc);
363 	}
364 
365 	parser.serialise(session);
366 }
367 
368 template<typename Document, typename Selector>
369 typename basic_buffer<Document, Selector>::document_info_type*
document_find(unsigned int owner_id,unsigned int id) const370 basic_buffer<Document, Selector>::document_find(unsigned int owner_id,
371                                                 unsigned int id) const
372 {
373 	document_iterator iter;
374 	for(iter = m_docs.begin(); iter != m_docs.end(); ++ iter)
375 	{
376 		// Check document ID
377 		if(iter->get_id() != id) continue;
378 		// Check owner ID
379 		if(iter->get_owner_id() != owner_id) continue;
380 		// Found requested document
381 		return &(*iter);
382 	}
383 
384 	return NULL;
385 }
386 
387 template<typename Document, typename Selector>
check_colour(const colour & colour,const user * ignore) const388 bool basic_buffer<Document, Selector>::check_colour(const colour& colour,
389                                                     const user* ignore) const
390 {
391 	for(user_table::iterator iter =
392 		m_user_table.begin(user::flags::CONNECTED, user::flags::NONE);
393 	    iter !=
394 		m_user_table.end(user::flags::CONNECTED, user::flags::NONE);
395 	    ++ iter)
396 	{
397 		// Ignore given user to ignore
398 		if(&(*iter) == ignore) continue;
399 
400 		if(colour.similar_colour(iter->get_colour()) )
401 		{
402 			// Conflict
403 			return false;
404 		}
405 	}
406 
407 	return true;
408 }
409 
410 template<typename Document, typename Selector>
411 typename basic_buffer<Document, Selector>::document_iterator
document_begin() const412 basic_buffer<Document, Selector>::document_begin() const
413 {
414 	return static_cast<document_iterator>(m_docs.begin() );
415 }
416 
417 template<typename Document, typename Selector>
418 typename basic_buffer<Document, Selector>::document_iterator
document_end() const419 basic_buffer<Document, Selector>::document_end() const
420 {
421 	return static_cast<document_iterator>(m_docs.end() );
422 }
423 
424 template<typename Document, typename Selector>
425 typename basic_buffer<Document, Selector>::document_size_type
document_count() const426 basic_buffer<Document, Selector>::document_count() const
427 {
428 	return m_docs.size();
429 }
430 
431 template<typename Document, typename Selector>
432 const typename basic_buffer<Document, Selector>::document_template_type&
get_document_template() const433 basic_buffer<Document, Selector>::get_document_template() const
434 {
435 	return m_document_template;
436 }
437 
438 template<typename Document, typename Selector>
439 void basic_buffer<Document, Selector>::
set_document_template(const document_template_type & tmpl)440 	set_document_template(const document_template_type& tmpl)
441 {
442 	m_document_template = tmpl;
443 }
444 
445 template<typename Document, typename Selector>
446 unsigned int basic_buffer<Document, Selector>::
find_free_suffix(const std::string & for_title,const document_info_type * ignore) const447 	find_free_suffix(const std::string& for_title,
448 	                 const document_info_type* ignore) const
449 {
450 	// Set that sorts suffixes in ascending order
451 	std::set<unsigned int> suffixes;
452 
453 	// Put all suffixes into the set
454 	for(document_iterator it = m_docs.begin(); it != m_docs.end(); ++ it)
455 	{
456 		if(ignore == &(*it) )
457 			continue;
458 
459 		if(it->get_title() == for_title)
460 			suffixes.insert(it->get_suffix() );
461 	}
462 
463 	// Choose the lowest free one
464 	unsigned int prev_suffix = 0;
465 	for(std::set<unsigned int>::const_iterator iter = suffixes.begin();
466 	    iter != suffixes.end();
467 	    ++ iter)
468 	{
469 		if(*iter > prev_suffix + 1)
470 			break;
471 		else
472 			prev_suffix = *iter;
473 	}
474 
475 	return prev_suffix + 1;
476 }
477 
478 template<typename Document, typename Selector>
479 typename basic_buffer<Document, Selector>::signal_sync_init_type
sync_init_event() const480 basic_buffer<Document, Selector>::sync_init_event() const
481 {
482 	return m_signal_sync_init;
483 }
484 
485 template<typename Document, typename Selector>
486 typename basic_buffer<Document, Selector>::signal_sync_final_type
sync_final_event() const487 basic_buffer<Document, Selector>::sync_final_event() const
488 {
489 	return m_signal_sync_final;
490 }
491 
492 template<typename Document, typename Selector>
493 typename basic_buffer<Document, Selector>::signal_user_join_type
user_join_event() const494 basic_buffer<Document, Selector>::user_join_event() const
495 {
496 	return m_signal_user_join;
497 }
498 
499 template<typename Document, typename Selector>
500 typename basic_buffer<Document, Selector>::signal_user_part_type
user_part_event() const501 basic_buffer<Document, Selector>::user_part_event() const
502 {
503 	return m_signal_user_part;
504 }
505 
506 template<typename Document, typename Selector>
507 typename basic_buffer<Document, Selector>::signal_user_colour_type
user_colour_event() const508 basic_buffer<Document, Selector>::user_colour_event() const
509 {
510 	return m_signal_user_colour;
511 }
512 
513 template<typename Document, typename Selector>
514 typename basic_buffer<Document, Selector>::signal_document_insert_type
document_insert_event() const515 basic_buffer<Document, Selector>::document_insert_event() const
516 {
517 	return m_signal_document_insert;
518 }
519 
520 /*template<typename Document, typename Selector>
521 typename basic_buffer<Document, Selector>::signal_document_rename_type
522 basic_buffer<Document, Selector>::document_rename_event() const
523 {
524 	return m_signal_document_rename;
525 }*/
526 
527 template<typename Document, typename Selector>
528 typename basic_buffer<Document, Selector>::signal_document_remove_type
document_remove_event() const529 basic_buffer<Document, Selector>::document_remove_event() const
530 {
531 	return m_signal_document_remove;
532 }
533 
534 template<typename Document, typename Selector>
535 void basic_buffer<Document, Selector>::
document_add(document_info_type & info)536 	document_add(document_info_type& info)
537 {
538 	typedef typename document_info_type::user_iterator user_iterator;
539 	std::set<const obby::user*> users;
540 
541 	// Remember all users initially subscribed
542 	for(user_iterator iter = info.user_begin();
543 	    iter != info.user_end();
544 	    ++ iter)
545 	{
546 		users.insert(&(*iter));
547 	}
548 
549 	// Add new document into list
550 	m_docs.push_back(&info);
551 	// Emit document_insert signal
552 	m_signal_document_insert.emit(info);
553 	// Emit user_subscribe signal for each user that was initially
554 	// subscribed to the document. This does not include users that
555 	// have been subscribed by the document insert signal handler.
556 	for(user_iterator iter = info.user_begin();
557 	    iter != info.user_end();
558 	    ++ iter)
559 	{
560 		if(users.find(&(*iter)) != users.end())
561 			info.subscribe_event().emit(*iter);
562 	}
563 }
564 
565 template<typename Document, typename Selector>
566 void basic_buffer<Document, Selector>::
document_delete(document_info_type & info)567 	document_delete(document_info_type& info)
568 {
569 	// TODO: Emit user_unsubscribe signal for each user that was subscribed?
570 	// Emit document_remove signal
571 	m_signal_document_remove.emit(info);
572 	// Delete from list (TODO: Use std::set?)
573 	m_docs.erase(
574 		std::remove(m_docs.begin(), m_docs.end(), &info),
575 		m_docs.end()
576 	);
577 
578 	// Delete document
579 	delete &info;
580 }
581 
582 template<typename Document, typename Selector>
document_clear()583 void basic_buffer<Document, Selector>::document_clear()
584 {
585 	// TODO: Emit document_remove signal for each document?
586 	typename document_list::iterator iter;
587 	for(iter = m_docs.begin(); iter != m_docs.end(); ++ iter)
588 		delete *iter;
589 
590 	m_docs.clear();
591 }
592 
593 template<typename Document, typename Selector>
user_join(const user & user)594 void basic_buffer<Document, Selector>::user_join(const user& user)
595 {
596 	// User should have already been added to the user table (that creates
597 	// the user object).
598 	for(document_iterator iter = document_begin();
599 	    iter != document_end();
600 	    ++ iter)
601 	{
602 		iter->obby_user_join(user);
603 	}
604 
605 	// TODO: Move signal emission to user_table::add_user.
606 	m_signal_user_join.emit(user);
607 }
608 
609 template<typename Document, typename Selector>
user_part(const user & user)610 void basic_buffer<Document, Selector>::user_part(const user& user)
611 {
612 	for(document_iterator iter = document_begin();
613 	    iter != document_end();
614 	    ++ iter)
615 	{
616 		iter->obby_user_part(user);
617 	}
618 
619 	m_signal_user_part.emit(user);
620 
621 	// TODO: Move signal emission to user_table::remove_user
622 	m_user_table.remove_user(user);
623 }
624 
625 template<typename Document, typename Selector>
session_close()626 void basic_buffer<Document, Selector>::session_close()
627 {
628 	session_close_impl();
629 }
630 
631 template<typename Document, typename Selector>
session_close_impl()632 void basic_buffer<Document, Selector>::session_close_impl()
633 {
634 	for(document_iterator iter = document_begin();
635 	    iter != document_end();
636 	    ++ iter)
637 	{
638 		iter->obby_session_close();
639 	}
640 
641 	m_net.reset(NULL);
642 }
643 
644 } // namespace obby
645 
646 #endif // _OBBY_BUFFER_HPP_
647