lyx_mirror/src/lyxserver.C

539 lines
13 KiB
C++
Raw Normal View History

/**
* \file lyxserver.C
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Lars Gullik Bj<EFBFBD>nnes
* \author Jean-Marc Lasgouttes
* \author Angus Leeming
* \author John Levon
*
* Full author contact details are available in file CREDITS.
*/
/**
Docu : To use the lyxserver define the name of the pipe in your
lyxrc:
\serverpipe "/home/myhome/.lyxpipe"
Then use .lyxpipe.in and .lyxpipe.out to communicate to LyX.
Each message consists of a single line in ASCII. Input lines
(client -> LyX) have the following format:
"LYXCMD:<clientname>:<functionname>:<argument>"
Answers from LyX look like this:
"INFO:<clientname>:<functionname>:<data>"
[asierra970531] Or like this in case of error:
"ERROR:<clientname>:<functionname>:<error message>"
where <clientname> and <functionname> are just echoed.
If LyX notifies about a user defined extension key-sequence,
the line looks like this:
"NOTIFY:<key-sequence>"
[asierra970531] New server-only messages to implement a simple protocol
"LYXSRV:<clientname>:<protocol message>"
where <protocol message> can be "hello" or "bye". If hello is
received LyX will inform the client that it's listening its
messages, and 'bye' will inform that lyx is closing.
See development/server_monitor.c for an example client.
Purpose: implement a client/server lib for LyX
*/
#include <config.h>
#include "lyxserver.h"
#include "debug.h"
#include "funcrequest.h"
#include "LyXAction.h"
#include "lyxfunc.h"
#include "support/lstrings.h"
#include "support/lyxlib.h"
#include "frontends/lyx_gui.h"
#include <boost/bind.hpp>
#include <cerrno>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef __EMX__
#include <cstdlib>
#include <io.h>
#define OS2EMX_PLAIN_CHAR
#define INCL_DOSNMPIPES
#define INCL_DOSERRORS
#include <os2.h>
#include "support/os2_errortable.h"
#endif
using lyx::support::compare;
using lyx::support::rtrim;
using lyx::support::split;
using lyx::support::unlink;
using std::endl;
using std::string;
// provide an empty mkfifo() if we do not have one. This disables the
// lyxserver.
#ifndef HAVE_MKFIFO
int mkfifo(char const * __path, mode_t __mode) {
return 0;
}
#endif
void LyXComm::openConnection()
{
lyxerr[Debug::LYXSERVER] << "LyXComm: Opening connection" << endl;
// If we are up, that's an error
if (ready) {
lyxerr << "LyXComm: Already connected" << endl;
return;
}
// We assume that we don't make it
ready = false;
if (pipename.empty()) {
lyxerr[Debug::LYXSERVER]
<< "LyXComm: server is disabled, nothing to do"
<< endl;
return;
}
if ((infd = startPipe(inPipeName(), false)) == -1)
return;
if ((outfd = startPipe(outPipeName(), true)) == -1) {
endPipe(infd, inPipeName(), false);
return;
}
if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
<< '\n' << strerror(errno) << endl;
return;
}
// We made it!
ready = true;
lyxerr[Debug::LYXSERVER] << "LyXComm: Connection established" << endl;
}
/// Close pipes
void LyXComm::closeConnection()
{
lyxerr[Debug::LYXSERVER] << "LyXComm: Closing connection" << endl;
if (pipename.empty()) {
lyxerr[Debug::LYXSERVER]
<< "LyXComm: server is disabled, nothing to do"
<< endl;
return;
}
if (!ready) {
lyxerr << "LyXComm: Already disconnected" << endl;
return;
}
endPipe(infd, inPipeName(), false);
endPipe(outfd, outPipeName(), true);
ready = false;
}
int LyXComm::startPipe(string const & filename, bool write)
{
#ifdef __EMX__
HPIPE os2fd;
APIRET rc;
int errnum;
// Try create one instance of named pipe with the mode O_RDONLY|O_NONBLOCK.
// The current emx implementation of access() won't work with pipes.
rc = DosCreateNPipe(filename.c_str(), &os2fd, NP_ACCESS_INBOUND,
NP_NOWAIT|0x01, 0600, 0600, 0);
if (rc == ERROR_PIPE_BUSY) {
lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
<< "If no other LyX program is active, please delete"
" the pipe by hand and try again." << endl;
pipename.erase();
return -1;
}
if (rc != NO_ERROR) {
errnum = TranslateOS2Error(rc);
lyxerr <<"LyXComm: Could not create pipe " << filename
<< strerror(errnum) << endl;
return -1;
};
// Listen to it.
rc = DosConnectNPipe(os2fd);
if (rc != NO_ERROR && rc != ERROR_PIPE_NOT_CONNECTED) {
errnum = TranslateOS2Error(rc);
lyxerr <<"LyXComm: Could not create pipe " << filename
<< strerror(errnum) << endl;
return -1;
};
// Imported handles can be used both with OS/2 APIs and emx
// library functions.
int const fd = _imphandle(os2fd);
#else
if (::access(filename.c_str(), F_OK) == 0) {
lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
<< "If no other LyX program is active, please delete"
" the pipe by hand and try again." << endl;
pipename.erase();
return -1;
}
if (::mkfifo(filename.c_str(), 0600) < 0) {
lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
<< strerror(errno) << endl;
return -1;
};
int const fd = ::open(filename.c_str(),
write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
#endif
if (fd < 0) {
lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
<< strerror(errno) << endl;
unlink(filename);
return -1;
}
if (!write) {
lyx_gui::register_socket_callback(fd, boost::bind(&LyXComm::read_ready, this));
}
return fd;
}
void LyXComm::endPipe(int & fd, string const & filename, bool write)
{
if (fd < 0)
return;
if (!write) {
lyx_gui::unregister_socket_callback(fd);
}
#ifdef __EMX__
APIRET rc;
int errnum;
rc = DosDisConnectNPipe(fd);
if (rc != NO_ERROR) {
errnum = TranslateOS2Error(rc);
lyxerr << "LyXComm: Could not disconnect pipe " << filename
<< '\n' << strerror(errnum) << endl;
return;
}
#endif
if (::close(fd) < 0) {
lyxerr << "LyXComm: Could not close pipe " << filename
<< '\n' << strerror(errno) << endl;
}
// OS/2 pipes are deleted automatically
#ifndef __EMX__
if (unlink(filename) < 0) {
lyxerr << "LyXComm: Could not remove pipe " << filename
<< '\n' << strerror(errno) << endl;
};
#endif
fd = -1;
}
void LyXComm::emergencyCleanup()
{
if (!pipename.empty()) {
endPipe(infd, inPipeName(), false);
endPipe(outfd, outPipeName(), true);
}
}
// Receives messages and sends then to client
void LyXComm::read_ready()
{
// nb! make read_buffer_ a class-member for multiple sessions
static string read_buffer_;
read_buffer_.erase();
int const charbuf_size = 100;
char charbuf[charbuf_size];
errno = 0;
int status;
// the single = is intended here.
while ((status = ::read(infd, charbuf, charbuf_size - 1))) {
if (status > 0) {
charbuf[status] = '\0'; // turn it into a c string
read_buffer_ += rtrim(charbuf, "\r");
// commit any commands read
while (read_buffer_.find('\n') != string::npos) {
// split() grabs the entire string if
// the delim /wasn't/ found. ?:-P
string cmd;
read_buffer_= split(read_buffer_, cmd,'\n');
lyxerr[Debug::LYXSERVER]
<< "LyXComm: status:" << status
<< ", read_buffer_:" << read_buffer_
<< ", cmd:" << cmd << endl;
if (!cmd.empty())
clientcb(client, cmd);
//\n or not \n?
}
}
if (errno == EAGAIN) {
errno = 0;
return;
}
if (errno != 0) {
lyxerr << "LyXComm: " << strerror(errno) << endl;
if (!read_buffer_.empty()) {
lyxerr << "LyXComm: truncated command: "
<< read_buffer_ << endl;
read_buffer_.erase();
}
break; // reset connection
}
}
// The connection gets reset in errno != EAGAIN
// Why does it need to be reset if errno == 0?
closeConnection();
openConnection();
errno = 0;
}
void LyXComm::send(string const & msg)
{
if (msg.empty()) {
lyxerr << "LyXComm: Request to send empty string. Ignoring."
<< endl;
return;
}
if (lyxerr.debugging(Debug::LYXSERVER)) {
lyxerr << "LyXComm: Sending '" << msg << '\'' << endl;
}
if (pipename.empty()) return;
if (!ready) {
lyxerr << "LyXComm: Pipes are closed. Could not send "
<< msg << endl;
} else if (::write(outfd, msg.c_str(), msg.length()) < 0) {
lyxerr << "LyXComm: Error sending message: " << msg
<< '\n' << strerror(errno)
<< "\nLyXComm: Resetting connection" << endl;
closeConnection();
openConnection();
}
#ifdef __EMX__
APIRET rc;
int errnum;
rc = DosResetBuffer(outfd); // To avoid synchronization problems.
if (rc != NO_ERROR) {
errnum = TranslateOS2Error(rc);
lyxerr << "LyXComm: Message could not be flushed: " << msg
<< '\n' << strerror(errnum) << endl;
}
#endif
}
string const LyXComm::inPipeName() const
{
return pipename + string(".in");
}
string const LyXComm::outPipeName() const
{
return pipename + string(".out");
}
// LyXServer class
LyXServer::~LyXServer()
{
// say goodbye to clients so they stop sending messages
// modified june 1999 by stefano@zool.su.se to send as many bye
// messages as there are clients, each with client's name.
string message;
for (int i= 0; i<numclients; ++i) {
message = "LYXSRV:" + clients[i] + ":bye\n";
pipes.send(message);
}
}
/* ---F+------------------------------------------------------------------ *\
Function : ServerCallback
Called by : LyXComm
Purpose : handle data gotten from communication
\* ---F------------------------------------------------------------------- */
void LyXServer::callback(LyXServer * serv, string const & msg)
{
lyxerr[Debug::LYXSERVER] << "LyXServer: Received: '"
<< msg << '\'' << endl;
char const * p = msg.c_str();
// --- parse the string --------------------------------------------
//
// Format: LYXCMD:<client>:<func>:<argstring>\n
//
bool server_only = false;
while (*p) {
// --- 1. check 'header' ---
if (compare(p, "LYXSRV:", 7) == 0) {
server_only = true;
} else if (0 != compare(p, "LYXCMD:", 7)) {
lyxerr << "LyXServer: Unknown request \""
<< p << '"' << endl;
return;
}
p += 7;
// --- 2. for the moment ignore the client name ---
string client;
while (*p && *p != ':')
client += char(*p++);
if (*p == ':') ++p;
if (!*p) return;
// --- 3. get function name ---
string cmd;
while (*p && *p != ':')
cmd += char(*p++);
// --- 4. parse the argument ---
string arg;
if (!server_only && *p == ':' && *(++p)) {
while (*p && *p != '\n')
arg += char(*p++);
if (*p) ++p;
}
lyxerr[Debug::LYXSERVER]
<< "LyXServer: Client: '" << client
<< "' Command: '" << cmd
<< "' Argument: '" << arg << '\'' << endl;
// --- lookup and exec the command ------------------
if (server_only) {
string buf;
// return the greeting to inform the client that
// we are listening.
if (cmd == "hello") {
// One more client
if (serv->numclients == MAX_CLIENTS) { //paranoid check
lyxerr[Debug::LYXSERVER]
<< "LyXServer: too many clients..."
<< endl;
return;
}
int i= 0; //find place in clients[]
while (!serv->clients[i].empty()
&& i<serv->numclients)
++i;
serv->clients[i] = client;
serv->numclients++;
buf = "LYXSRV:" + client + ":hello\n";
lyxerr[Debug::LYXSERVER]
<< "LyXServer: Greeting "
<< client << endl;
serv->pipes.send(buf);
} else if (cmd == "bye") {
// If clients == 0 maybe we should reset the pipes
// to prevent fake callbacks
int i = 0; //look if client is registered
for (; i < serv->numclients; ++i) {
if (serv->clients[i] == client) break;
}
if (i < serv->numclients) {
serv->numclients--;
serv->clients[i].erase();
lyxerr[Debug::LYXSERVER]
<< "LyXServer: Client "
<< client << " said goodbye"
<< endl;
} else {
lyxerr[Debug::LYXSERVER]
<< "LyXServer: ignoring bye messge from unregistered client"
<< client << endl;
}
} else {
lyxerr <<"LyXServer: Undefined server command "
<< cmd << '.' << endl;
}
return;
}
if (!cmd.empty()) {
// which lyxfunc should we let it connect to?
// The correct solution would be to have a
// specialized (non-gui) BufferView. But how do
// we do it now? Probably we should just let it
// connect to the lyxfunc in the single LyXView we
// support currently. (Lgb)
serv->func->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
string const rval = serv->func->getMessage();
//modified june 1999 stefano@zool.su.se:
//all commands produce an INFO or ERROR message
//in the output pipe, even if they do not return
//anything. See chapter 4 of Customization doc.
string buf;
if (serv->func->errorStat())
buf = "ERROR:";
else
buf = "INFO:";
buf += client + ':' + cmd + ':' + rval + '\n';
serv->pipes.send(buf);
// !!! we don't do any error checking -
// if the client won't listen, the
// message is lost and others too
// maybe; so the client should empty
// the outpipe before issuing a request.
// not found
}
} /* while *p */
}
/* ---F+------------------------------------------------------------------ *\
Function : LyXNotifyClient
Called by : WorkAreaKeyPress
Purpose : send a notify messge to a client
Parameters: s - string to send
Returns : nothing
\* ---F------------------------------------------------------------------- */
void LyXServer::notifyClient(string const & s)
{
string buf = string("NOTIFY:") + s + "\n";
pipes.send(buf);
}