]> git.lyx.org Git - lyx.git/blob - src/lyxsocket.C
fix bug 2089: Touching Navigate menu crashes Lyx when a TOC inset is in a section...
[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         lyx_gui::unregister_socket_callback(fd_);
78         ::close(fd_);
79         lyx::support::unlink(address_);
80         lyxerr[Debug::LYXSERVER] << "lyx: Server socket quitting" << endl;
81 }
82
83
84 string const & LyXServerSocket::address() const
85 {
86         return address_;
87 }
88
89
90 // Creates a new LyXDataSocket and checks to see if the connection
91 // is OK and if the number of clients does not exceed MAX_CLIENTS
92 void LyXServerSocket::serverCallback()
93 {
94         int const client_fd = lyx::support::socktools::accept(fd_);
95
96         if (fd_ == -1) {
97                 lyxerr[Debug::LYXSERVER] << "lyx: Failed to accept new client"
98                                          << endl;
99                 return;
100         }
101
102         if (clients.size() >= MAX_CLIENTS) {
103                 writeln("BYE:Too many clients connected");
104                 return;
105         }
106
107         // Register the new client.
108         clients[client_fd] =
109                 shared_ptr<LyXDataSocket>(new LyXDataSocket(client_fd));
110         lyx_gui::register_socket_callback(
111                 client_fd,
112                 boost::bind(&LyXServerSocket::dataCallback,
113                             this, client_fd)
114                 );
115 }
116
117
118 // Reads and processes input from client and check
119 // if the connection has been closed
120 void LyXServerSocket::dataCallback(int fd)
121 {
122         shared_ptr<LyXDataSocket> client = clients[fd];
123
124         string line;
125         string::size_type pos;
126         bool saidbye = false;
127         while ((!saidbye) && client->readln(line)) {
128                 // The protocol must be programmed here
129                 // Split the key and the data
130                 if ((pos = line.find(':')) == string::npos) {
131                         client->writeln("ERROR:" + line + ":malformed message");
132                         continue;
133                 }
134
135                 string const key = line.substr(0, pos);
136                 if (key == "LYXCMD") {
137                         string const cmd = line.substr(pos + 1);
138                         func->dispatch(lyxaction.lookupFunc(cmd));
139                         string const rval = func->getMessage();
140                         if (func->errorStat()) {
141                                 client->writeln("ERROR:" + cmd + ':' + rval);
142                         } else {
143                                 client->writeln("INFO:" + cmd + ':' + rval);
144                         }
145                 } else if (key == "HELLO") {
146                         // no use for client name!
147                         client->writeln("HELLO:");
148                 } else if (key == "BYE") {
149                         saidbye = true;
150                 } else {
151                         client->writeln("ERROR:unknown key " + key);
152                 }
153         }
154
155         if (saidbye || (!client->connected())) {
156                 clients.erase(fd);
157         }
158 }
159
160
161 void LyXServerSocket::writeln(string const & line)
162 {
163         string const linen(line + '\n');
164         int const size = linen.size();
165         int const written = ::write(fd_, linen.c_str(), size);
166         if (written < size) { // Always mean end of connection.
167                 if ((written == -1) && (errno == EPIPE)) {
168                         // The program will also receive a SIGPIPE
169                         // that must be caught
170                         lyxerr << "lyx: Server socket " << fd_
171                                << " connection closed while writing." << endl;
172                 } else {
173                         // Anything else, including errno == EAGAIN, must be
174                         // considered IO error. EAGAIN should never happen
175                         // when line is small
176                         lyxerr << "lyx: Server socket " << fd_
177                              << " IO error: " << strerror(errno);
178                 }
179         }
180 }
181
182 // Debug
183 // void LyXServerSocket::dump() const
184 // {
185 //      lyxerr << "LyXServerSocket debug dump.\n"
186 //           << "fd = " << fd_ << ", address = " << address_ << ".\n"
187 //           << "Clients: " << clients.size() << ".\n";
188 //      std::map<int, shared_ptr<LyXDataSocket> >::const_iterator client = clients.begin();
189 //      std::map<int, shared_ptr<LyXDataSocket> >::const_iterator end = clients.end();
190 //      for (; client != end; ++client)
191 //              lyxerr << "fd = " << client->first << '\n';
192 // }
193
194
195 LyXDataSocket::LyXDataSocket(int fd)
196         : fd_(fd), connected_(true)
197 {
198         lyxerr[Debug::LYXSERVER] << "lyx: New data socket " << fd_ << endl;
199 }
200
201
202 LyXDataSocket::~LyXDataSocket()
203 {
204         ::close(fd_);
205
206         lyx_gui::unregister_socket_callback(fd_);
207         lyxerr[Debug::LYXSERVER] << "lyx: Data socket " << fd_ << " quitting."
208                                  << endl;
209 }
210
211
212 bool LyXDataSocket::connected() const
213 {
214         return connected_;
215 }
216
217
218 // Returns true if there was a complete line to input
219 bool LyXDataSocket::readln(string & line)
220 {
221         int const charbuf_size = 100;
222         char charbuf[charbuf_size]; // buffer for the ::read() system call
223         int count;
224
225         // read and store characters in buffer
226         while ((count = ::read(fd_, charbuf, charbuf_size - 1)) > 0) {
227                 buffer_.append(charbuf, charbuf + count);
228         }
229
230         // Error conditions. The buffer must still be
231         // processed for lines read
232         if (count == 0) { // EOF -- connection closed
233                 lyxerr[Debug::LYXSERVER] << "lyx: Data socket " << fd_
234                                          << ": connection closed." << endl;
235                 connected_ = false;
236         } else if ((count == -1) && (errno != EAGAIN)) { // IO error
237                 lyxerr << "lyx: Data socket " << fd_
238                        << ": IO error." << endl;
239                 connected_ = false;
240         }
241
242         // Cut a line from buffer
243         string::size_type pos = buffer_.find('\n');
244         if (pos == string::npos) {
245                 lyxerr[Debug::LYXSERVER] << "lyx: Data socket " << fd_
246                                          << ": line not completed." << endl;
247                 return false; // No complete line stored
248         }
249         line = buffer_.substr(0, pos);
250         buffer_.erase(0, pos + 1);
251         return true;
252 }
253
254
255 // Write a line of the form <key>:<value> to the socket
256 void LyXDataSocket::writeln(string const & line)
257 {
258         string const linen(line + '\n');
259         int const size = linen.size();
260         int const written = ::write(fd_, linen.c_str(), size);
261         if (written < size) { // Always mean end of connection.
262                 if ((written == -1) && (errno == EPIPE)) {
263                         // The program will also receive a SIGPIPE
264                         // that must be catched
265                         lyxerr << "lyx: Data socket " << fd_
266                                << " connection closed while writing." << endl;
267                 } else {
268                         // Anything else, including errno == EAGAIN, must be
269                         // considered IO error. EAGAIN should never happen
270                         // when line is small
271                         lyxerr << "lyx: Data socket " << fd_
272                              << " IO error: " << strerror(errno);
273                 }
274                 connected_ = false;
275         }
276 }