lyx_mirror/src/ServerSocket.cpp
Enrico Forestieri 482355e7b4 Fix bugs #6871 and #8119.
Both bugs above were due to a missing screen update. This patch
updates the current view after dispatching a lyxserver command
and thus solves both.

The patch is quite strightforward and the only difficulty was due
to the fact that the lyxserver needs the result of the dispatched
command. Now, GuiApplication::dispatch(FuncRequest const &)
does update the view, but does not return any result, while
GuiApplication::dispatch(FuncRequest const &, DispatchResult &),
which is also called by the former, does not update the view.
So, I split the first one, isolating the code performing the update,
such that the second one can also update the current view when
the caller is the lyx server. When the action is initiated by
anything different from the lyx server, the behavior is unchanged.

(cherry picked from commit ea31541848)
2012-04-16 00:19:22 +02:00

289 lines
7.1 KiB
C++

/**
* \file ServerSocket.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Lars Gullik Bjønnes
* \author Jean-Marc Lasgouttes
* \author Angus Leeming
* \author John Levon
* \author João Luis M. Assirati
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "ServerSocket.h"
#include "DispatchResult.h"
#include "FuncRequest.h"
#include "LyX.h"
#include "LyXAction.h"
#include "frontends/Application.h"
#include "support/debug.h"
#include "support/environment.h"
#include "support/FileName.h"
#include "support/socktools.h"
#include "support/bind.h"
#include <cerrno>
#include <ostream>
#if defined (_WIN32)
# include <io.h>
#endif
using namespace std;
using namespace lyx::support;
namespace lyx {
// Address is the unix address for the socket.
// MAX_CLIENTS is the maximum number of clients
// that can connect at the same time.
ServerSocket::ServerSocket(FileName const & addr)
: fd_(socktools::listen(addr, 3)),
address_(addr)
{
if (fd_ == -1) {
LYXERR(Debug::LYXSERVER, "lyx: Disabling LyX socket.");
return;
}
// These env vars are used by DVI inverse search
// Needed by xdvi
setEnv("XEDITOR", "lyxclient -g %f %l");
// Needed by lyxclient
setEnv("LYXSOCKET", address_.absFileName());
theApp()->registerSocketCallback(
fd_,
bind(&ServerSocket::serverCallback, this)
);
LYXERR(Debug::LYXSERVER, "lyx: New server socket "
<< fd_ << ' ' << address_.absFileName());
}
// Close the socket and remove the address of the filesystem.
ServerSocket::~ServerSocket()
{
if (fd_ != -1) {
BOOST_ASSERT (theApp());
theApp()->unregisterSocketCallback(fd_);
if (::close(fd_) != 0)
lyxerr << "lyx: Server socket " << fd_
<< " IO error on closing: " << strerror(errno)
<< endl;
}
address_.removeFile();
LYXERR(Debug::LYXSERVER, "lyx: Server socket quitting");
}
string const ServerSocket::address() const
{
return address_.absFileName();
}
// Creates a new LyXDataSocket and checks to see if the connection
// is OK and if the number of clients does not exceed MAX_CLIENTS
void ServerSocket::serverCallback()
{
int const client_fd = socktools::accept(fd_);
if (fd_ == -1) {
LYXERR(Debug::LYXSERVER, "lyx: Failed to accept new client");
return;
}
if (clients.size() >= MAX_CLIENTS) {
writeln("BYE:Too many clients connected");
return;
}
// Register the new client.
clients[client_fd] =
shared_ptr<LyXDataSocket>(new LyXDataSocket(client_fd));
theApp()->registerSocketCallback(
client_fd,
bind(&ServerSocket::dataCallback,
this, client_fd)
);
}
// Reads and processes input from client and check
// if the connection has been closed
void ServerSocket::dataCallback(int fd)
{
shared_ptr<LyXDataSocket> client = clients[fd];
string line;
size_t pos;
bool saidbye = false;
while (!saidbye && client->readln(line)) {
// The protocol must be programmed here
// Split the key and the data
if ((pos = line.find(':')) == string::npos) {
client->writeln("ERROR:" + line + ":malformed message");
continue;
}
string const key = line.substr(0, pos);
if (key == "LYXCMD") {
string const cmd = line.substr(pos + 1);
FuncRequest fr(lyxaction.lookupFunc(cmd));
fr.setOrigin(FuncRequest::LYXSERVER);
DispatchResult dr;
theApp()->dispatch(fr, dr);
string const rval = to_utf8(dr.message());
if (dr.error())
client->writeln("ERROR:" + cmd + ':' + rval);
else
client->writeln("INFO:" + cmd + ':' + rval);
} else if (key == "HELLO") {
// no use for client name!
client->writeln("HELLO:");
} else if (key == "BYE") {
saidbye = true;
} else {
client->writeln("ERROR:unknown key " + key);
}
}
if (saidbye || !client->connected()) {
clients.erase(fd);
}
}
void ServerSocket::writeln(string const & line)
{
string const linen = line + '\n';
int const size = linen.size();
int const written = ::write(fd_, linen.c_str(), size);
if (written < size) { // Always mean end of connection.
if (written == -1 && errno == EPIPE) {
// The program will also receive a SIGPIPE
// that must be caught
lyxerr << "lyx: Server socket " << fd_
<< " connection closed while writing." << endl;
} else {
// Anything else, including errno == EAGAIN, must be
// considered IO error. EAGAIN should never happen
// when line is small
lyxerr << "lyx: Server socket " << fd_
<< " IO error: " << strerror(errno);
}
}
}
// Debug
// void ServerSocket::dump() const
// {
// lyxerr << "ServerSocket debug dump.\n"
// << "fd = " << fd_ << ", address = " << address_.absFileName() << ".\n"
// << "Clients: " << clients.size() << ".\n";
// map<int, shared_ptr<LyXDataSocket> >::const_iterator client = clients.begin();
// map<int, shared_ptr<LyXDataSocket> >::const_iterator end = clients.end();
// for (; client != end; ++client)
// lyxerr << "fd = " << client->first << '\n';
// }
LyXDataSocket::LyXDataSocket(int fd)
: fd_(fd), connected_(true)
{
LYXERR(Debug::LYXSERVER, "lyx: New data socket " << fd_);
}
LyXDataSocket::~LyXDataSocket()
{
if (::close(fd_) != 0)
lyxerr << "lyx: Data socket " << fd_
<< " IO error on closing: " << strerror(errno);
theApp()->unregisterSocketCallback(fd_);
LYXERR(Debug::LYXSERVER, "lyx: Data socket " << fd_ << " quitting.");
}
bool LyXDataSocket::connected() const
{
return connected_;
}
// Returns true if there was a complete line to input
bool LyXDataSocket::readln(string & line)
{
int const charbuf_size = 100;
char charbuf[charbuf_size]; // buffer for the ::read() system call
int count;
// read and store characters in buffer
while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
buffer_.append(charbuf, charbuf + count);
}
// Error conditions. The buffer must still be
// processed for lines read
if (count == 0) { // EOF -- connection closed
LYXERR(Debug::LYXSERVER, "lyx: Data socket " << fd_
<< ": connection closed.");
connected_ = false;
} else if ((count == -1) && (errno != EAGAIN)) { // IO error
lyxerr << "lyx: Data socket " << fd_
<< ": IO error." << endl;
connected_ = false;
}
// Cut a line from buffer
size_t pos = buffer_.find('\n');
if (pos == string::npos) {
LYXERR(Debug::LYXSERVER, "lyx: Data socket " << fd_
<< ": line not completed.");
return false; // No complete line stored
}
line = buffer_.substr(0, pos);
buffer_.erase(0, pos + 1);
return true;
}
// Write a line of the form <key>:<value> to the socket
void LyXDataSocket::writeln(string const & line)
{
string const linen = line + '\n';
int const size = linen.size();
int const written = ::write(fd_, linen.c_str(), size);
if (written < size) { // Always mean end of connection.
if (written == -1 && errno == EPIPE) {
// The program will also receive a SIGPIPE
// that must be catched
lyxerr << "lyx: Data socket " << fd_
<< " connection closed while writing." << endl;
} else {
// Anything else, including errno == EAGAIN, must be
// considered IO error. EAGAIN should never happen
// when line is small
lyxerr << "lyx: Data socket " << fd_
<< " IO error: " << strerror(errno);
}
connected_ = false;
}
}
} // namespace lyx