/* libobby - Network text editing library * Copyright (C) 2005, 2006 0x539 dev group * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef _OBBY_DOCUMENT_INFO_HPP_ #define _OBBY_DOCUMENT_INFO_HPP_ #include #include #include #include "position.hpp" #include "ptr_iterator.hpp" #include "format_string.hpp" #include "command.hpp" #include "user.hpp" #include "document_packet.hpp" #include "serialise/object.hpp" namespace obby { template class basic_buffer; /** Information about a document that is provided without being subscribed to * a document. */ template class basic_document_info: private net6::non_copyable, public sigc::trackable { public: class privileges { public: static const privileges NONE; static const privileges SUBSCRIBE; static const privileges MODIFY; static const privileges CLOSE; static const privileges RENAME; static const privileges ADMIN; static const privileges ALL; privileges operator|(privileges other) const { return privileges(m_value | other.m_value); } privileges operator&(privileges other) const { return privileges(m_value & other.m_value); } privileges operator^(privileges other) const { return privileges(m_value ^ other.m_value); } privileges& operator|=(privileges other) { m_value |= other.m_value; return *this; } privileges& operator&=(privileges other) { m_value &= other.m_value; return *this; } privileges& operator^=(privileges other) { m_value ^= other.m_value; return *this; } privileges operator~() const { return privileges(~m_value); } operator bool() const { return m_value != NONE.m_value; } bool operator!() const { return m_value == NONE.m_value; } bool operator==(privileges other) const { return m_value == other.m_value; } bool operator!=(privileges other) const { return m_value != other.m_value; } unsigned int get_value() const { return m_value; } protected: explicit privileges(unsigned int value) : m_value(value) { } unsigned int m_value; }; typedef Document document_type; typedef Selector selector_type; class privileges_table; typedef sigc::signal signal_rename_type; typedef sigc::signal signal_subscribe_type; typedef sigc::signal signal_unsubscribe_type; // TODO: Outsource subscribed users into an own class typedef std::list::size_type user_size_type; typedef ptr_iterator< const user, std::list, std::list::const_iterator > user_iterator; typedef basic_buffer buffer_type; typedef typename buffer_type::net_type net_type; basic_document_info(const buffer_type& buffer, net_type& net, const user* owner, unsigned int id, const std::string& title, unsigned int suffix, const std::string& encoding); basic_document_info(const buffer_type& buffer, net_type& net, const user* owner, unsigned int id, const std::string& title, const std::string& encoding); basic_document_info(const buffer_type& buffer, net_type& net, const serialise::object& obj); basic_document_info(const buffer_type& buffer, net_type& net, const net6::packet& init_pack); /** Serialises the document into the given serialisation object. */ void serialise(serialise::object& obj) const; /** Returns the owner of this document. It may return NULL if the * document has no owner (indicating that the server created the * document). */ const user* get_owner() const; /** Returns the ID of the owner or 0 if the document has no owner. */ unsigned int get_owner_id() const; /** Returns a unique ID for this document. */ unsigned int get_id() const; /** Returns the title set for this document. */ const std::string& get_title() const; /** @brief Returns the suffix for this document. * * The suffix is a number that is normally just 1. However, if two * documents with the exact same titles are in the same session, one * of them is assigned the suffix 2. */ unsigned int get_suffix() const; /** @brief Returns the suffixed title for the document that is * unique in the whole session. */ std::string get_suffixed_title() const; /** @brief Returns the character set in which the document is encoded. */ const std::string& get_encoding() const; /** Returns the content of the document, if available. */ const Document& get_content() const; /** Returns the privileges table for this document. */ const privileges_table& get_privileges_table() const; /** Inserts the given text at the given position into the document. */ virtual void insert(position pos, const std::string& text) = 0; /** Erases the given range from the document. */ virtual void erase(position pos, position len) = 0; /** Renames the document or requests a rename operation. * signal_rename will be emitted if the document has been renamed. */ virtual void rename(const std::string& new_title) = 0; /** Checks if the given user is subscribed to this document. */ virtual bool is_subscribed(const user& user) const; /** Returns the begin of the list of subscribed users. */ user_iterator user_begin() const; /** Returns the end of the list of subscrbed users. */ user_iterator user_end() const; /** Returns the amount of subscribed users. */ user_size_type user_count() const; /** Signal which is emitted if the documents gets renamed. */ signal_rename_type rename_event() const; /** Signal which is emitted if a user subscribed to this document. */ signal_subscribe_type subscribe_event() const; /** Signal which is emitted if a user unsubscribed from this document. */ signal_unsubscribe_type unsubscribe_event() const; /** Called by the buffer when the session has been closed. */ virtual void obby_session_close(); /** Called by the buffer when a user has joined. */ virtual void obby_user_join(const user& user); /** Called by the buffer when a user has left. */ virtual void obby_user_part(const user& user); protected: /** Subscribes a user to this document. */ virtual void user_subscribe(const user& user); /** Unsubscribes a user from this document. */ virtual void user_unsubscribe(const user& user); /** Internally renames the document. */ void document_rename(const std::string& title, unsigned int suffix); /** Internal function to create the underlaying document. */ void assign_document(); /** Internal function to release the underlaying document. */ void release_document(); /** @brief Implementation of session_close() that does not call * base functions. */ void session_close_impl(); const buffer_type& m_buffer; net_type* m_net; const user* m_owner; unsigned int m_id; std::string m_title; unsigned int m_suffix; std::string m_encoding; std::auto_ptr m_priv_table; std::auto_ptr m_document; std::list m_users; signal_rename_type m_signal_rename; signal_subscribe_type m_signal_subscribe; signal_unsubscribe_type m_signal_unsubscribe; public: /** Returns the buffer to which this document_info belongs. */ const buffer_type& get_buffer() const; protected: /** Returns the underlaying net6 object through which requests are * transmitted. */ net_type& get_net6(); /** Returns the underlaying net6 object through which requests are * transmitted. */ const net_type& get_net6() const; }; template const typename basic_document_info::privileges basic_document_info::privileges::NONE = typename basic_document_info::privileges(0x00000000); template const typename basic_document_info::privileges basic_document_info::privileges::SUBSCRIBE = typename basic_document_info::privileges(0x00000001); template const typename basic_document_info::privileges basic_document_info::privileges::MODIFY = typename basic_document_info::privileges(0x00000002); template const typename basic_document_info::privileges basic_document_info::privileges::CLOSE = typename basic_document_info::privileges(0x00000004); template const typename basic_document_info::privileges basic_document_info::privileges::RENAME = typename basic_document_info::privileges(0x00000008); template const typename basic_document_info::privileges basic_document_info::privileges::ADMIN = typename basic_document_info::privileges(0x00000010); template const typename basic_document_info::privileges basic_document_info::privileges::ALL = typename basic_document_info::privileges(0xffffffff); /** Table that stores the privileges for multiple users. */ template class basic_document_info::privileges_table { public: typedef sigc::signal signal_privileges_changed_type; /** Creates a new privileges_table with the given default privileges. * The default privileges are used if there is no privileges entry for * a user. */ privileges_table(privileges default_privileges); /** Returns the default privileges set for this table. */ privileges get_default_privileges() const; /** Queries the privileges for the given user. */ privileges privileges_query(const user& user, privileges privs = privileges::ALL) const; /** Changes the privileges of a user. */ void privileges_change(const user& user, privileges privs); /** Signal which is emitted if the privileges for a given user have * changed (by a document administrator or the document owner). */ signal_privileges_changed_type privileges_changed_event() const; protected: typedef std::map priv_map; privileges m_default_privs; priv_map m_privs; signal_privileges_changed_type m_signal_privileges_changed; }; } // namespace obby namespace serialise { // Default context - lookup by owner and ID template class document_info_context_to: public context_base_to { public: typedef DocumentInfo* data_type; virtual std::string to_string(const data_type& from) const; protected: virtual void on_stream_setup(std::stringstream& stream) const; }; template class default_context_to*>: public document_info_context_to< obby::basic_document_info > { }; template class default_context_to*>: public document_info_context_to< const obby::basic_document_info > { }; template class document_info_context_from: public context_base_from { public: typedef DocumentInfo* data_type; typedef typename DocumentInfo::buffer_type buffer_type; document_info_context_from(const buffer_type& buffer); virtual data_type from_string(const std::string& from) const; protected: virtual void on_stream_setup(std::stringstream& stream) const; const buffer_type& m_buffer; }; template class document_info_hex_context_from: public document_info_context_from { public: typedef typename document_info_context_from::buffer_type buffer_type; document_info_hex_context_from(const buffer_type& buffer); protected: virtual void on_stream_setup(std::stringstream& stream) const; }; template class default_context_from*>: public document_info_context_from< obby::basic_document_info > { public: typedef typename document_info_context_from< obby::basic_document_info >::buffer_type buffer_type; default_context_from(const buffer_type& buffer); }; template class default_context_from< const obby::basic_document_info* >: public document_info_context_from< const obby::basic_document_info > { public: typedef typename document_info_context_from< const obby::basic_document_info >::buffer_type buffer_type; default_context_from(const buffer_type& buffer); }; template class hex_context_from*>: public document_info_hex_context_from< obby::basic_document_info > { public: typedef typename document_info_context_from< obby::basic_document_info >::buffer_type buffer_type; hex_context_from(const buffer_type& buffer); }; template class hex_context_from*>: public document_info_hex_context_from< const obby::basic_document_info > { public: typedef typename document_info_context_from< const obby::basic_document_info >::buffer_type buffer_type; hex_context_from(const buffer_type& buffer); }; template std::string document_info_context_to:: to_string(const data_type& from) const { std::stringstream stream; on_stream_setup(stream); stream << from->get_owner_id() << ' ' << from->get_id(); return stream.str(); } template void document_info_context_to:: on_stream_setup(std::stringstream& stream) const { } template document_info_context_from:: document_info_context_from(const buffer_type& buffer): m_buffer(buffer) { } template typename document_info_context_from::data_type document_info_context_from:: from_string(const std::string& from) const { // Read document and owner id unsigned int owner_id, document_id; std::stringstream stream(from); on_stream_setup(stream); stream >> owner_id >> document_id; // Successful conversion? if(stream.bad() ) throw conversion_error("Document ID ought to be two integers"); // Lookup document data_type info = m_buffer.document_find(owner_id, document_id); if(info == NULL) { // No such document obby::format_string str("Document ID %0%/%1% does not exist"); str << owner_id << document_id; throw conversion_error(str.str() ); } return info; } template void document_info_context_from:: on_stream_setup(std::stringstream& stream) const { } template document_info_hex_context_from:: document_info_hex_context_from(const buffer_type& buffer): document_info_context_from(buffer) { } template void document_info_hex_context_from:: on_stream_setup(std::stringstream& stream) const { stream >> std::hex; } template default_context_from*>:: default_context_from(const buffer_type& buffer): document_info_context_from< obby::basic_document_info >(buffer) { } template default_context_from*>:: default_context_from(const buffer_type& buffer): document_info_context_from< const obby::basic_document_info >(buffer) { } template hex_context_from*>:: hex_context_from(const buffer_type& buffer): document_info_hex_context_from< obby::basic_document_info >(buffer) { } template hex_context_from*>:: hex_context_from(const buffer_type& buffer): document_info_hex_context_from< const obby::basic_document_info >(buffer) { } } // namespace serialise namespace obby { // Command context - lookup by document title template class command_document_info_context_from: public ::serialise::context_base_from { public: typedef typename DocumentInfo::buffer_type buffer_type; typedef DocumentInfo* data_type; command_document_info_context_from(const buffer_type& buffer); virtual data_type from_string(const std::string& from) const; protected: const buffer_type& m_buffer; }; template class command_context_from*>: public command_document_info_context_from< obby::basic_document_info > { public: typedef typename command_document_info_context_from< obby::basic_document_info >::buffer_type buffer_type; command_context_from(const buffer_type& buffer); }; template class command_context_from*>: public command_document_info_context_from< const obby::basic_document_info > { public: typedef typename command_document_info_context_from< const obby::basic_document_info >::buffer_type buffer_type; command_context_from(const buffer_type& buffer); }; template command_document_info_context_from:: command_document_info_context_from(const buffer_type& buffer): m_buffer(buffer) { } template typename command_document_info_context_from::data_type command_document_info_context_from:: from_string(const std::string& from) const { for(typename buffer_type::document_iterator iter = m_buffer.document_begin(); iter != m_buffer.document_end(); ++ iter) { if(iter->get_suffixed_title() == from) return &(*iter); } throw ::serialise::conversion_error( "Document with title " + from + " does not exist" ); } template command_context_from*>:: command_context_from(const buffer_type& buffer): command_document_info_context_from< obby::basic_document_info >(buffer) { } template command_context_from*>:: command_context_from(const buffer_type& buffer): command_document_info_context_from< const obby::basic_document_info >(buffer) { } // Document info implementation template basic_document_info:: basic_document_info(const buffer_type& buffer, net_type& net, const user* owner, unsigned int id, const std::string& title, unsigned int suffix, const std::string& encoding): m_buffer(buffer), m_net(&net), m_owner(owner), m_id(id), m_title(title), m_suffix(suffix), m_encoding(encoding), m_priv_table( new privileges_table(privileges::SUBSCRIBE | privileges::MODIFY) ) { } template basic_document_info:: basic_document_info(const buffer_type& buffer, net_type& net, const user* owner, unsigned int id, const std::string& title, const std::string& encoding): m_buffer(buffer), m_net(&net), m_owner(owner), m_id(id), m_title(title), m_suffix(buffer.find_free_suffix(title, this)), m_encoding(encoding), m_priv_table( new privileges_table(privileges::SUBSCRIBE | privileges::MODIFY) ) { } template basic_document_info:: basic_document_info(const buffer_type& buffer, net_type& net, const serialise::object& obj): m_buffer(buffer), m_net(&net), m_owner( obj.get_required_attribute("owner"). obby::serialise::attribute::as( ::serialise::default_context_from( buffer.get_user_table() ) ) ), m_id( obj.get_required_attribute("id"). obby::serialise::attribute::as() ), m_title( obj.get_required_attribute("title"). obby::serialise::attribute::as() ), m_suffix( obj.get_required_attribute("suffix"). obby::serialise::attribute::as() ), m_encoding( obj.get_required_attribute("encoding"). obby::serialise::attribute::as() ), m_priv_table( new privileges_table(privileges::SUBSCRIBE | privileges::MODIFY) ) { } template basic_document_info:: basic_document_info(const buffer_type& buffer, net_type& net, const net6::packet& init_pack): m_buffer(buffer), m_net(&net), m_owner( init_pack.get_param(0).net6::parameter::as( ::serialise::hex_context_from( buffer.get_user_table() ) ) ), m_id(init_pack.get_param(1).net6::parameter::as() ), m_title(init_pack.get_param(2).net6::parameter::as() ), m_suffix(init_pack.get_param(3).net6::parameter::as() ), m_encoding(init_pack.get_param(4).net6::parameter::as() ), m_priv_table( new privileges_table(privileges::SUBSCRIBE | privileges::MODIFY) ) { } template void basic_document_info:: serialise(serialise::object& obj) const { /* Cannot serialise an object whose content we do not have */ if(m_document.get() == NULL) { throw std::logic_error( "obby::basic_document_info::serialise:\n" "No content available, probably not subscribed" ); } obj.add_attribute("owner").set_value(m_owner); obj.add_attribute("id").set_value(m_id); obj.add_attribute("title").set_value(m_title); obj.add_attribute("suffix").set_value(m_suffix); obj.add_attribute("encoding").set_value(m_encoding); for(typename document_type::chunk_iterator chunk_it = m_document->chunk_begin(); chunk_it != m_document->chunk_end(); ++ chunk_it) { serialise::object& chunk = obj.add_child(); chunk.set_name("chunk"); chunk.add_attribute("content").set_value(chunk_it.get_text() ); chunk.add_attribute("author").set_value(chunk_it.get_author() ); } } template const user* basic_document_info::get_owner() const { return m_owner; } template unsigned int basic_document_info::get_owner_id() const { if(m_owner == NULL) return 0; return m_owner->get_id(); } template unsigned int basic_document_info::get_id() const { return m_id; } template const std::string& basic_document_info::get_title() const { return m_title; } template unsigned int basic_document_info::get_suffix() const { return m_suffix; } template std::string basic_document_info::get_suffixed_title() const { if(m_suffix != 1) { format_string str("%0% (%1%)"); str << m_title << m_suffix; return str.str(); } return m_title; } template const std::string& basic_document_info::get_encoding() const { return m_encoding; } template const Document& basic_document_info::get_content() const { if(m_document.get() == NULL) { throw std::logic_error( "obby::basic_document_info::get_content:\n" "No content available, probably not subscribed" ); } return *m_document; } template const typename basic_document_info::privileges_table& basic_document_info::get_privileges_table() const { return *m_priv_table; } template bool basic_document_info:: is_subscribed(const user& user) const { // TODO: Use std::set return std::find( m_users.begin(), m_users.end(), &user ) != m_users.end(); } template typename basic_document_info::user_iterator basic_document_info::user_begin() const { return m_users.begin(); } template typename basic_document_info::user_iterator basic_document_info::user_end() const { return m_users.end(); } template typename basic_document_info::user_size_type basic_document_info::user_count() const { return m_users.size(); } template typename basic_document_info::signal_rename_type basic_document_info::rename_event() const { return m_signal_rename; } template typename basic_document_info::signal_subscribe_type basic_document_info::subscribe_event() const { return m_signal_subscribe; } template typename basic_document_info::signal_unsubscribe_type basic_document_info::unsubscribe_event() const { return m_signal_unsubscribe; } template void basic_document_info::obby_session_close() { session_close_impl(); } template void basic_document_info::obby_user_join(const user& user) { } template void basic_document_info::obby_user_part(const user& user) { // User left the session: Unsubscribe from document if(is_subscribed(user) ) user_unsubscribe(user); } template void basic_document_info::user_subscribe(const user& user) { if(is_subscribed(user) ) { throw std::logic_error( "obby::basic_document_info::user_subscribe:\n" "User is already subscribed" ); } m_users.push_back(&user); m_signal_subscribe.emit(user); } template void basic_document_info::user_unsubscribe(const user& user) { if(!is_subscribed(user) ) { throw std::logic_error( "obby::basic_document_info::user_unsubscribe:\n" "User is not subscribed" ); } m_users.erase( std::remove(m_users.begin(), m_users.end(), &user), m_users.end() ); m_signal_unsubscribe.emit(user); } template void basic_document_info:: document_rename(const std::string& title, unsigned int suffix) { m_title = title; m_suffix = suffix; m_signal_rename.emit(title); } template void basic_document_info::assign_document() { // TODO: Document template given to buffer that may be copied here. // TODO: Give only user table to document m_document.reset(new Document(get_buffer().get_document_template() )); } template void basic_document_info::release_document() { m_document.reset(NULL); } template void basic_document_info::session_close_impl() { // Drop network reference m_net = NULL; } template const typename basic_document_info::buffer_type& basic_document_info::get_buffer() const { return m_buffer; } template typename basic_document_info::net_type& basic_document_info::get_net6() { if(m_net == NULL) { throw std::logic_error( "obby::basic_document_info::::get_net6:\n" "No network object available. Most probably the " "session has been closed" ); } return *m_net; } template const typename basic_document_info::net_type& basic_document_info::get_net6() const { if(m_net == NULL) { throw std::logic_error( "obby::basic_document_info::::get_net6:\n" "No network object available. Most probably the " "session has been closed" ); } return *m_net; } // privileges_table template basic_document_info::privileges_table:: privileges_table(privileges default_privileges): m_default_privs(default_privileges) { } template typename basic_document_info::privileges basic_document_info::privileges_table:: get_default_privileges() const { return m_default_privs; } template typename basic_document_info::privileges basic_document_info::privileges_table:: privileges_query(const user& user, privileges privs) const { typename priv_map::const_iterator iter = m_privs.find(&user); if(iter == m_privs.end() ) return m_default_privs & privs; return iter->second & privs; } template void basic_document_info::privileges_table:: privileges_change(const user& user, privileges privs) { m_privs[&user] = privs; m_signal_privileges_changed.emit(user, privs); } template typename basic_document_info::privileges_table:: signal_privileges_changed_type basic_document_info::privileges_table:: privileges_changed_event() const { return m_signal_privileges_changed; } } // namespace obby #endif // _OBBY_DOCUMENT_INFO_HPP_