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