}
///////////////////////////////////////////////////////////////////////////
+// senf::console::detail::DumbClientReader
+
+prefix_ senf::console::detail::DumbClientReader::DumbClientReader(Client & client)
+ : ClientReader(client), promptLen_ (0), promptActive_ (false)
+{
+ showPrompt();
+ ReadHelper<ClientHandle>::dispatch( handle(), 16384u, ReadUntil("\n"),
+ senf::membind(&DumbClientReader::clientData, this) );
+}
+
+prefix_ void
+senf::console::detail::DumbClientReader::clientData(senf::ReadHelper<ClientHandle>::ptr helper)
+{
+ if (helper->error() || handle().eof()) {
+ // THIS COMMITS SUICIDE. THE INSTANCE IS GONE AFTER stopClient RETURNS
+ stopClient();
+ return;
+ }
+
+ promptLen_ = 0;
+ promptActive_ = false;
+
+ std::string data (tail_ + helper->data());
+ tail_ = helper->tail();
+ boost::trim(data); // Gets rid of superfluous \r or \n characters
+ handleInput(data);
+
+ showPrompt();
+ ReadHelper<ClientHandle>::dispatch( handle(), 16384u, ReadUntil("\n"),
+ senf::membind(&DumbClientReader::clientData, this) );
+
+}
+
+prefix_ void senf::console::detail::DumbClientReader::showPrompt()
+{
+ std::string prompt (promptString());
+
+ stream() << prompt << std::flush;
+ promptLen_ = prompt.size();
+ promptActive_ = true;
+}
+
+prefix_ void senf::console::detail::DumbClientReader::v_disablePrompt()
+{
+ if (promptActive_ && promptLen_ > 0) {
+ stream() << '\r' << std::string(' ', promptLen_) << '\r';
+ promptLen_ = 0;
+ }
+}
+
+prefix_ void senf::console::detail::DumbClientReader::v_enablePrompt()
+{
+ if (promptActive_ && ! promptLen_)
+ showPrompt();
+}
+
+///////////////////////////////////////////////////////////////////////////
// senf::console::Client
prefix_ senf::console::Client::Client(Server & server, ClientHandle handle,
std::string const & name)
: out_t(handle), senf::log::IOStreamTarget(out_t::member), server_ (server),
- handle_ (handle), name_ (name), promptLen_(0)
+ handle_ (handle), name_ (name), reader_ (0)
{
- showPrompt();
- ReadHelper<ClientHandle>::dispatch( handle_, 16384u, ReadUntil("\n"),
- senf::membind(&Client::clientData, this) );
+ reader_.reset( new detail::DumbClientReader (*this) );
route< senf::SenfLog, senf::log::NOTICE >();
}
prefix_ senf::console::Client::~Client()
{}
-prefix_ void senf::console::Client::stopClient()
+prefix_ void senf::console::Client::stop()
{
// THIS COMMITS SUICIDE. THE INSTANCE IS GONE AFTER removeClient RETURNS
server_.removeClient(*this);
}
-prefix_ void senf::console::Client::clientData(ReadHelper<ClientHandle>::ptr helper)
+prefix_ void senf::console::Client::handleInput(std::string data)
{
- promptLen_ = 0;
- if (helper->error() || handle_.eof()) {
- // THIS COMMITS SUICIDE. THE INSTANCE IS GONE AFTER stopClient RETURNS
- stopClient();
- return;
- }
-
- std::string data (tail_ + helper->data());
- tail_ = helper->tail();
- boost::trim(data); // Gets rid of superfluous \r or \n characters
-
if (data.empty())
data = lastCommand_;
else
try {
if (! parser_.parse(data, boost::bind<void>( boost::ref(executor_),
- boost::ref(out_t::member),
+ boost::ref(stream()),
_1 )) )
- out_t::member << "syntax error" << std::endl;
+ stream() << "syntax error" << std::endl;
}
catch (Executor::ExitException &) {
- // THIS COMMITS SUICIDE. THE INSTANCE IS GONE AFTER stopClient RETURNS
- stopClient();
+ // This generates an EOF condition on the Handle. This EOF condition is expected
+ // to be handled gracefully by the ClientReader. We cannot call stop() here, since we
+ // are called from the client reader callback and that will continue executing even if we
+ // call stop here ...
+ handle_.facet<senf::TCPSocketProtocol>().shutdown(senf::TCPSocketProtocol::ShutRD);
return;
}
catch (std::exception & ex) {
- out_t::member << ex.what() << std::endl;
+ stream() << ex.what() << std::endl;
}
catch (...) {
- out_t::member << "unidentified error (unknown exception thrown)" << std::endl;
+ stream() << "unidentified error (unknown exception thrown)" << std::endl;
}
-
- showPrompt();
- ReadHelper<ClientHandle>::dispatch( handle_, 16384u, ReadUntil("\n"),
- senf::membind(&Client::clientData, this) );
-}
-
-prefix_ void senf::console::Client::showPrompt()
-{
- std::string path (executor_.cwd().path());
- out_t::member << name_ << ":" << path << "# " << std::flush;
- promptLen_ = name_.size() + 1 + path.size() + 1;
}
prefix_ void senf::console::Client::v_write(boost::posix_time::ptime timestamp,
std::string const & area, unsigned level,
std::string const & message)
{
- if (promptLen_)
- out_t::member << '\r' << std::string(' ', promptLen_) << '\r';
+ reader_->disablePrompt();
IOStreamTarget::v_write(timestamp, stream, area, level, message);
- if (promptLen_)
- showPrompt();
+ reader_->enablePrompt();
}
///////////////////////////////cc.e////////////////////////////////////////
name_ = name;
}
+///////////////////////////////////////////////////////////////////////////
+// senf::console::detail::ClientReader
+
+prefix_ senf::console::detail::ClientReader::~ClientReader()
+{}
+
+prefix_ senf::console::Client & senf::console::detail::ClientReader::client()
+ const
+{
+ return client_;
+}
+
+prefix_ std::string senf::console::detail::ClientReader::promptString()
+ const
+{
+ return client().promptString();
+}
+
+prefix_ senf::console::detail::ClientReader::ClientHandle senf::console::detail::ClientReader::handle()
+ const
+{
+ return client().handle();
+}
+
+prefix_ senf::console::detail::ClientReader::OutputStream & senf::console::detail::ClientReader::stream()
+ const
+{
+ return client().stream();
+}
+
+prefix_ void senf::console::detail::ClientReader::stopClient()
+{
+ client().stop();
+}
+
+prefix_ void senf::console::detail::ClientReader::handleInput(std::string const & input)
+ const
+{
+ client().handleInput(input);
+}
+
+prefix_ void senf::console::detail::ClientReader::disablePrompt()
+{
+ v_disablePrompt();
+}
+
+prefix_ void senf::console::detail::ClientReader::enablePrompt()
+{
+ v_enablePrompt();
+}
+
+prefix_ senf::console::detail::ClientReader::ClientReader(Client & client)
+ : client_ (client)
+{}
+
+///////////////////////////////////////////////////////////////////////////
+// senf::console::Client
+
+prefix_ std::string senf::console::Client::promptString()
+ const
+{
+ return name_ + ":" + executor_.cwd().path() + "$ ";
+}
+
+prefix_ senf::console::Client::ClientHandle senf::console::Client::handle()
+ const
+{
+ return handle_;
+}
+
+prefix_ senf::console::detail::NonblockingSocketOStream & senf::console::Client::stream()
+{
+ return out_t::member;
+}
+
///////////////////////////////cci.e///////////////////////////////////////
#undef prefix_
public:
///////////////////////////////////////////////////////////////////////////
// Types
-
- typedef senf::ServerSocketHandle<
- senf::MakeSocketPolicy< senf::TCPv4SocketProtocol::Policy,
- senf::UnspecifiedAddressingPolicy>::policy > ServerHandle;
+
+ typedef detail::ServerHandle ServerHandle;
~Server();
SENF_LOG_CLASS_AREA();
SENF_LOG_DEFAULT_LEVEL( senf::log::NOTICE );
+
public:
typedef Server::ServerHandle::ClientSocketHandle ClientHandle;
~Client();
- void stopClient(); ///< Stop the client
+ void stop(); ///< Stop the client
/**< This will close the client socket. */
protected:
private:
Client(Server & server, ClientHandle handle, std::string const & name);
- void clientData(ReadHelper<ClientHandle>::ptr helper);
- void showPrompt();
+ std::string promptString() const;
+ ClientHandle handle() const;
+ detail::NonblockingSocketOStream & stream();
+
+ void handleInput(std::string input);
virtual void v_write(boost::posix_time::ptime timestamp, std::string const & stream,
std::string const & area, unsigned level,
Server & server_;
ClientHandle handle_;
- std::string tail_;
CommandParser parser_;
Executor executor_;
std::string name_;
- unsigned promptLen_;
std::string lastCommand_;
+ boost::scoped_ptr<detail::ClientReader> reader_;
friend class Server;
+ friend class detail::ClientReader;
};
}}
namespace senf {
namespace console {
+
+ class Server;
+ class Client;
+
namespace detail {
/** \brief Internal: Nonblocking boost::iostreams::sink
typedef boost::iostreams::stream<NonblockingSocketSink> NonblockingSocketOStream;
+ typedef senf::ServerSocketHandle<
+ senf::MakeSocketPolicy< senf::TCPv4SocketProtocol::Policy,
+ senf::UnspecifiedAddressingPolicy>::policy > ServerHandle;
+
+ /** \brief Internal: Generic client interface
+
+ The ClientReader encapsulates the interaction of a single network client with the user: It
+ manages prompt display and reading an interactive command.
+ */
+ class ClientReader
+ {
+ public:
+ typedef ServerHandle::ClientSocketHandle ClientHandle;
+ typedef NonblockingSocketOStream OutputStream;
+
+ virtual ~ClientReader() = 0;
+
+ Client & client() const;
+ std::string promptString() const;
+ ClientHandle handle() const;
+ OutputStream & stream() const;
+ void stopClient();
+
+ void handleInput(std::string const & input) const;
+
+ void disablePrompt();
+ void enablePrompt();
+
+ protected:
+ ClientReader(Client & client);
+
+ private:
+ virtual void v_disablePrompt() = 0;
+ virtual void v_enablePrompt() = 0;
+
+ Client & client_;
+ };
+
+ /** \brief Internal: Primitive ClientReader implementation
+
+ This implementation uses the cooked telnet mode to read lines from the console. It does not
+ support explicit line editing or any other advanced features.
+ */
+ class DumbClientReader
+ : public ClientReader
+ {
+ public:
+ DumbClientReader(Client & client);
+
+ private:
+ virtual void v_disablePrompt();
+ virtual void v_enablePrompt();
+
+ void clientData(senf::ReadHelper<ClientHandle>::ptr helper);
+ void showPrompt();
+
+ std::string tail_;
+ unsigned promptLen_;
+ bool promptActive_;
+ };
+
}}}
///////////////////////////////ih.e////////////////////////////////////////