]> git.lyx.org Git - lyx.git/blob - src/lyxsocket.C
minimal effort implementation of:
[lyx.git] / src / lyxsocket.C
1 /**
2  * \file lyxsocket.C
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  * \author João Luis M. Assirati
11  *
12  * Full author contact details are available in file CREDITS.
13  */
14
15 #include <config.h>
16
17 #include "lyxsocket.h"
18
19 #include "debug.h"
20 #include "funcrequest.h"
21 #include "LyXAction.h"
22 #include "lyxfunc.h"
23
24 #include "frontends/lyx_gui.h"
25
26 #include "support/environment.h"
27 #include "support/lyxlib.h"
28 #include "support/socktools.h"
29
30 #include <boost/bind.hpp>
31
32 #include <cerrno>
33
34 #if defined (_WIN32)
35 # include <io.h>
36 #endif
37
38 using boost::shared_ptr;
39
40 using std::auto_ptr;
41 using std::endl;
42 using std::string;
43
44
45 // Address is the unix address for the socket.
46 // MAX_CLIENTS is the maximum number of clients
47 // that can connect at the same time.
48 LyXServerSocket::LyXServerSocket(LyXFunc * f, string const & addr)
49         : func(f),
50           fd_(lyx::support::socktools::listen(addr, 3)),
51           address_(addr)
52 {
53         if (fd_ == -1) {
54                 lyxerr << "lyx: Disabling LyX socket." << endl;
55                 return;
56         }
57
58         // These env vars are used by DVI inverse search
59         // Needed by xdvi
60         lyx::support::setEnv("XEDITOR", "lyxclient -g %f %l");
61         // Needed by lyxclient
62         lyx::support::setEnv("LYXSOCKET", address_);
63
64         lyx_gui::register_socket_callback(
65                 fd_,
66                 boost::bind(&LyXServerSocket::serverCallback, this)
67                 );
68
69         lyxerr[Debug::LYXSERVER] << "lyx: New server socket "
70                                  << fd_ << ' ' << address_ << endl;
71 }
72
73
74 // Close the socket and remove the address of the filesystem.
75 LyXServerSocket::~LyXServerSocket()
76 {
77         if (fd_ != -1) {
78                 lyx_gui::unregister_socket_callback(fd_);
79                 if (::close(fd_) != 0)
80                         lyxerr << "lyx: Server socket " << fd_
81                                << " IO error on closing: " << strerror(errno);
82         }
83         lyx::support::unlink(address_);
84         lyxerr[Debug::LYXSERVER] << "lyx: Server socket quitting" << endl;
85 }
86
87
88 string const & LyXServerSocket::address() const
89 {
90         return address_;
91 }
92
93
94 // Creates a new LyXDataSocket and checks to see if the connection
95 // is OK and if the number of clients does not exceed MAX_CLIENTS
96 void LyXServerSocket::serverCallback()
97 {
98         int const client_fd = lyx::support::socktools::accept(fd_);
99
100         if (fd_ == -1) {
101                 lyxerr[Debug::LYXSERVER] << "lyx: Failed to accept new client"
102                                          << endl;
103                 return;
104         }
105
106         if (clients.size() >= MAX_CLIENTS) {
107                 writeln("BYE:Too many clients connected");
108                 return;
109         }
110
111         // Register the new client.
112         clients[client_fd] =
113                 shared_ptr<LyXDataSocket>(new LyXDataSocket(client_fd));
114         lyx_gui::register_socket_callback(
115                 client_fd,
116                 boost::bind(&LyXServerSocket::dataCallback,
117                             this, client_fd)
118                 );
119 }
120
121
122 // Reads and processes input from client and check
123 // if the connection has been closed
124 void LyXServerSocket::dataCallback(int fd)
125 {
126         shared_ptr<LyXDataSocket> client = clients[fd];
127
128         string line;
129         string::size_type pos;
130         bool saidbye = false;
131         while ((!saidbye) && client->readln(line)) {
132                 // The protocol must be programmed here
133                 // Split the key and the data
134                 if ((pos = line.find(':')) == string::npos) {
135                         client->writeln("ERROR:" + line + ":malformed message");
136                         continue;
137                 }
138
139                 string const key = line.substr(0, pos);
140                 if (key == "LYXCMD") {
141                         string const cmd = line.substr(pos + 1);
142                         func->dispatch(lyxaction.lookupFunc(cmd));
143                         string const rval = func->getMessage();
144                         if (func->errorStat()) {
145                                 client->writeln("ERROR:" + cmd + ':' + rval);
146                         } else {
147                                 client->writeln("INFO:" + cmd + ':' + rval);
148                         }
149                 } else if (key == "HELLO") {
150                         // no use for client name!
151                         client->writeln("HELLO:");
152                 } else if (key == "BYE") {
153                         saidbye = true;
154                 } else {
155                         client->writeln("ERROR:unknown key " + key);
156                 }
157         }
158
159         if (saidbye || (!client->connected())) {
160                 clients.erase(fd);
161         }
162 }
163
164
165 void LyXServerSocket::writeln(string const & line)
166 {
167         string const linen(line + '\n');
168         int const size = linen.size();
169         int const written = ::write(fd_, linen.c_str(), size);
170         if (written < size) { // Always mean end of connection.
171                 if ((written == -1) && (errno == EPIPE)) {
172                         // The program will also receive a SIGPIPE
173                         // that must be caught
174                         lyxerr << "lyx: Server socket " << fd_
175                                << " connection closed while writing." << endl;
176                 } else {
177                         // Anything else, including errno == EAGAIN, must be
178                         // considered IO error. EAGAIN should never happen
179                         // when line is small
180                         lyxerr << "lyx: Server socket " << fd_
181                              << " IO error: " << strerror(errno);
182                 }
183         }
184 }
185
186 // Debug
187 // void LyXServerSocket::dump() const
188 // {
189 //      lyxerr << "LyXServerSocket debug dump.\n"
190 //           << "fd = " << fd_ << ", address = " << address_ << ".\n"
191 //           << "Clients: " << clients.size() << ".\n";
192 //      std::map<int, shared_ptr<LyXDataSocket> >::const_iterator client = clients.begin();
193 //      std::map<int, shared_ptr<LyXDataSocket> >::const_iterator end = clients.end();
194 //      for (; client != end; ++client)
195 //              lyxerr << "fd = " << client->first << '\n';
196 // }
197
198
199 LyXDataSocket::LyXDataSocket(int fd)
200         : fd_(fd), connected_(true)
201 {
202         lyxerr[Debug::LYXSERVER] << "lyx: New data socket " << fd_ << endl;
203 }
204
205
206 LyXDataSocket::~LyXDataSocket()
207 {
208         if (::close(fd_) != 0)
209                 lyxerr << "lyx: Data socket " << fd_
210                        << " IO error on closing: " << strerror(errno);
211
212         lyx_gui::unregister_socket_callback(fd_);
213         lyxerr[Debug::LYXSERVER] << "lyx: Data socket " << fd_ << " quitting."
214                                  << endl;
215 }
216
217
218 bool LyXDataSocket::connected() const
219 {
220         return connected_;
221 }
222
223
224 // Returns true if there was a complete line to input
225 bool LyXDataSocket::readln(string & line)
226 {
227         int const charbuf_size = 100;
228         char charbuf[charbuf_size]; // buffer for the ::read() system call
229         int count;
230
231         // read and store characters in buffer
232         while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
233                 buffer_.append(charbuf, charbuf + count);
234         }
235
236         // Error conditions. The buffer must still be
237         // processed for lines read
238         if (count == 0) { // EOF -- connection closed
239                 lyxerr[Debug::LYXSERVER] << "lyx: Data socket " << fd_
240                                          << ": connection closed." << endl;
241                 connected_ = false;
242         } else if ((count == -1) && (errno != EAGAIN)) { // IO error
243                 lyxerr << "lyx: Data socket " << fd_
244                        << ": IO error." << endl;
245                 connected_ = false;
246         }
247
248         // Cut a line from buffer
249         string::size_type pos = buffer_.find('\n');
250         if (pos == string::npos) {
251                 lyxerr[Debug::LYXSERVER] << "lyx: Data socket " << fd_
252                                          << ": line not completed." << endl;
253                 return false; // No complete line stored
254         }
255         line = buffer_.substr(0, pos);
256         buffer_.erase(0, pos + 1);
257         return true;
258 }
259
260
261 // Write a line of the form <key>:<value> to the socket
262 void LyXDataSocket::writeln(string const & line)
263 {
264         string const linen(line + '\n');
265         int const size = linen.size();
266         int const written = ::write(fd_, linen.c_str(), size);
267         if (written < size) { // Always mean end of connection.
268                 if ((written == -1) && (errno == EPIPE)) {
269                         // The program will also receive a SIGPIPE
270                         // that must be catched
271                         lyxerr << "lyx: Data socket " << fd_
272                                << " connection closed while writing." << endl;
273                 } else {
274                         // Anything else, including errno == EAGAIN, must be
275                         // considered IO error. EAGAIN should never happen
276                         // when line is small
277                         lyxerr << "lyx: Data socket " << fd_
278                              << " IO error: " << strerror(errno);
279                 }
280                 connected_ = false;
281         }
282 }