]> git.lyx.org Git - lyx.git/blob - src/Server.cpp
I'll find a solution for the 'dirList problem', Abdel.
[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                 lyxerr << "LyXComm: Already disconnected" << endl;
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                         lyxerr << "LyXComm: " << strerror(errno) << endl;
290                         if (!read_buffer_.empty()) {
291                                 lyxerr << "LyXComm: truncated command: "
292                                        << read_buffer_ << endl;
293                                 read_buffer_.erase();
294                         }
295                         break; // reset connection
296                 }
297         }
298
299         // The connection gets reset in errno != EAGAIN
300         // Why does it need to be reset if errno == 0?
301         closeConnection();
302         openConnection();
303         errno = 0;
304 }
305
306
307 void LyXComm::send(string const & msg)
308 {
309         if (msg.empty()) {
310                 lyxerr << "LyXComm: Request to send empty string. Ignoring."
311                        << endl;
312                 return;
313         }
314
315         LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
316
317         if (pipename_.empty()) return;
318
319         if (!ready_) {
320                 lyxerr << "LyXComm: Pipes are closed. Could not send "
321                        << msg << endl;
322         } else if (::write(outfd_, msg.c_str(), msg.length()) < 0) {
323                 lyxerr << "LyXComm: Error sending message: " << msg
324                        << '\n' << strerror(errno)
325                        << "\nLyXComm: Resetting connection" << endl;
326                 closeConnection();
327                 openConnection();
328         }
329 }
330
331 #endif // defined (HAVE_MKFIFO)
332
333
334 string const LyXComm::inPipeName() const
335 {
336         return pipename_ + ".in";
337 }
338
339
340 string const LyXComm::outPipeName() const
341 {
342         return pipename_ + ".out";
343 }
344
345
346 /////////////////////////////////////////////////////////////////////
347 //
348 // Server
349 //
350 /////////////////////////////////////////////////////////////////////
351
352 void ServerCallback(Server * server, string const & msg)
353 {
354         server->callback(msg);
355 }
356
357 Server::Server(LyXFunc * f, std::string const & pipes)
358         : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
359 {}
360
361
362 Server::~Server()
363 {
364         // say goodbye to clients so they stop sending messages
365         // send as many bye messages as there are clients,
366         // each with client's name.
367         string message;
368         for (int i = 0; i != numclients_; ++i) {
369                 message = "LYXSRV:" + clients_[i] + ":bye\n";
370                 pipes_.send(message);
371         }
372 }
373
374
375 // Handle data gotten from communication, called by LyXComm
376 void Server::callback(string const & msg)
377 {
378         LYXERR(Debug::LYXSERVER, "Server: Received: '" << msg << '\'');
379
380         char const * p = msg.c_str();
381
382         // --- parse the string --------------------------------------------
383         //
384         //  Format: LYXCMD:<client>:<func>:<argstring>\n
385         //
386         bool server_only = false;
387         while (*p) {
388                 // --- 1. check 'header' ---
389
390                 if (compare(p, "LYXSRV:", 7) == 0) {
391                         server_only = true;
392                 } else if (0 != compare(p, "LYXCMD:", 7)) {
393                         lyxerr << "Server: Unknown request \""
394                                << p << '"' << endl;
395                         return;
396                 }
397                 p += 7;
398
399                 // --- 2. for the moment ignore the client name ---
400                 string client;
401                 while (*p && *p != ':')
402                         client += char(*p++);
403                 if (*p == ':')
404                         ++p;
405                 if (!*p)
406                         return;
407
408                 // --- 3. get function name ---
409                 string cmd;
410                 while (*p && *p != ':')
411                         cmd += char(*p++);
412
413                 // --- 4. parse the argument ---
414                 string arg;
415                 if (!server_only && *p == ':' && *(++p)) {
416                         while (*p && *p != '\n')
417                                 arg += char(*p++);
418                         if (*p) ++p;
419                 }
420
421                 LYXERR(Debug::LYXSERVER, "Server: Client: '" << client
422                         << "' Command: '" << cmd << "' Argument: '" << arg << '\'');
423
424                 // --- lookup and exec the command ------------------
425
426                 if (server_only) {
427                         string buf;
428                         // return the greeting to inform the client that
429                         // we are listening.
430                         if (cmd == "hello") {
431                                 // One more client
432                                 if (numclients_ == MAX_CLIENTS) { //paranoid check
433                                         LYXERR(Debug::LYXSERVER, "Server: too many clients...");
434                                         return;
435                                 }
436                                 int i = 0;
437                                 while (!clients_[i].empty() && i < numclients_)
438                                         ++i;
439                                 clients_[i] = client;
440                                 ++numclients_;
441                                 buf = "LYXSRV:" + client + ":hello\n";
442                                 LYXERR(Debug::LYXSERVER, "Server: Greeting " << client);
443                                 pipes_.send(buf);
444                         } else if (cmd == "bye") {
445                                 // If clients_ == 0 maybe we should reset the pipes
446                                 // to prevent fake callbacks
447                                 int i = 0; //look if client is registered
448                                 for (; i < numclients_; ++i) {
449                                         if (clients_[i] == client)
450                                                 break;
451                                 }
452                                 if (i < numclients_) {
453                                         --numclients_;
454                                         clients_[i].erase();
455                                         LYXERR(Debug::LYXSERVER, "Server: Client "
456                                                 << client << " said goodbye");
457                                 } else {
458                                         LYXERR(Debug::LYXSERVER,
459                                                 "Server: ignoring bye messge from unregistered client" << client);
460                                 }
461                         } else {
462                                 lyxerr <<"Server: Undefined server command "
463                                        << cmd << '.' << endl;
464                         }
465                         return;
466                 }
467
468                 if (!cmd.empty()) {
469                         // which lyxfunc should we let it connect to?
470                         // The correct solution would be to have a
471                         // specialized (non-gui) BufferView. But how do
472                         // we do it now? Probably we should just let it
473                         // connect to the lyxfunc in the single LyXView we
474                         // support currently. (Lgb)
475
476                         func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg));
477                         string const rval = to_utf8(func_->getMessage());
478
479                         // all commands produce an INFO or ERROR message
480                         // in the output pipe, even if they do not return
481                         // anything. See chapter 4 of Customization doc.
482                         string buf;
483                         if (func_->errorStat())
484                                 buf = "ERROR:";
485                         else
486                                 buf = "INFO:";
487                         buf += client + ':' + cmd + ':' +  rval + '\n';
488                         pipes_.send(buf);
489
490                         // !!! we don't do any error checking -
491                         //  if the client won't listen, the
492                         //  message is lost and others too
493                         //  maybe; so the client should empty
494                         //  the outpipe before issuing a request.
495
496                         // not found
497                 }
498         }  // while *p
499 }
500
501
502 // Send a notify messge to a client, called by WorkAreaKeyPress
503 void Server::notifyClient(string const & s)
504 {
505         pipes_.send("NOTIFY:" + s + "\n");
506 }
507
508
509 } // namespace lyx