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