]> git.lyx.org Git - lyx.git/blob - src/Server.cpp
adjust
[lyx.git] / src / Server.cpp
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.
5  *
6  * \author Lars Gullik Bjønnes
7  * \author Jean-Marc Lasgouttes
8  * \author Angus Leeming
9  * \author John Levon
10  *
11  * Full author contact details are available in file CREDITS.
12  */
13
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.
35
36            See development/server_monitor.c for an example client.
37   Purpose: implement a client/server lib for LyX
38 */
39
40 #include <config.h>
41
42 #include "Server.h"
43 #include "debug.h"
44 #include "FuncRequest.h"
45 #include "LyXAction.h"
46 #include "LyXFunc.h"
47 #include "frontends/Application.h"
48
49 #include "support/FileName.h"
50 #include "support/lstrings.h"
51 #include "support/lyxlib.h"
52
53 #include <boost/bind.hpp>
54
55 #include <cerrno>
56 #ifdef HAVE_SYS_STAT_H
57 # include <sys/stat.h>
58 #endif
59 #include <fcntl.h>
60
61
62 namespace lyx {
63
64 using support::compare;
65 using support::FileName;
66 using support::rtrim;
67 using support::split;
68 using support::unlink;
69
70 using std::endl;
71 using std::string;
72
73
74 /////////////////////////////////////////////////////////////////////
75 //
76 // LyXComm
77 //
78 /////////////////////////////////////////////////////////////////////
79
80 #if !defined (HAVE_MKFIFO)
81 // We provide a stub class that disables the lyxserver.
82
83 LyXComm::LyXComm(std::string const &, Server *, ClientCallbackfct)
84 {}
85
86 void LyXComm::openConnection()
87 {}
88
89
90 void LyXComm::closeConnection()
91 {}
92
93
94 int LyXComm::startPipe(string const & filename, bool write)
95 {
96         return -1;
97 }
98
99
100 void LyXComm::endPipe(int & fd, string const & filename, bool write)
101 {}
102
103
104 void LyXComm::emergencyCleanup()
105 {}
106
107 void LyXComm::read_ready()
108 {}
109
110
111 void LyXComm::send(string const & msg)
112 {}
113
114
115 #else // defined (HAVE_MKFIFO)
116
117
118 LyXComm::LyXComm(std::string const & pip, Server * cli, ClientCallbackfct ccb)
119         : pipename_(pip), client_(cli), clientcb_(ccb)
120 {
121         ready_ = false;
122         openConnection();
123 }
124
125
126 void LyXComm::openConnection()
127 {
128         LYXERR(Debug::LYXSERVER) << "LyXComm: Opening connection" << endl;
129
130         // If we are up, that's an error
131         if (ready_) {
132                 lyxerr << "LyXComm: Already connected" << endl;
133                 return;
134         }
135         // We assume that we don't make it
136         ready_ = false;
137
138         if (pipename_.empty()) {
139                 LYXERR(Debug::LYXSERVER)
140                         << "LyXComm: server is disabled, nothing to do"
141                         << endl;
142                 return;
143         }
144
145         infd_ = startPipe(inPipeName(), false);
146         if (infd_ == -1)
147                 return;
148
149         outfd_ = startPipe(outPipeName(), true);
150         if (outfd_ == -1) {
151                 endPipe(infd_, inPipeName(), false);
152                 return;
153         }
154
155         if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
156                 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
157                        << '\n' << strerror(errno) << endl;
158                 return;
159         }
160
161         // We made it!
162         ready_ = true;
163         LYXERR(Debug::LYXSERVER) << "LyXComm: Connection established" << endl;
164 }
165
166
167 /// Close pipes
168 void LyXComm::closeConnection()
169 {
170         LYXERR(Debug::LYXSERVER) << "LyXComm: Closing connection" << endl;
171
172         if (pipename_.empty()) {
173                 LYXERR(Debug::LYXSERVER)
174                         << "LyXComm: server is disabled, nothing to do"
175                         << endl;
176                 return;
177         }
178
179         if (!ready_) {
180                 lyxerr << "LyXComm: Already disconnected" << endl;
181                 return;
182         }
183
184         endPipe(infd_, inPipeName(), false);
185         endPipe(outfd_, outPipeName(), true);
186
187         ready_ = false;
188 }
189
190
191 int LyXComm::startPipe(string const & file, bool write)
192 {
193         FileName const filename(file);
194         if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
195                 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
196                        << "If no other LyX program is active, please delete"
197                         " the pipe by hand and try again." << endl;
198                 pipename_.erase();
199                 return -1;
200         }
201
202         if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
203                 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
204                        << strerror(errno) << endl;
205                 return -1;
206         }
207         int const fd = ::open(filename.toFilesystemEncoding().c_str(),
208                               write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
209
210         if (fd < 0) {
211                 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
212                        << strerror(errno) << endl;
213                 unlink(filename);
214                 return -1;
215         }
216
217         if (!write) {
218                 theApp()->registerSocketCallback(fd,
219                         boost::bind(&LyXComm::read_ready, this));
220         }
221
222         return fd;
223 }
224
225
226 void LyXComm::endPipe(int & fd, string const & filename, bool write)
227 {
228         if (fd < 0)
229                 return;
230
231         if (!write)
232                 theApp()->unregisterSocketCallback(fd);
233
234         if (::close(fd) < 0) {
235                 lyxerr << "LyXComm: Could not close pipe " << filename
236                        << '\n' << strerror(errno) << endl;
237         }
238
239         if (unlink(FileName(filename)) < 0) {
240                 lyxerr << "LyXComm: Could not remove pipe " << filename
241                        << '\n' << strerror(errno) << endl;
242         }
243
244         fd = -1;
245 }
246
247
248 void LyXComm::emergencyCleanup()
249 {
250         if (!pipename_.empty()) {
251                 endPipe(infd_, inPipeName(), false);
252                 endPipe(outfd_, outPipeName(), true);
253         }
254 }
255
256
257 // Receives messages and sends then to client
258 void LyXComm::read_ready()
259 {
260         // FIXME: make read_buffer_ a class-member for multiple sessions
261         static string read_buffer_;
262         read_buffer_.erase();
263
264         int const charbuf_size = 100;
265         char charbuf[charbuf_size];
266
267         errno = 0;
268         int status;
269         // the single = is intended here.
270         while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
271
272                 if (status > 0) {
273                         charbuf[status] = '\0'; // turn it into a c string
274                         read_buffer_ += rtrim(charbuf, "\r");
275                         // commit any commands read
276                         while (read_buffer_.find('\n') != string::npos) {
277                                 // split() grabs the entire string if
278                                 // the delim /wasn't/ found. ?:-P
279                                 string cmd;
280                                 read_buffer_= split(read_buffer_, cmd,'\n');
281                                 LYXERR(Debug::LYXSERVER)
282                                         << "LyXComm: status:" << status
283                                         << ", read_buffer_:" << read_buffer_
284                                         << ", cmd:" << cmd << endl;
285                                 if (!cmd.empty())
286                                         clientcb_(client_, cmd);
287                                         //\n or not \n?
288                         }
289                 }
290                 if (errno == EAGAIN) {
291                         errno = 0;
292                         return;
293                 }
294                 if (errno != 0) {
295                         lyxerr << "LyXComm: " << strerror(errno) << endl;
296                         if (!read_buffer_.empty()) {
297                                 lyxerr << "LyXComm: truncated command: "
298                                        << read_buffer_ << endl;
299                                 read_buffer_.erase();
300                         }
301                         break; // reset connection
302                 }
303         }
304
305         // The connection gets reset in errno != EAGAIN
306         // Why does it need to be reset if errno == 0?
307         closeConnection();
308         openConnection();
309         errno = 0;
310 }
311
312
313 void LyXComm::send(string const & msg)
314 {
315         if (msg.empty()) {
316                 lyxerr << "LyXComm: Request to send empty string. Ignoring."
317                        << endl;
318                 return;
319         }
320
321         LYXERR(Debug::LYXSERVER) << "LyXComm: Sending '" << msg << '\'' << endl;
322
323         if (pipename_.empty()) return;
324
325         if (!ready_) {
326                 lyxerr << "LyXComm: Pipes are closed. Could not send "
327                        << msg << endl;
328         } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
329                 lyxerr << "LyXComm: Error sending message: " << msg
330                        << '\n' << strerror(errno)
331                        << "\nLyXComm: Resetting connection" << endl;
332                 closeConnection();
333                 openConnection();
334         }
335 }
336
337 #endif // defined (HAVE_MKFIFO)
338
339
340 string const LyXComm::inPipeName() const
341 {
342         return pipename_ + ".in";
343 }
344
345
346 string const LyXComm::outPipeName() const
347 {
348         return pipename_ + ".out";
349 }
350
351
352 /////////////////////////////////////////////////////////////////////
353 //
354 // Server
355 //
356 /////////////////////////////////////////////////////////////////////
357
358 void ServerCallback(Server * server, string const & msg)
359 {
360         server->callback(msg);
361 }
362
363 Server::Server(LyXFunc * f, std::string const & pipes)
364         : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
365 {}
366
367
368 Server::~Server()
369 {
370         // say goodbye to clients so they stop sending messages
371         // send as many bye messages as there are clients,
372         // each with client's name.
373         string message;
374         for (int i = 0; i != numclients_; ++i) {
375                 message = "LYXSRV:" + clients_[i] + ":bye\n";
376                 pipes_.send(message);
377         }
378 }
379
380
381 // Handle data gotten from communication, called by LyXComm
382 void Server::callback(string const & msg)
383 {
384         LYXERR(Debug::LYXSERVER) << "Server: Received: '"
385                                  << msg << '\'' << endl;
386
387         char const * p = msg.c_str();
388
389         // --- parse the string --------------------------------------------
390         //
391         //  Format: LYXCMD:<client>:<func>:<argstring>\n
392         //
393         bool server_only = false;
394         while (*p) {
395                 // --- 1. check 'header' ---
396
397                 if (compare(p, "LYXSRV:", 7) == 0) {
398                         server_only = true;
399                 } else if (0 != compare(p, "LYXCMD:", 7)) {
400                         lyxerr << "Server: Unknown request \""
401                                << p << '"' << endl;
402                         return;
403                 }
404                 p += 7;
405
406                 // --- 2. for the moment ignore the client name ---
407                 string client;
408                 while (*p && *p != ':')
409                         client += char(*p++);
410                 if (*p == ':')
411                         ++p;
412                 if (!*p)
413                         return;
414
415                 // --- 3. get function name ---
416                 string cmd;
417                 while (*p && *p != ':')
418                         cmd += char(*p++);
419
420                 // --- 4. parse the argument ---
421                 string arg;
422                 if (!server_only && *p == ':' && *(++p)) {
423                         while (*p && *p != '\n')
424                                 arg += char(*p++);
425                         if (*p) ++p;
426                 }
427
428                 LYXERR(Debug::LYXSERVER)
429                         << "Server: Client: '" << client
430                         << "' Command: '" << cmd
431                         << "' Argument: '" << arg << '\'' << endl;
432
433                 // --- lookup and exec the command ------------------
434
435                 if (server_only) {
436                         string buf;
437                         // return the greeting to inform the client that
438                         // we are listening.
439                         if (cmd == "hello") {
440                                 // One more client
441                                 if (numclients_ == MAX_CLIENTS) { //paranoid check
442                                         LYXERR(Debug::LYXSERVER)
443                                                 << "Server: too many clients..."
444                                                 << endl;
445                                         return;
446                                 }
447                                 int i = 0;
448                                 while (!clients_[i].empty() && i < numclients_)
449                                         ++i;
450                                 clients_[i] = client;
451                                 ++numclients_;
452                                 buf = "LYXSRV:" + client + ":hello\n";
453                                 LYXERR(Debug::LYXSERVER)
454                                         << "Server: Greeting "
455                                         << client << endl;
456                                 pipes_.send(buf);
457                         } else if (cmd == "bye") {
458                                 // If clients_ == 0 maybe we should reset the pipes
459                                 // to prevent fake callbacks
460                                 int i = 0; //look if client is registered
461                                 for (; i < numclients_; ++i) {
462                                         if (clients_[i] == client)
463                                                 break;
464                                 }
465                                 if (i < numclients_) {
466                                         --numclients_;
467                                         clients_[i].erase();
468                                         LYXERR(Debug::LYXSERVER)
469                                                 << "Server: Client "
470                                                 << client << " said goodbye"
471                                                 << endl;
472                                 } else {
473                                         LYXERR(Debug::LYXSERVER)
474                                                 << "Server: ignoring bye messge from unregistered client"
475                                                 << client << endl;
476                                 }
477                         } else {
478                                 lyxerr <<"Server: Undefined server command "
479                                        << cmd << '.' << endl;
480                         }
481                         return;
482                 }
483
484                 if (!cmd.empty()) {
485                         // which lyxfunc should we let it connect to?
486                         // The correct solution would be to have a
487                         // specialized (non-gui) BufferView. But how do
488                         // we do it now? Probably we should just let it
489                         // connect to the lyxfunc in the single LyXView we
490                         // support currently. (Lgb)
491
492                         func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
493                         string const rval = to_utf8(func_->getMessage());
494
495                         // all commands produce an INFO or ERROR message
496                         // in the output pipe, even if they do not return
497                         // anything. See chapter 4 of Customization doc.
498                         string buf;
499                         if (func_->errorStat())
500                                 buf = "ERROR:";
501                         else
502                                 buf = "INFO:";
503                         buf += client + ':' + cmd + ':' +  rval + '\n';
504                         pipes_.send(buf);
505
506                         // !!! we don't do any error checking -
507                         //  if the client won't listen, the
508                         //  message is lost and others too
509                         //  maybe; so the client should empty
510                         //  the outpipe before issuing a request.
511
512                         // not found
513                 }
514         }  // while *p
515 }
516
517
518 // Send a notify messge to a client, called by WorkAreaKeyPress
519 void Server::notifyClient(string const & s)
520 {
521         pipes_.send("NOTIFY:" + s + "\n");
522 }
523
524
525 } // namespace lyx