]> git.lyx.org Git - lyx.git/blob - src/Server.cpp
Cosmetics.
[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 "FuncRequest.h"
44 #include "LyXAction.h"
45 #include "LyXFunc.h"
46
47 #include "frontends/Application.h"
48
49 #include "support/debug.h"
50 #include "support/FileName.h"
51 #include "support/lstrings.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 using namespace std;
62 using namespace lyx::support;
63
64 namespace lyx {
65
66 /////////////////////////////////////////////////////////////////////
67 //
68 // LyXComm
69 //
70 /////////////////////////////////////////////////////////////////////
71
72 #if !defined (HAVE_MKFIFO)
73 // We provide a stub class that disables the lyxserver.
74
75 LyXComm::LyXComm(string const &, Server *, ClientCallbackfct)
76 {}
77
78 void LyXComm::openConnection()
79 {}
80
81
82 void LyXComm::closeConnection()
83 {}
84
85
86 int LyXComm::startPipe(string const & filename, bool write)
87 {
88         return -1;
89 }
90
91
92 void LyXComm::endPipe(int & fd, string const & filename, bool write)
93 {}
94
95
96 void LyXComm::emergencyCleanup()
97 {}
98
99 void LyXComm::read_ready()
100 {}
101
102
103 void LyXComm::send(string const & msg)
104 {}
105
106
107 #else // defined (HAVE_MKFIFO)
108
109
110 LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
111         : pipename_(pip), client_(cli), clientcb_(ccb)
112 {
113         ready_ = false;
114         openConnection();
115 }
116
117
118 void LyXComm::openConnection()
119 {
120         LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
121
122         // If we are up, that's an error
123         if (ready_) {
124                 lyxerr << "LyXComm: Already connected" << endl;
125                 return;
126         }
127         // We assume that we don't make it
128         ready_ = false;
129
130         if (pipename_.empty()) {
131                 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
132                 return;
133         }
134
135         infd_ = startPipe(inPipeName(), false);
136         if (infd_ == -1)
137                 return;
138
139         outfd_ = startPipe(outPipeName(), true);
140         if (outfd_ == -1) {
141                 endPipe(infd_, inPipeName(), false);
142                 return;
143         }
144
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;
149         }
150
151         // We made it!
152         ready_ = true;
153         LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
154 }
155
156
157 /// Close pipes
158 void LyXComm::closeConnection()
159 {
160         LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
161
162         if (pipename_.empty()) {
163                 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
164                 return;
165         }
166
167         if (!ready_) {
168                 LYXERR0("LyXComm: Already disconnected");
169                 return;
170         }
171
172         endPipe(infd_, inPipeName(), false);
173         endPipe(outfd_, outPipeName(), true);
174
175         ready_ = false;
176 }
177
178
179 int LyXComm::startPipe(string const & file, bool write)
180 {
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;
188         }
189
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;
194         }
195         int const fd = ::open(filename.toFilesystemEncoding().c_str(),
196                               write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
197
198         if (fd < 0) {
199                 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
200                        << strerror(errno) << endl;
201                 filename.removeFile();
202                 return -1;
203         }
204
205         if (!write) {
206                 theApp()->registerSocketCallback(fd,
207                         boost::bind(&LyXComm::read_ready, this));
208         }
209
210         return fd;
211 }
212
213
214 void LyXComm::endPipe(int & fd, string const & filename, bool write)
215 {
216         if (fd < 0)
217                 return;
218
219         if (!write)
220                 theApp()->unregisterSocketCallback(fd);
221
222         if (::close(fd) < 0) {
223                 lyxerr << "LyXComm: Could not close pipe " << filename
224                        << '\n' << strerror(errno) << endl;
225         }
226
227         if (FileName(filename).removeFile() < 0) {
228                 lyxerr << "LyXComm: Could not remove pipe " << filename
229                        << '\n' << strerror(errno) << endl;
230         }
231
232         fd = -1;
233 }
234
235
236 void LyXComm::emergencyCleanup()
237 {
238         if (!pipename_.empty()) {
239                 endPipe(infd_, inPipeName(), false);
240                 endPipe(outfd_, outPipeName(), true);
241         }
242 }
243
244
245 // Receives messages and sends then to client
246 void LyXComm::read_ready()
247 {
248         // FIXME: make read_buffer_ a class-member for multiple sessions
249         static string read_buffer_;
250         read_buffer_.erase();
251
252         int const charbuf_size = 100;
253         char charbuf[charbuf_size];
254
255         errno = 0;
256         int status;
257         // the single = is intended here.
258         while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
259
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?
275                         }
276                 }
277                 if (errno == EAGAIN) {
278                         errno = 0;
279                         return;
280                 }
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();
286                         }
287                         break; // reset connection
288                 }
289         }
290
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;
296 }
297
298
299 void LyXComm::send(string const & msg)
300 {
301         if (msg.empty()) {
302                 LYXERR0("LyXComm: Request to send empty string. Ignoring.");
303                 return;
304         }
305
306         LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
307
308         if (pipename_.empty()) return;
309
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();
318         }
319 }
320
321 #endif // defined (HAVE_MKFIFO)
322
323
324 string const LyXComm::inPipeName() const
325 {
326         return pipename_ + ".in";
327 }
328
329
330 string const LyXComm::outPipeName() const
331 {
332         return pipename_ + ".out";
333 }
334
335
336 /////////////////////////////////////////////////////////////////////
337 //
338 // Server
339 //
340 /////////////////////////////////////////////////////////////////////
341
342 void ServerCallback(Server * server, string const & msg)
343 {
344         server->callback(msg);
345 }
346
347 Server::Server(LyXFunc * f, string const & pipes)
348         : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
349 {}
350
351
352 Server::~Server()
353 {
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);
361         }
362 }
363
364
365 int compare(char const * a, char const * b, unsigned int len)
366 {
367         using namespace std;
368         return strncmp(a, b, len);
369 }
370
371
372 // Handle data gotten from communication, called by LyXComm
373 void Server::callback(string const & msg)
374 {
375         LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
376
377         char const * p = msg.c_str();
378
379         // --- parse the string --------------------------------------------
380         //
381         //  Format: LYXCMD:<client>:<func>:<argstring>\n
382         //
383         bool server_only = false;
384         while (*p) {
385                 // --- 1. check 'header' ---
386
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;
393                 }
394                 p += 7;
395
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;
404
405                 // --- 3. get function name ---
406                 string cmd;
407                 while (*p && *p != ':')
408                         cmd += char(*p++);
409
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;
416                 }
417
418                 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
419                         << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
420
421                 // --- lookup and exec the command ------------------
422
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;
432                                 }
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;
448                                 }
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);
457                                 }
458                         } else {
459                                 LYXERR0("Server: Undefined server command " << cmd << '.');
460                         }
461                         return;
462                 }
463
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)
471
472                         func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
473                         string const rval = to_utf8(func_->getMessage());
474
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);
485
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.
491
492                         // not found
493                 }
494         }  // while *p
495 }
496
497
498 // Send a notify messge to a client, called by WorkAreaKeyPress
499 void Server::notifyClient(string const & s)
500 {
501         pipes_.send("NOTIFY:" + s + "\n");
502 }
503
504
505 } // namespace lyx