/* 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_DOCUMENT_INFO_HPP_
#define _OBBY_SERVER_DOCUMENT_INFO_HPP_
#include <net6/server.hpp>
#include "serialise/object.hpp"
#include "serialise/attribute.hpp"
#include "no_operation.hpp"
#include "split_operation.hpp"
#include "insert_operation.hpp"
#include "delete_operation.hpp"
#include "record.hpp"
#include "jupiter_server.hpp"
#include "document_packet.hpp"
#include "document_info.hpp"
namespace obby
{
template<typename Document, typename Selector>
class basic_server_buffer;
/** Information about a document that is provided without being subscribed to
* a document.
*/
template<typename Document, typename Selector>
class basic_server_document_info:
virtual public basic_document_info<Document, Selector>
{
public:
typedef basic_document_info<Document, Selector> base_type;
typedef typename base_type::document_type document_type;
typedef basic_server_buffer<Document, Selector> buffer_type;
typedef typename buffer_type::net_type net_type;
typedef jupiter_server<Document> jupiter_type;
typedef typename jupiter_type::record_type record_type;
basic_server_document_info(const buffer_type& buffer,
net_type& net,
const user* owner,
unsigned int id,
const std::string& title,
const std::string& encoding,
const std::string& content);
/** Deserialises a document from a serialisation object.
*/
basic_server_document_info(const buffer_type& buffer,
net_type& net,
const serialise::object& obj);
/** Inserts the given text at the given position into the document.
*/
virtual void insert(position pos, const std::string& text);
/** Erases the given range from the document.
*/
virtual void erase(position pos, position len);
/** Renames the given document.
*/
virtual void rename(const std::string& new_title);
/** Subscribes the given user to this document.
*/
void subscribe_user(const user& user);
/** Unsubscribes the given user from this document.
*/
void unsubscribe_user(const user& user);
/** Called by the buffer if a network event occured that belongs to the
* document.
*/
virtual void on_net_packet(const document_packet& pack,
const user& from);
/** 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);
/** @brief Called when the session has been closed.
*/
virtual void obby_session_close();
protected:
/** Internal function that subscribes a user to this document.
*/
virtual void user_subscribe(const user& user);
/** Internal function that unsubscribes a user from this document.
*/
virtual void user_unsubscribe(const user& user);
/** Inserts text written by <em>author</em> into the document.
*/
void insert_impl(position pos,
const std::string& text,
const user* author);
/** Erases text from the document. The operation is performed by
* <em>author</em>.
*/
void erase_impl(position pos,
position len,
const user* author);
/** Renames the document. The operation is performed by
* <em>from</em>.
*/
void rename_impl(const std::string& new_title,
const user* from);
/** Executes a network packet.
*/
bool execute_packet(const document_packet& pack,
const user& from);
/** Rename request.
*/
virtual void on_net_rename(const document_packet& pack,
const user& from);
/** Change in a document.
*/
virtual void on_net_record(const document_packet& pack,
const user& from);
/** Subscribe request.
*/
virtual void on_net_subscribe(const document_packet& pack,
const user& from);
/** Unsubscribe request.
*/
virtual void on_net_unsubscribe(const document_packet& pack,
const obby::user& from);
/** Callback from jupiter implementation with a record
* that may be sent to the given user.
*/
virtual void on_jupiter_record(const record_type& rec, const user& user,
const obby::user* from);
/** @brief Broadcasts a user subscription to the other users.
*/
void broadcast_subscription(const user& user);
/** @brief Broadcasts user unsubscription to the other users.
*/
void broadcast_unsubscription(const user& user);
/** @brief Implementation of the session close callback that does
* not call the base function.
*/
void session_close_impl();
std::auto_ptr<jupiter_type> m_jupiter;
public:
/** Returns the buffer to which this document_info belongs.
*/
const buffer_type& get_buffer() const;
protected:
/** Returns the underlaying net6 object.
*/
net_type& get_net6();
/** Returns the underlaying net6 object.
*/
const net_type& get_net6() const;
};
template<typename Document, typename Selector>
basic_server_document_info<Document, Selector>::
basic_server_document_info(const buffer_type& buffer,
net_type& net,
const user* owner,
unsigned int id,
const std::string& title,
const std::string& encoding,
const std::string& content):
base_type(
buffer,
net,
owner,
id,
title,
encoding
)
{
base_type::assign_document();
base_type::m_document->insert(0, content, NULL);
// Create jupiter server implementation
m_jupiter.reset(new jupiter_type(
*basic_document_info<Document, Selector>::m_document
) );
// Owner is subscribed implicitely
if(owner != NULL)
{
user_subscribe(*owner);
// Send resulting suffix to owner
document_packet pack(*this, "rename");
pack << static_cast<const obby::user*>(NULL)
<< base_type::m_title << base_type::m_suffix;
get_net6().send(pack, owner->get_net6());
}
// Connect to signals
m_jupiter->record_event().connect(
sigc::mem_fun(
*this,
&basic_server_document_info::on_jupiter_record
)
);
}
template<typename Document, typename Selector>
basic_server_document_info<Document, Selector>::
basic_server_document_info(const buffer_type& buffer,
net_type& net,
const serialise::object& obj):
base_type(buffer, net, obj)
{
// TODO: Avoid code duplication somehow
// What code duplication? :( -- armin, Fri Mar 10
// Assign document content
base_type::assign_document();
// Deserialise document
for(serialise::object::child_iterator child_it = obj.children_begin();
child_it != obj.children_end();
++ child_it)
{
if(child_it->get_name() != "chunk")
continue; // TODO: Throw unexpected child error
const serialise::attribute& content_attr =
child_it->get_required_attribute("content");
const serialise::attribute& author_attr =
child_it->get_required_attribute("author");
base_type::m_document->append(
content_attr.obby::serialise::attribute::as<std::string>(),
author_attr.obby::serialise::attribute::as<const user*>(
::serialise::default_context_from<const user*>(
buffer.get_user_table()
)
)
);
}
// Create jupiter server implementation
m_jupiter.reset(new jupiter_type(
*basic_document_info<Document, Selector>::m_document
) );
// Connect to signals
m_jupiter->record_event().connect(
sigc::mem_fun(
*this,
&basic_server_document_info::on_jupiter_record
)
);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
insert(position pos,
const std::string& text)
{
insert_impl(pos, text, NULL);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
erase(position pos,
position len)
{
erase_impl(pos, len, NULL);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
rename(const std::string& new_title)
{
rename_impl(new_title, NULL);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
subscribe_user(const user& user)
{
// Note that the host is able to subscribe the local user even if
// no network connection is available. The host overloads this
// function and basic_local_document_info::subscribe for that purpose
if(base_type::m_net == NULL)
{
throw std::logic_error(
"obby::basic_server_document_info::subscribe_user:\n"
"Cannot subscribe user without having a network object "
);
}
// TODO: Check userflags for connected?
// Subscribe given user
user_subscribe(user);
// Synchronise initial document to user
document_type& doc = *base_type::m_document;
document_packet init_pack(*this, "sync_init");
init_pack << doc.size();
get_net6().send(init_pack, user.get_net6() );
// Send content
for(typename document_type::chunk_iterator iter = doc.chunk_begin();
iter != doc.chunk_end();
++ iter)
{
// TODO: Do not send all at once.
document_packet chunk_pack(*this, "sync_chunk");
chunk_pack << iter.get_text() << iter.get_author();
get_net6().send(chunk_pack, user.get_net6() );
}
// Broadcast subscription
broadcast_subscription(user);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
unsubscribe_user(const user& user)
{
if(base_type::m_net == NULL)
{
throw std::logic_error(
"obby::basic_server_document_info::unsubscribe_user:\n"
"Cannot subscribe user without having a network object "
);
}
// Unsubscribe user
user_unsubscribe(user);
// Broadcast unsubscription
broadcast_unsubscription(user);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
on_net_packet(const document_packet& pack,
const user& from)
{
if(!execute_packet(pack, from) )
{
throw net6::bad_value(
"Unexpected command: " + pack.get_command()
);
}
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
obby_user_join(const user& user)
{
basic_document_info<Document, Selector>::obby_user_join(user);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
obby_user_part(const user& user)
{
basic_document_info<Document, Selector>::obby_user_part(user);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::obby_session_close()
{
session_close_impl();
basic_document_info<Document, Selector>::session_close_impl();
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
user_subscribe(const user& user)
{
// Add client to jupiter
m_jupiter->client_add(user);
// Call base function
basic_document_info<Document, Selector>::user_subscribe(user);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
user_unsubscribe(const user& user)
{
// Call base function
basic_document_info<Document, Selector>::user_unsubscribe(user);
// Remove client from jupiter
m_jupiter->client_remove(user);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
insert_impl(position pos,
const std::string& text,
const user* author)
{
if(m_jupiter.get() != NULL)
{
insert_operation<document_type> op(pos, text);
m_jupiter->local_op(op, author);
}
else
{
base_type::m_document->insert(pos, text, author);
}
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
erase_impl(position pos,
position len,
const user* author)
{
if(m_jupiter.get() != NULL)
{
delete_operation<document_type> op(pos, len);
m_jupiter->local_op(op, author);
}
else
{
base_type::m_document->erase(pos, len);
}
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
rename_impl(const std::string& new_title,
const user* from)
{
// Rename document
base_type::document_rename(
new_title,
base_type::m_buffer.find_free_suffix(new_title, this)
);
if(base_type::m_net != NULL)
{
// Forward to clients
document_packet pack(*this, "rename");
pack << from << new_title << base_type::m_suffix;
get_net6().send(pack);
}
}
template<typename Document, typename Selector>
bool basic_server_document_info<Document, Selector>::
execute_packet(const document_packet& pack,
const user& from)
{
// TODO: std::map<>
if(pack.get_command() == "rename")
{ on_net_rename(pack, from); return true; }
if(pack.get_command() == "record")
{ on_net_record(pack, from); return true; }
if(pack.get_command() == "subscribe")
{ on_net_subscribe(pack, from); return true; }
if(pack.get_command() == "unsubscribe")
{ on_net_unsubscribe(pack, from); return true; }
return false;
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
on_net_rename(const document_packet& pack,
const user& from)
{
// TODO: Authentication
const std::string& new_title =
pack.get_param(0).net6::parameter::as<std::string>();
rename_impl(new_title, &from);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
on_net_record(const document_packet& pack,
const user& from)
{
unsigned int index = 2;
record_type rec(
pack,
index,
base_type::m_buffer.get_user_table()
);
m_jupiter->remote_op(rec, &from);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
on_net_subscribe(const document_packet& pack,
const user& from)
{
subscribe_user(from);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
on_net_unsubscribe(const document_packet& pack,
const user& from)
{
unsubscribe_user(from);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
on_jupiter_record(const record_type& rec,
const user& user,
const obby::user* from)
{
document_packet pack(*this, "record");
pack << from;
rec.append_packet(pack);
get_net6().send(pack, user.get_net6() );
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
broadcast_subscription(const user& user)
{
document_packet pack(*this, "subscribe");
pack << &user;
get_net6().send(pack);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::
broadcast_unsubscription(const user& user)
{
document_packet pack(*this, "unsubscribe");
pack << &user;
get_net6().send(pack);
}
template<typename Document, typename Selector>
void basic_server_document_info<Document, Selector>::session_close_impl()
{
m_jupiter.reset(NULL);
}
template<typename Document, typename Selector>
const typename basic_server_document_info<Document, Selector>::buffer_type&
basic_server_document_info<Document, Selector>::get_buffer() const
{
return dynamic_cast<const buffer_type&>(base_type::get_buffer() );
}
template<typename Document, typename Selector>
typename basic_server_document_info<Document, Selector>::net_type&
basic_server_document_info<Document, Selector>::get_net6()
{
return dynamic_cast<net_type&>(base_type::get_net6() );
}
template<typename Document, typename Selector>
const typename basic_server_document_info<Document, Selector>::net_type&
basic_server_document_info<Document, Selector>::get_net6() const
{
return dynamic_cast<const net_type&>(base_type::get_net6() );
}
} // namespace obby
#endif // _OBBY_SERVER_DOCUMENT_INFO_HPP_
syntax highlighted by Code2HTML, v. 0.9.1