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