Update NEWS preparing for alpha1
[lyx.git] / src / Server.cpp
blob14c1e202dd7eb09fe275338e04e72272c2b308c1
1 /**
2 * \file Server.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Lars Gullik Bjønnes
7 * \author Jean-Marc Lasgouttes
8 * \author Angus Leeming
9 * \author John Levon
11 * Full author contact details are available in file CREDITS.
14 /**
15 Docu : To use the lyxserver define the name of the pipe in your
16 lyxrc:
17 \serverpipe "/home/myhome/.lyxpipe"
18 Then use .lyxpipe.in and .lyxpipe.out to communicate to LyX.
19 Each message consists of a single line in ASCII. Input lines
20 (client -> LyX) have the following format:
21 "LYXCMD:<clientname>:<functionname>:<argument>"
22 Answers from LyX look like this:
23 "INFO:<clientname>:<functionname>:<data>"
24 [asierra970531] Or like this in case of error:
25 "ERROR:<clientname>:<functionname>:<error message>"
26 where <clientname> and <functionname> are just echoed.
27 If LyX notifies about a user defined extension key-sequence,
28 the line looks like this:
29 "NOTIFY:<key-sequence>"
30 [asierra970531] New server-only messages to implement a simple protocol
31 "LYXSRV:<clientname>:<protocol message>"
32 where <protocol message> can be "hello" or "bye". If hello is
33 received LyX will inform the client that it's listening its
34 messages, and 'bye' will inform that lyx is closing.
36 See development/server_monitor.c for an example client.
37 Purpose: implement a client/server lib for LyX
40 #include <config.h>
42 #include "Server.h"
43 #include "FuncRequest.h"
44 #include "LyXAction.h"
45 #include "LyXFunc.h"
47 #include "frontends/Application.h"
49 #include "support/debug.h"
50 #include "support/FileName.h"
51 #include "support/lstrings.h"
53 #include <boost/bind.hpp>
55 #include <cerrno>
56 #ifdef HAVE_SYS_STAT_H
57 # include <sys/stat.h>
58 #endif
59 #include <fcntl.h>
61 using namespace std;
62 using namespace lyx::support;
64 namespace lyx {
66 /////////////////////////////////////////////////////////////////////
68 // LyXComm
70 /////////////////////////////////////////////////////////////////////
72 #if !defined (HAVE_MKFIFO)
73 // We provide a stub class that disables the lyxserver.
75 LyXComm::LyXComm(string const &, Server *, ClientCallbackfct)
78 void LyXComm::openConnection()
82 void LyXComm::closeConnection()
86 int LyXComm::startPipe(string const & filename, bool write)
88 return -1;
92 void LyXComm::endPipe(int & fd, string const & filename, bool write)
96 void LyXComm::emergencyCleanup()
99 void LyXComm::read_ready()
103 void LyXComm::send(string const & msg)
107 #else // defined (HAVE_MKFIFO)
110 LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
111 : pipename_(pip), client_(cli), clientcb_(ccb)
113 ready_ = false;
114 openConnection();
118 void LyXComm::openConnection()
120 LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
122 // If we are up, that's an error
123 if (ready_) {
124 lyxerr << "LyXComm: Already connected" << endl;
125 return;
127 // We assume that we don't make it
128 ready_ = false;
130 if (pipename_.empty()) {
131 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
132 return;
135 infd_ = startPipe(inPipeName(), false);
136 if (infd_ == -1)
137 return;
139 outfd_ = startPipe(outPipeName(), true);
140 if (outfd_ == -1) {
141 endPipe(infd_, inPipeName(), false);
142 return;
145 if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
146 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
147 << '\n' << strerror(errno) << endl;
148 return;
151 // We made it!
152 ready_ = true;
153 LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
157 /// Close pipes
158 void LyXComm::closeConnection()
160 LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
162 if (pipename_.empty()) {
163 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
164 return;
167 if (!ready_) {
168 LYXERR0("LyXComm: Already disconnected");
169 return;
172 endPipe(infd_, inPipeName(), false);
173 endPipe(outfd_, outPipeName(), true);
175 ready_ = false;
179 int LyXComm::startPipe(string const & file, bool write)
181 FileName const filename(file);
182 if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
183 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
184 << "If no other LyX program is active, please delete"
185 " the pipe by hand and try again." << endl;
186 pipename_.erase();
187 return -1;
190 if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
191 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
192 << strerror(errno) << endl;
193 return -1;
195 int const fd = ::open(filename.toFilesystemEncoding().c_str(),
196 write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
198 if (fd < 0) {
199 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
200 << strerror(errno) << endl;
201 filename.removeFile();
202 return -1;
205 if (!write) {
206 theApp()->registerSocketCallback(fd,
207 boost::bind(&LyXComm::read_ready, this));
210 return fd;
214 void LyXComm::endPipe(int & fd, string const & filename, bool write)
216 if (fd < 0)
217 return;
219 if (!write)
220 theApp()->unregisterSocketCallback(fd);
222 if (::close(fd) < 0) {
223 lyxerr << "LyXComm: Could not close pipe " << filename
224 << '\n' << strerror(errno) << endl;
227 if (FileName(filename).removeFile() < 0) {
228 lyxerr << "LyXComm: Could not remove pipe " << filename
229 << '\n' << strerror(errno) << endl;
232 fd = -1;
236 void LyXComm::emergencyCleanup()
238 if (!pipename_.empty()) {
239 endPipe(infd_, inPipeName(), false);
240 endPipe(outfd_, outPipeName(), true);
245 // Receives messages and sends then to client
246 void LyXComm::read_ready()
248 // FIXME: make read_buffer_ a class-member for multiple sessions
249 static string read_buffer_;
250 read_buffer_.erase();
252 int const charbuf_size = 100;
253 char charbuf[charbuf_size];
255 errno = 0;
256 int status;
257 // the single = is intended here.
258 while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
260 if (status > 0) {
261 charbuf[status] = '\0'; // turn it into a c string
262 read_buffer_ += rtrim(charbuf, "\r");
263 // commit any commands read
264 while (read_buffer_.find('\n') != string::npos) {
265 // split() grabs the entire string if
266 // the delim /wasn't/ found. ?:-P
267 string cmd;
268 read_buffer_= split(read_buffer_, cmd,'\n');
269 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
270 << ", read_buffer_:" << read_buffer_
271 << ", cmd:" << cmd);
272 if (!cmd.empty())
273 clientcb_(client_, cmd);
274 //\n or not \n?
277 if (errno == EAGAIN) {
278 errno = 0;
279 return;
281 if (errno != 0) {
282 LYXERR0("LyXComm: " << strerror(errno));
283 if (!read_buffer_.empty()) {
284 LYXERR0("LyXComm: truncated command: " << read_buffer_);
285 read_buffer_.erase();
287 break; // reset connection
291 // The connection gets reset in errno != EAGAIN
292 // Why does it need to be reset if errno == 0?
293 closeConnection();
294 openConnection();
295 errno = 0;
299 void LyXComm::send(string const & msg)
301 if (msg.empty()) {
302 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
303 return;
306 LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
308 if (pipename_.empty()) return;
310 if (!ready_) {
311 LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
312 } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
313 lyxerr << "LyXComm: Error sending message: " << msg
314 << '\n' << strerror(errno)
315 << "\nLyXComm: Resetting connection" << endl;
316 closeConnection();
317 openConnection();
321 #endif // defined (HAVE_MKFIFO)
324 string const LyXComm::inPipeName() const
326 return pipename_ + ".in";
330 string const LyXComm::outPipeName() const
332 return pipename_ + ".out";
336 /////////////////////////////////////////////////////////////////////
338 // Server
340 /////////////////////////////////////////////////////////////////////
342 void ServerCallback(Server * server, string const & msg)
344 server->callback(msg);
347 Server::Server(LyXFunc * f, string const & pipes)
348 : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
352 Server::~Server()
354 // say goodbye to clients so they stop sending messages
355 // send as many bye messages as there are clients,
356 // each with client's name.
357 string message;
358 for (int i = 0; i != numclients_; ++i) {
359 message = "LYXSRV:" + clients_[i] + ":bye\n";
360 pipes_.send(message);
365 int compare(char const * a, char const * b, unsigned int len)
367 using namespace std;
368 return strncmp(a, b, len);
372 // Handle data gotten from communication, called by LyXComm
373 void Server::callback(string const & msg)
375 LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
377 char const * p = msg.c_str();
379 // --- parse the string --------------------------------------------
381 // Format: LYXCMD:<client>:<func>:<argstring>\n
383 bool server_only = false;
384 while (*p) {
385 // --- 1. check 'header' ---
387 if (compare(p, "LYXSRV:", 7) == 0) {
388 server_only = true;
389 } else if (0 != compare(p, "LYXCMD:", 7)) {
390 lyxerr << "Server: Unknown request \""
391 << p << '"' << endl;
392 return;
394 p += 7;
396 // --- 2. for the moment ignore the client name ---
397 string client;
398 while (*p && *p != ':')
399 client += char(*p++);
400 if (*p == ':')
401 ++p;
402 if (!*p)
403 return;
405 // --- 3. get function name ---
406 string cmd;
407 while (*p && *p != ':')
408 cmd += char(*p++);
410 // --- 4. parse the argument ---
411 string arg;
412 if (!server_only && *p == ':' && *(++p)) {
413 while (*p && *p != '\n')
414 arg += char(*p++);
415 if (*p) ++p;
418 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
419 << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
421 // --- lookup and exec the command ------------------
423 if (server_only) {
424 string buf;
425 // return the greeting to inform the client that
426 // we are listening.
427 if (cmd == "hello") {
428 // One more client
429 if (numclients_ == MAX_CLIENTS) { //paranoid check
430 LYXERR(Debug::LYXSERVER, "Server: too many clients...");
431 return;
433 int i = 0;
434 while (!clients_[i].empty() && i < numclients_)
435 ++i;
436 clients_[i] = client;
437 ++numclients_;
438 buf = "LYXSRV:" + client + ":hello\n";
439 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
440 pipes_.send(buf);
441 } else if (cmd == "bye") {
442 // If clients_ == 0 maybe we should reset the pipes
443 // to prevent fake callbacks
444 int i = 0; //look if client is registered
445 for (; i < numclients_; ++i) {
446 if (clients_[i] == client)
447 break;
449 if (i < numclients_) {
450 --numclients_;
451 clients_[i].erase();
452 LYXERR(Debug::LYXSERVER, "Server: Client "
453 << client << " said goodbye");
454 } else {
455 LYXERR(Debug::LYXSERVER,
456 "Server: ignoring bye messge from unregistered client" << client);
458 } else {
459 LYXERR0("Server: Undefined server command " << cmd << '.');
461 return;
464 if (!cmd.empty()) {
465 // which lyxfunc should we let it connect to?
466 // The correct solution would be to have a
467 // specialized (non-gui) BufferView. But how do
468 // we do it now? Probably we should just let it
469 // connect to the lyxfunc in the single LyXView we
470 // support currently. (Lgb)
472 func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
473 string const rval = to_utf8(func_->getMessage());
475 // all commands produce an INFO or ERROR message
476 // in the output pipe, even if they do not return
477 // anything. See chapter 4 of Customization doc.
478 string buf;
479 if (func_->errorStat())
480 buf = "ERROR:";
481 else
482 buf = "INFO:";
483 buf += client + ':' + cmd + ':' + rval + '\n';
484 pipes_.send(buf);
486 // !!! we don't do any error checking -
487 // if the client won't listen, the
488 // message is lost and others too
489 // maybe; so the client should empty
490 // the outpipe before issuing a request.
492 // not found
494 } // while *p
498 // Send a notify messge to a client, called by WorkAreaKeyPress
499 void Server::notifyClient(string const & s)
501 pipes_.send("NOTIFY:" + s + "\n");
505 } // namespace lyx