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