/* 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_SERVER_BUFFER_HPP_
#define _OBBY_SERVER_BUFFER_HPP_
#include "serialise/error.hpp"
#include "serialise/parser.hpp"
#include "common.hpp"
#include "error.hpp"
#include "command.hpp"
#include "buffer.hpp"
#include "server_document_info.hpp"
namespace obby
{
/** Buffer that serves as (dedicated) server. It listens for incoming
* connections from client_buffers and synchronises their changes.
*/
template<typename Document, typename Selector>
class basic_server_buffer:
virtual public basic_buffer<Document, Selector>
{
public:
// Document info
typedef typename basic_buffer<Document, Selector>::
base_document_info_type base_document_info_type;
typedef basic_server_document_info<Document, Selector>
document_info_type;
// Network
typedef typename basic_buffer<Document, Selector>::
base_net_type base_net_type;
typedef net6::basic_server<Selector> net_type;
// Signal
typedef sigc::signal<void, const net6::user&> signal_connect_type;
typedef sigc::signal<void, const net6::user&> signal_disconnect_type;
/** Default constructor.
*/
basic_server_buffer();
/** Opens the server on the given port.
*/
virtual void open(unsigned int port);
/** Opens the server on the given port and resumes the obby session
* stored in the given file.
*
* Note that the session deserialisation is not atomic. So, if an
* error occured while loading the session, the session is partly
* loaded (but the server is closed) and the old session is gone.
*
* Keep an old buffer while trying to open a new one if you would like
* to keep old data on deserialisation error.
*/
virtual void open(const std::string& session, unsigned int port);
/** Closes the obby session.
*/
virtual void close();
/** Changes the global password for this session.
*/
void set_global_password(const std::string& password);
/** Creates a new document with predefined content.
* signal_document_insert will be emitted and may be used to access
* the resulting obby::document_info.
*/
virtual void document_create(const std::string& title,
const std::string& encoding,
const std::string& content = "");
/** Removes an existing document.
*/
virtual void document_remove(base_document_info_type& doc);
/** Looks for a document with the given ID which belongs to the user
* with the given owner ID. Note that we do not take a real user object
* here because the ID is enough and one might not have a user object
* to the corresponding ID. So a time-consuming lookup is obsolete.
*/
document_info_type* document_find(unsigned int owner_id,
unsigned int id) const;
/** Sends a global message to all users.
*/
virtual void send_message(const std::string& message);
/** Sends a message to the given user.
*/
virtual void send_message(const std::string& message, const user& to);
/** @brief Sets whether keepalives are sent to clients.
*
* With this option enabled, the server sends keepalive packets to
* client if the connection is idle to make sure that the
* connection has not gone away.
*/
void set_enable_keepalives(bool enable);
/** @brief Provides access to the server's command map.
*/
command_map& get_command_map();
/** @brief Provides access to the server's command map.
*/
const command_map& get_command_map() const;
/** Signal which will be emitted if a new client has connected.
*/
signal_connect_type connect_event() const;
/** Signal which will be emitted if a connected client has quit.
*/
signal_disconnect_type disconnect_event() const;
protected:
/** Registers net6 signal handlers. May be used by derived classes
* which override the server_buffer constructor.
*/
void register_signal_handlers();
/** Creates a new document info object according to the type of buffer.
*/
virtual base_document_info_type*
new_document_info(const user* owner,
unsigned int id,
const std::string& title,
const std::string& encoding,
const std::string& content);
/** Creates a new document info deserialised from a serialisation
* object according to the type of buffer.
*/
virtual base_document_info_type*
new_document_info(const serialise::object& obj);
/** Creates the underlaying net6 network object corresponding to the
* buffer's type.
*/
virtual base_net_type* new_net();
/** Internal function to create a document with the given owner.
*/
void document_create_impl(const obby::user* owner,
unsigned int id,
const std::string& title,
const std::string& encoding,
const std::string& content);
/** Internal function send a message to all users that comes from
* the user <em>writer</em>.
*/
void send_message_impl(const std::string& message,
const obby::user* writer);
/** Internal function send a message to a given user that comes from
* the user <em>writer</em>.
*/
void send_message_impl(const std::string& message,
const obby::user* writer,
const obby::user& to);
/** Changes the colour of the given user to the new provided
* colour and relays the fact to the other users.
*/
void user_colour_impl(const obby::user& user,
const colour& colour);
/** net6 signal handlers.
*/
void on_connect(const net6::user& user6);
void on_disconnect(const net6::user& user6);
void on_join(const net6::user& user6);
void on_part(const net6::user& user6);
bool on_auth(const net6::user& user6,
const net6::packet& pack,
net6::login::error& error);
void on_login(const net6::user& user6,
const net6::packet& pack);
void on_extend(const net6::user& user6,
net6::packet& pack);
void on_data(const net6::user& from,
const net6::packet& pack);
/** Executes a network packet.
*/
bool execute_packet(const net6::packet& pack, const user& from);
/** Document commands.
*/
virtual void on_net_document_create(const net6::packet& pack,
const user& from);
virtual void on_net_document_remove(const net6::packet& pack,
const user& from);
/** Messaging commands.
*/
virtual void on_net_message(const net6::packet& pack, const user& from);
/** User commands.
*/
virtual void on_net_user_password(const net6::packet& pack,
const user& from);
virtual void on_net_user_colour(const net6::packet& pack,
const user& from);
/** Forwarding commands.
*/
virtual void on_net_document(const net6::packet& pack,
const user& from);
/** Command commands ;-)
*/
virtual void on_net_command_query(const net6::packet& pack,
const user& from);
/** Commands.
*/
command_result on_command_emote(const user& from,
const std::string& paramlist);
/** @brief Closes a session.
*/
virtual void session_close();
/** @brief Implementation of session_close() that does not call
* a base function.
*/
void session_close_impl();
/** Global session password. Only people who know this password are
* allowed to join the session.
*/
std::string m_global_password;
bool m_enable_keepalives;
signal_connect_type m_signal_connect;
signal_disconnect_type m_signal_disconnect;
command_map m_command_map;
private:
void reopen_impl(unsigned int port);
/** This function provides access to the underlaying net6::basic_server
* object.
*/
net_type& net6_server();
/** This fucntion provides access to the underlaying net6::basic_server
* object.
*/
const net_type& net6_server() const;
};
typedef basic_server_buffer<obby::document, net6::selector> server_buffer;
template<typename Document, typename Selector>
basic_server_buffer<Document, Selector>::
basic_server_buffer():
basic_buffer<Document, Selector>(),
m_enable_keepalives(false)
{
// Note that the command description is translated on server side.
// We cannot just send a number or something that the client converts
// to localised text since the client does not know the available
// commands (and neither their descriptions).
m_command_map.add_command(
"me",
_("Sends an action to the chat."),
sigc::mem_fun(*this, &basic_server_buffer::on_command_emote)
);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::reopen_impl(unsigned int port)
{
if(IPV6_ENABLED)
{
// Prefer IPv6 when compiled with IPv6 support,
// fallback to IPv4
try
{
net6_server().reopen(port, true);
}
catch(net6::error& e)
{
net6_server().reopen(port, false);
}
}
else
{
net6_server().reopen(port, false);
}
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::open(unsigned int port)
{
if(basic_buffer<Document, Selector>::is_open() )
{
throw std::logic_error(
"obby::basic_server_buffer::open:\n"
"Server is already open"
);
}
basic_buffer<Document, Selector>::m_net.reset(new_net());
register_signal_handlers();
reopen_impl(port);
// Clear previous documents and users
basic_buffer<Document, Selector>::document_clear();
basic_buffer<Document, Selector>::m_user_table.clear();
basic_buffer<Document, Selector>::m_signal_sync_init.emit(0);
basic_buffer<Document, Selector>::m_signal_sync_final.emit();
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::open(const std::string& session,
unsigned int port)
{
// TODO: Close server when session deserialisation failed
if(basic_buffer<Document, Selector>::is_open() )
{
throw std::logic_error(
"obby::basic_server_buffer::open:\n"
"Server is already open"
);
}
// Open server
basic_buffer<Document, Selector>::m_net.reset(new_net());
register_signal_handlers();
reopen_impl(port);
// Deserialise file
serialise::parser parser;
parser.deserialise(session);
// Clear previous documents and users
basic_buffer<Document, Selector>::document_clear();
basic_buffer<Document, Selector>::m_user_table.clear();
basic_buffer<Document, Selector>::m_signal_sync_init.emit(0);
if(parser.get_type() != "obby")
throw serialise::error(_("File is not an obby document"), 1);
// Get root object, verify that it is an obby session
serialise::object& root = parser.get_root();
if(root.get_name() != "session")
{
throw serialise::error(
_("File is not a stored obby session"),
root.get_line()
);
}
serialise::attribute& version_attr =
root.get_required_attribute("version");
// TODO: Check version for incompatibilites
// TODO: Block higher version files
// Check children
for(serialise::object::child_iterator iter = root.children_begin();
iter != root.children_end();
++ iter)
{
if(iter->get_name() == "user_table")
{
// Stored user table
basic_buffer<Document, Selector>::
m_user_table.deserialise(
*iter
);
}
else if(iter->get_name() == "chat")
{
// Stored chat history
basic_buffer<Document, Selector>::m_chat.deserialise(
*iter,
basic_buffer<Document, Selector>::m_user_table
);
}
else if(iter->get_name() == "document")
{
// Stored document, load it
base_document_info_type* info =
new_document_info(*iter);
// Add to list
basic_buffer<Document, Selector>::document_add(*info);
}
else
{
// Unexpected child
// TODO: unexpected_child_error
format_string str(_("Unexpected child node: '%0%'") );
str << iter->get_name();
throw serialise::error(str.str(), iter->get_line() );
}
}
basic_buffer<Document, Selector>::m_signal_sync_final.emit();
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::close()
{
if(!basic_buffer<Document, Selector>::is_open() )
{
throw std::logic_error(
"obby::basic_server_buffer::close:\n"
"Server is not open"
);
}
// Virtual call - host buffer overwrites this to keep local
// user subscribed
session_close();
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
set_global_password(const std::string& password)
{
m_global_password = password;
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
document_create(const std::string& title,
const std::string& encoding,
const std::string& content)
{
// TODO: m_doc_counter should not be declared in basic_buffer
// but client_buffer / server_buffer separately
unsigned int id = ++ basic_buffer<Document, Selector>::m_doc_counter;
// Create the document with the special owner NULL which means that
// this document was created by the server.
document_create_impl(NULL, id, title, encoding, content);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
document_remove(base_document_info_type& info)
{
if(basic_buffer<Document, Selector>::is_open() )
{
// Emit unsubscribe signal for all users that were
// subscribed to this document
// TODO: Do this in document_delete
for(typename document_info_type::user_iterator user_iter =
info.user_begin();
user_iter != info.user_end();
++ user_iter)
{
info.unsubscribe_event().emit(*user_iter);
}
// Tell other clients about removal
net6::packet remove_pack("obby_document_remove");
remove_pack << &info;
net6_server().send(remove_pack);
}
// Delete document
basic_buffer<Document, Selector>::document_delete(info);
}
template<typename Document, typename Selector>
typename basic_server_buffer<Document, Selector>::document_info_type*
basic_server_buffer<Document, Selector>::
document_find(unsigned int owner_id,
unsigned int id) const
{
return dynamic_cast<document_info_type*>(
basic_buffer<Document, Selector>::document_find(owner_id, id)
);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
send_message(const std::string& message)
{
// send_message_impl relays the message to the clients
send_message_impl(message, NULL);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
send_message(const std::string& message, const user& to)
{
// send_message_impl relays the message to the clients
send_message_impl(message, NULL, to);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
set_enable_keepalives(bool enable)
{
if(m_enable_keepalives == enable) return;
m_enable_keepalives = enable;
user_table& table = this->m_user_table;
for(user_table::iterator iter =
table.begin(user::flags::CONNECTED, user::flags::NONE);
iter != table.end(user::flags::CONNECTED, user::flags::NONE);
++ iter)
{
// Dirty hack for host to work (local user...). We need
// user::flags::DIRECT_CONNECTION and user::flags::LOCAL!
try
{
iter->get_net6().set_enable_keepalives(enable);
} catch(...) {}
}
}
template<typename Document, typename Selector>
command_map& basic_server_buffer<Document, Selector>::get_command_map()
{
return m_command_map;
}
template<typename Document, typename Selector>
const command_map& basic_server_buffer<Document, Selector>::
get_command_map() const
{
return m_command_map;
}
template<typename Document, typename Selector>
typename basic_server_buffer<Document, Selector>::signal_connect_type
basic_server_buffer<Document, Selector>::connect_event() const
{
return m_signal_connect;
}
template<typename Document, typename Selector>
typename basic_server_buffer<Document, Selector>::signal_disconnect_type
basic_server_buffer<Document, Selector>::disconnect_event() const
{
return m_signal_disconnect;
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
document_create_impl(const user* owner,
unsigned int id,
const std::string& title,
const std::string& encoding,
const std::string& content)
{
if(!basic_buffer<Document, Selector>::is_open() )
{
throw std::logic_error(
"obby::basic_server_buffer::document_create_impl:\n"
"Document creation while server is closed"
);
}
// No need to ignore since the document is not yet in
// the document list
unsigned int suffix =
basic_buffer<Document, Selector>::find_free_suffix(title, NULL);
// Create new document
base_document_info_type* info =
new_document_info(owner, id, title, encoding, content);
// Add to buffer
basic_buffer<Document, Selector>::document_add(*info);
// Publish the new document to the users. Note that we do not have
// to send the document's initial content because no user is currently
// subscribed, and the content is synced with the subscription.
net6::packet pack("obby_document_create");
pack << owner << id << title << suffix << encoding;
// TODO: send_to_all_except function or something
// or, better: user_table.send() with given flags.
for(user_table::iterator iter = basic_buffer<Document, Selector>::
m_user_table.begin(user::flags::CONNECTED, user::flags::NONE);
iter != basic_buffer<Document, Selector>::
m_user_table.end(user::flags::CONNECTED, user::flags::NONE);
++ iter)
{
// The owner already knows about the document.
if(&(*iter) != owner)
net6_server().send(pack, iter->get_net6() );
}
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
send_message_impl(const std::string& message, const user* writer, const user& to)
{
if(basic_buffer<Document, Selector>::is_open())
{
net6::packet message_pack("obby_message");
message_pack << writer << message;
net6_server().send(message_pack, to.get_net6());
}
if(writer == NULL)
{
basic_buffer<Document, Selector>::m_chat.add_server_message(
message
);
}
else
{
basic_buffer<Document, Selector>::m_chat.add_user_message(
message,
*writer
);
}
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
send_message_impl(const std::string& message, const user* writer)
{
if(basic_buffer<Document, Selector>::is_open() )
{
net6::packet message_pack("obby_message");
message_pack << writer << message;
net6_server().send(message_pack);
}
if(writer == NULL)
{
basic_buffer<Document, Selector>::m_chat.add_server_message(
message
);
}
else
{
basic_buffer<Document, Selector>::m_chat.add_user_message(
message,
*writer
);
}
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
user_colour_impl(const obby::user& user,
const colour& colour)
{
// TODO: user_colour_impl should check for color conflicts - should it?
basic_buffer<Document, Selector>::
m_user_table.set_user_colour(user, colour);
// TODO: user::set_colour should emit this signal
basic_buffer<Document, Selector>::m_signal_user_colour.emit(user);
if(basic_buffer<Document, Selector>::is_open() )
{
net6::packet colour_pack("obby_user_colour");
colour_pack << &user << colour;
net6_server().send(colour_pack);
}
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
on_connect(const net6::user& user6)
{
// Send our protocol version.
net6::packet welcome_pack("obby_welcome");
welcome_pack << PROTOCOL_VERSION;
net6_server().send(welcome_pack, user6);
// Request encryption after welcome packet.
net6_server().request_encryption(user6);
user6.set_enable_keepalives(m_enable_keepalives);
// User connected
m_signal_connect.emit(user6);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
on_disconnect(const net6::user& user6)
{
m_signal_disconnect.emit(user6);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::on_join(const net6::user& user6)
{
// Find user in list
const user* new_user =
basic_buffer<Document, Selector>::m_user_table.find(
user6,
user::flags::CONNECTED,
user::flags::NONE
);
// Should not happen
if(new_user == NULL)
{
format_string str("User %0% is not connected");
str << user6.get_id();
throw net6::bad_value(str.str() );
}
// Synchronise non-connected users.
for(user_table::iterator iter = basic_buffer<Document, Selector>::
m_user_table.begin(user::flags::NONE, user::flags::CONNECTED);
iter != basic_buffer<Document, Selector>::
m_user_table.end(user::flags::NONE, user::flags::CONNECTED);
++ iter)
{
net6::packet user_pack("obby_sync_usertable_user");
user_pack << iter->get_id() << iter->get_name()
<< iter->get_colour();
net6_server().send(user_pack, user6);
}
// Synchronise the document list
for(typename basic_buffer<Document, Selector>::document_iterator iter =
basic_buffer<Document, Selector>::document_begin();
iter != basic_buffer<Document, Selector>::document_end();
++ iter)
{
// Setup document packet
net6::packet document_pack("obby_sync_doclist_document");
// TODO: Creation of this packet should be a method of
// server_document_info
document_pack << iter->get_owner() << iter->get_id()
<< iter->get_title() << iter->get_suffix()
<< iter->get_encoding();
// Add users that are subscribed
for(typename document_info_type::user_iterator user_iter =
iter->user_begin();
user_iter != iter->user_end();
++ user_iter)
{
document_pack << &(*user_iter);
}
net6_server().send(document_pack, user6);
}
// Done with synchronising
net6::packet final_pack("obby_sync_final");
net6_server().send(final_pack, user6);
// Forward join message to documents
// TODO: Let the documents connect to signal_user_join
for(typename basic_buffer<Document, Selector>::document_iterator iter =
basic_buffer<Document, Selector>::document_begin();
iter != basic_buffer<Document, Selector>::document_end();
++ iter)
{
iter->obby_user_join(*new_user);
}
// User joined successfully; emit user_join signal
// TODO: Move to user_table::add_user
basic_buffer<Document, Selector>::m_signal_user_join.emit(*new_user);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::on_part(const net6::user& user6)
{
// Find obby::user object for given net6::user
const user* cur_user =
basic_buffer<Document, Selector>::m_user_table.find(
user6,
user::flags::CONNECTED,
user::flags::NONE
);
// Should not happen
if(cur_user == NULL)
{
format_string str("User %0% is not connected");
str << user6.get_id();
throw net6::bad_value(str.str() );
}
// Forward part message to documents
// TODO: Let the documents connect to signal_user_part
for(typename basic_buffer<Document, Selector>::document_iterator iter =
basic_buffer<Document, Selector>::document_begin();
iter != basic_buffer<Document, Selector>::document_end();
++ iter)
{
iter->obby_user_part(*cur_user);
}
// Emit part signal, remove user from list
// TODO: Move part signal emission to remove_user
basic_buffer<Document, Selector>::m_signal_user_part.emit(*cur_user);
basic_buffer<Document, Selector>::m_user_table.remove_user(*cur_user);
}
template<typename Document, typename Selector>
bool basic_server_buffer<Document, Selector>::
on_auth(const net6::user& user6,
const net6::packet& pack,
net6::login::error& error)
{
// Do not allow joins from clients whose connection is not encrypted
if(!user6.is_encrypted() )
{
error = login::ERROR_NOT_ENCRYPTED;
return false;
}
const std::string name =
pack.get_param(0).net6::parameter::as<std::string>();
colour colour =
pack.get_param(1).net6::parameter::as<obby::colour>();
// Get global and user password, if given
std::string global_password, user_password;
if(pack.get_param_count() > 2)
global_password =
pack.get_param(2).net6::parameter::as<std::string>();
if(pack.get_param_count() > 3)
user_password =
pack.get_param(3).net6::parameter::as<std::string>();
// Check colour
if(!basic_buffer<Document, Selector>::check_colour(colour) )
{
error = login::ERROR_COLOUR_IN_USE;
return false;
}
// Check global password
if(!m_global_password.empty() )
{
if(global_password != m_global_password)
{
error = login::ERROR_WRONG_GLOBAL_PASSWORD;
return false;
}
}
// Search user in user table
const obby::user* user =
basic_buffer<Document, Selector>::m_user_table.find(
name,
user::flags::NONE,
user::flags::CONNECTED
);
// Compare user password
if(user && !user->get_password().empty() )
{
if(user_password != user->get_password() )
{
error = login::ERROR_WRONG_USER_PASSWORD;
return false;
}
}
// Auth OK
return true;
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
on_login(const net6::user& user6,
const net6::packet& pack)
{
// Get color
colour colour =
pack.get_param(1).net6::parameter::as<obby::colour>();
// Choose free user ID (note that this is another ID as the net6
// user ID because this ID must remain valid over multiple sessions).
unsigned int user_id =
basic_buffer<Document, Selector>::m_user_table.find_free_id();
// Insert into user list
const user* new_user =
basic_buffer<Document, Selector>::m_user_table.add_user(
user_id, user6, colour
);
// Send initial sync packet; this is here in on_login() for it to
// happen before net6 syncs its users.
// Calculate number of required sync packets (one for each
// non-connected user, one for each document in the list).
unsigned int sync_n = basic_buffer<Document, Selector>::
m_user_table.count(user::flags::NONE, user::flags::NONE);
sync_n += basic_buffer<Document, Selector>::document_count();
// Send initial sync packet with this number, the client may then show
// a progressbar or something.
net6::packet init_pack("obby_sync_init");
init_pack << sync_n;
net6_server().send(init_pack, user6);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::on_extend(const net6::user& user6,
net6::packet& pack)
{
// Find corresponding user in list
const user* cur_user =
basic_buffer<Document, Selector>::m_user_table.find(
user6,
user::flags::CONNECTED,
user::flags::NONE
);
// Should not happen
if(cur_user == NULL)
{
format_string str("User %0% is not connected");
str << user6.get_id();
throw net6::bad_value(str.str() );
}
// Extend user-join packet with colour and obby-ID
pack << cur_user->get_id() << cur_user->get_colour();
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::on_data(const net6::user& user6,
const net6::packet& pack)
{
// Get obby::user from net6::user
const user* from_user =
basic_buffer<Document, Selector>::m_user_table.find(
user6,
user::flags::CONNECTED,
user::flags::NONE
);
// Should not happen
if(from_user == NULL)
{
format_string str("User %0% is not connected");
str << user6.get_id();
throw net6::bad_value(str.str() );
}
// Execute packet
if(!execute_packet(pack, *from_user) )
{
throw net6::bad_value(
"Unexpected command: " + pack.get_command()
);
}
}
template<typename Document, typename Selector>
bool basic_server_buffer<Document, Selector>::
execute_packet(const net6::packet& pack, const user& from)
{
try
{
// TODO: std::map<> mapping command to function
if(pack.get_command() == "obby_document_create")
{ on_net_document_create(pack, from); return true; }
if(pack.get_command() == "obby_document_remove")
{ on_net_document_remove(pack, from); return true; }
if(pack.get_command() == "obby_message")
{ on_net_message(pack, from); return true; }
if(pack.get_command() == "obby_user_password")
{ on_net_user_password(pack, from); return true; }
if(pack.get_command() == "obby_user_colour")
{ on_net_user_colour(pack, from); return true; }
if(pack.get_command() == "obby_document")
{ on_net_document(pack, from); return true; }
if(pack.get_command() == "obby_command_query")
{ on_net_command_query(pack, from); return true; }
return false;
}
catch(std::logic_error& e)
{
// Print a meaningful error message to stderr why
// the offending user was dropped.
std::cerr << "obby logic error caught in connection to "
<< from.get_name() << ": " << e.what() << std::endl;
// Packet caused a logic error. Not good. Drop the client.
throw net6::bad_packet(e.what());
}
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
on_net_document_create(const net6::packet& pack,
const user& from)
{
unsigned int id =
pack.get_param(0).net6::parameter::as<unsigned int>();
const std::string title =
pack.get_param(1).net6::parameter::as<std::string>();
const std::string encoding =
pack.get_param(2).net6::parameter::as<std::string>();
const std::string content =
pack.get_param(3).net6::parameter::as<std::string>();
document_create_impl(&from, id, title, encoding, content);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
on_net_document_remove(const net6::packet& pack,
const user& from)
{
// Get document to remove
document_info_type& doc = dynamic_cast<document_info_type&>(
*pack.get_param(0).net6::parameter::as<
base_document_info_type*
>(::serialise::hex_context_from<base_document_info_type*>(
*this
))
);
// TODO: AUTH
// Remove it
document_remove(doc);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
on_net_message(const net6::packet& pack,
const user& from)
{
const std::string message =
pack.get_param(0).net6::parameter::as<std::string>();
send_message_impl(message, &from);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
on_net_user_password(const net6::packet& pack,
const user& from)
{
// Set password for this user
basic_buffer<Document, Selector>::m_user_table.set_user_password(
from,
pack.get_param(0).net6::parameter::as<std::string>()
);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
on_net_user_colour(const net6::packet& pack,
const user& from)
{
colour colour = pack.get_param(0).net6::parameter::as<obby::colour>();
// Check new user colour for conflicts
if(!basic_buffer<Document, Selector>::check_colour(colour, &from) )
{
net6::packet reply_pack("obby_user_colour_failed");
net6_server().send(reply_pack, from.get_net6() );
}
else
{
user_colour_impl(from, colour);
}
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
on_net_document(const net6::packet& pack,
const user& from)
{
document_info_type& info = dynamic_cast<document_info_type&>(
*pack.get_param(0).net6::parameter::as<
base_document_info_type*
>(::serialise::hex_context_from<base_document_info_type*>(
*this
))
);
// TODO: Rename this function. Think about providing a signal that may
// be emitted.
info.on_net_packet(document_packet(pack), from);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::
on_net_command_query(const net6::packet& pack,
const user& from)
{
unsigned int index = 0;
command_query query(pack, index);
command_result result = m_command_map.exec_command(from, query);
net6::packet reply_pack("obby_command_result");
result.append_packet(reply_pack);
net6_server().send(reply_pack, from.get_net6() );
}
template<typename Document, typename Selector>
command_result basic_server_buffer<Document, Selector>::
on_command_emote(const user& from,
const std::string& paramlist)
{
user_table& table = this->m_user_table;
net6::packet pack("obby_emote_message");
pack << &from << paramlist;
for(user_table::iterator iter =
table.begin(user::flags::CONNECTED, user::flags::NONE);
iter != table.end(user::flags::CONNECTED, user::flags::NONE);
++ iter)
{
// command_result will be sent to sender automatically, so
// there is no need to send emote message packet extra
if(&(*iter) == &from) continue;
net6_server().send(pack, iter->get_net6() );
}
basic_buffer<Document, Selector>::m_chat.add_emote_message(
paramlist,
from
);
return command_result(command_result::NO_REPLY);
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::session_close()
{
session_close_impl();
basic_buffer<Document, Selector>::session_close_impl();
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::session_close_impl()
{
// Session is closed, so all users have quit
user_table& table = this->m_user_table;
for(user_table::iterator iter =
table.begin(user::flags::CONNECTED, user::flags::NONE);
iter != table.end(user::flags::CONNECTED, user::flags::NONE);
++ iter)
{
// This call also unsubscribes the user from all documents
basic_buffer<Document, Selector>::user_part(*iter);
}
}
template<typename Document, typename Selector>
void basic_server_buffer<Document, Selector>::register_signal_handlers()
{
net6_server().connect_event().connect(
sigc::mem_fun(*this, &basic_server_buffer::on_connect) );
net6_server().disconnect_event().connect(
sigc::mem_fun(*this, &basic_server_buffer::on_disconnect) );
net6_server().join_event().connect(
sigc::mem_fun(*this, &basic_server_buffer::on_join) );
net6_server().part_event().connect(
sigc::mem_fun(*this, &basic_server_buffer::on_part) );
net6_server().login_auth_event().connect(
sigc::mem_fun(*this, &basic_server_buffer::on_auth) );
net6_server().login_event().connect(
sigc::mem_fun(*this, &basic_server_buffer::on_login) );
net6_server().login_extend_event().connect(
sigc::mem_fun(*this, &basic_server_buffer::on_extend) );
net6_server().data_event().connect(
sigc::mem_fun(*this, &basic_server_buffer::on_data) );
}
template<typename Document, typename Selector>
typename basic_server_buffer<Document, Selector>::base_document_info_type*
basic_server_buffer<Document, Selector>::
new_document_info(const user* owner,
unsigned int id,
const std::string& title,
const std::string& encoding,
const std::string& content)
{
// Create server_document_info, according to server_buffer
return new document_info_type(
*this, net6_server(), owner, id, title, encoding, content
);
}
template<typename Document, typename Selector>
typename basic_server_buffer<Document, Selector>::base_document_info_type*
basic_server_buffer<Document, Selector>::
new_document_info(const serialise::object& obj)
{
// Create server_document_info, according to server_buffer
return new document_info_type(*this, net6_server(), obj);
}
template<typename Document, typename Selector>
typename basic_server_buffer<Document, Selector>::base_net_type*
basic_server_buffer<Document, Selector>::new_net()
{
return new net_type();
}
template<typename Document, typename Selector>
typename basic_server_buffer<Document, Selector>::net_type&
basic_server_buffer<Document, Selector>::net6_server()
{
return dynamic_cast<net_type&>(
*basic_buffer<Document, Selector>::m_net
);
}
template<typename Document, typename Selector>
const typename basic_server_buffer<Document, Selector>::net_type&
basic_server_buffer<Document, Selector>::net6_server() const
{
return dynamic_cast<const net_type&>(
*basic_buffer<Document, Selector>::m_net
);
}
} // namespace obby
#endif // _OBBY_SERVER_BUFFER_HPP_
syntax highlighted by Code2HTML, v. 0.9.1