]> git.lyx.org Git - lyx.git/blob - src/Server.cpp
Move Dialog.{h,cpp} and ButtonPolicy.{h,cpp} to frontends/qt4/
[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");
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, "LyXComm: server is disabled, nothing to do");
140                 return;
141         }
142
143         infd_ = startPipe(inPipeName(), false);
144         if (infd_ == -1)
145                 return;
146
147         outfd_ = startPipe(outPipeName(), true);
148         if (outfd_ == -1) {
149                 endPipe(infd_, inPipeName(), false);
150                 return;
151         }
152
153         if (fcntl(outfd_, F_SETFL, O_NONBLOCK) < 0) {
154                 lyxerr << "LyXComm: Could not set flags on pipe " << outPipeName()
155                        << '\n' << strerror(errno) << endl;
156                 return;
157         }
158
159         // We made it!
160         ready_ = true;
161         LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
162 }
163
164
165 /// Close pipes
166 void LyXComm::closeConnection()
167 {
168         LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
169
170         if (pipename_.empty()) {
171                 LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
172                 return;
173         }
174
175         if (!ready_) {
176                 lyxerr << "LyXComm: Already disconnected" << endl;
177                 return;
178         }
179
180         endPipe(infd_, inPipeName(), false);
181         endPipe(outfd_, outPipeName(), true);
182
183         ready_ = false;
184 }
185
186
187 int LyXComm::startPipe(string const & file, bool write)
188 {
189         FileName const filename(file);
190         if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
191                 lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
192                        << "If no other LyX program is active, please delete"
193                         " the pipe by hand and try again." << endl;
194                 pipename_.erase();
195                 return -1;
196         }
197
198         if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
199                 lyxerr << "LyXComm: Could not create pipe " << filename << '\n'
200                        << strerror(errno) << endl;
201                 return -1;
202         }
203         int const fd = ::open(filename.toFilesystemEncoding().c_str(),
204                               write ? (O_RDWR) : (O_RDONLY|O_NONBLOCK));
205
206         if (fd < 0) {
207                 lyxerr << "LyXComm: Could not open pipe " << filename << '\n'
208                        << strerror(errno) << endl;
209                 unlink(filename);
210                 return -1;
211         }
212
213         if (!write) {
214                 theApp()->registerSocketCallback(fd,
215                         boost::bind(&LyXComm::read_ready, this));
216         }
217
218         return fd;
219 }
220
221
222 void LyXComm::endPipe(int & fd, string const & filename, bool write)
223 {
224         if (fd < 0)
225                 return;
226
227         if (!write)
228                 theApp()->unregisterSocketCallback(fd);
229
230         if (::close(fd) < 0) {
231                 lyxerr << "LyXComm: Could not close pipe " << filename
232                        << '\n' << strerror(errno) << endl;
233         }
234
235         if (unlink(FileName(filename)) < 0) {
236                 lyxerr << "LyXComm: Could not remove pipe " << filename
237                        << '\n' << strerror(errno) << endl;
238         }
239
240         fd = -1;
241 }
242
243
244 void LyXComm::emergencyCleanup()
245 {
246         if (!pipename_.empty()) {
247                 endPipe(infd_, inPipeName(), false);
248                 endPipe(outfd_, outPipeName(), true);
249         }
250 }
251
252
253 // Receives messages and sends then to client
254 void LyXComm::read_ready()
255 {
256         // FIXME: make read_buffer_ a class-member for multiple sessions
257         static string read_buffer_;
258         read_buffer_.erase();
259
260         int const charbuf_size = 100;
261         char charbuf[charbuf_size];
262
263         errno = 0;
264         int status;
265         // the single = is intended here.
266         while ((status = ::read(infd_, charbuf, charbuf_size - 1))) {
267
268                 if (status > 0) {
269                         charbuf[status] = '\0'; // turn it into a c string
270                         read_buffer_ += rtrim(charbuf, "\r");
271                         // commit any commands read
272                         while (read_buffer_.find('\n') != string::npos) {
273                                 // split() grabs the entire string if
274                                 // the delim /wasn't/ found. ?:-P
275                                 string cmd;
276                                 read_buffer_= split(read_buffer_, cmd,'\n');
277                                 LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
278                                         << ", read_buffer_:" << read_buffer_
279                                         << ", cmd:" << cmd);
280                                 if (!cmd.empty())
281                                         clientcb_(client_, cmd);
282                                         //\n or not \n?
283                         }
284                 }
285                 if (errno == EAGAIN) {
286                         errno = 0;
287                         return;
288                 }
289                 if (errno != 0) {
290                         lyxerr << "LyXComm: " << strerror(errno) << endl;
291                         if (!read_buffer_.empty()) {
292                                 lyxerr << "LyXComm: truncated command: "
293                                        << read_buffer_ << endl;
294                                 read_buffer_.erase();
295                         }
296                         break; // reset connection
297                 }
298         }
299
300         // The connection gets reset in errno != EAGAIN
301         // Why does it need to be reset if errno == 0?
302         closeConnection();
303         openConnection();
304         errno = 0;
305 }
306
307
308 void LyXComm::send(string const & msg)
309 {
310         if (msg.empty()) {
311                 lyxerr << "LyXComm: Request to send empty string. Ignoring."
312                        << endl;
313                 return;
314         }
315
316         LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
317
318         if (pipename_.empty()) return;
319
320         if (!ready_) {
321                 lyxerr << "LyXComm: Pipes are closed. Could not send "
322                        << msg << endl;
323         } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
324                 lyxerr << "LyXComm: Error sending message: " << msg
325                        << '\n' << strerror(errno)
326                        << "\nLyXComm: Resetting connection" << endl;
327                 closeConnection();
328                 openConnection();
329         }
330 }
331
332 #endif // defined (HAVE_MKFIFO)
333
334
335 string const LyXComm::inPipeName() const
336 {
337         return pipename_ + ".in";
338 }
339
340
341 string const LyXComm::outPipeName() const
342 {
343         return pipename_ + ".out";
344 }
345
346
347 /////////////////////////////////////////////////////////////////////
348 //
349 // Server
350 //
351 /////////////////////////////////////////////////////////////////////
352
353 void ServerCallback(Server * server, string const & msg)
354 {
355         server->callback(msg);
356 }
357
358 Server::Server(LyXFunc * f, std::string const & pipes)
359         : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
360 {}
361
362
363 Server::~Server()
364 {
365         // say goodbye to clients so they stop sending messages
366         // send as many bye messages as there are clients,
367         // each with client's name.
368         string message;
369         for (int i = 0; i != numclients_; ++i) {
370                 message = "LYXSRV:" + clients_[i] + ":bye\n";
371                 pipes_.send(message);
372         }
373 }
374
375
376 // Handle data gotten from communication, called by LyXComm
377 void Server::callback(string const & msg)
378 {
379         LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
380
381         char const * p = msg.c_str();
382
383         // --- parse the string --------------------------------------------
384         //
385         //  Format: LYXCMD:<client>:<func>:<argstring>\n
386         //
387         bool server_only = false;
388         while (*p) {
389                 // --- 1. check 'header' ---
390
391                 if (compare(p, "LYXSRV:", 7) == 0) {
392                         server_only = true;
393                 } else if (0 != compare(p, "LYXCMD:", 7)) {
394                         lyxerr << "Server: Unknown request \""
395                                << p << '"' << endl;
396                         return;
397                 }
398                 p += 7;
399
400                 // --- 2. for the moment ignore the client name ---
401                 string client;
402                 while (*p && *p != ':')
403                         client += char(*p++);
404                 if (*p == ':')
405                         ++p;
406                 if (!*p)
407                         return;
408
409                 // --- 3. get function name ---
410                 string cmd;
411                 while (*p && *p != ':')
412                         cmd += char(*p++);
413
414                 // --- 4. parse the argument ---
415                 string arg;
416                 if (!server_only && *p == ':' && *(++p)) {
417                         while (*p && *p != '\n')
418                                 arg += char(*p++);
419                         if (*p) ++p;
420                 }
421
422                 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
423                         << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
424
425                 // --- lookup and exec the command ------------------
426
427                 if (server_only) {
428                         string buf;
429                         // return the greeting to inform the client that
430                         // we are listening.
431                         if (cmd == "hello") {
432                                 // One more client
433                                 if (numclients_ == MAX_CLIENTS) { //paranoid check
434                                         LYXERR(Debug::LYXSERVER, "Server: too many clients...");
435                                         return;
436                                 }
437                                 int i = 0;
438                                 while (!clients_[i].empty() && i < numclients_)
439                                         ++i;
440                                 clients_[i] = client;
441                                 ++numclients_;
442                                 buf = "LYXSRV:" + client + ":hello\n";
443                                 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
444                                 pipes_.send(buf);
445                         } else if (cmd == "bye") {
446                                 // If clients_ == 0 maybe we should reset the pipes
447                                 // to prevent fake callbacks
448                                 int i = 0; //look if client is registered
449                                 for (; i < numclients_; ++i) {
450                                         if (clients_[i] == client)
451                                                 break;
452                                 }
453                                 if (i < numclients_) {
454                                         --numclients_;
455                                         clients_[i].erase();
456                                         LYXERR(Debug::LYXSERVER, "Server: Client "
457                                                 << client << " said goodbye");
458                                 } else {
459                                         LYXERR(Debug::LYXSERVER,
460                                                 "Server: ignoring bye messge from unregistered client" << client);
461                                 }
462                         } else {
463                                 lyxerr <<"Server: Undefined server command "
464                                        << cmd << '.' << endl;
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