]> git.lyx.org Git - lyx.git/blobdiff - src/Server.cpp
Implement the LyXServer on Windows.
[lyx.git] / src / Server.cpp
index a9233745afd1afca82edf7bea37a2109566f2088..77c1a5981a86fdfce8c91793bc5e7df9bdbdcd9b 100644 (file)
@@ -3,10 +3,11 @@
  * This file is part of LyX, the document processor.
  * Licence details can be found in the file COPYING.
  *
- * \author Lars Gullik Bjønnes
+ * \author Lars Gullik Bjønnes
  * \author Jean-Marc Lasgouttes
  * \author Angus Leeming
  * \author John Levon
+ * \author Enrico Forestieri
  *
  * Full author contact details are available in file CREDITS.
  */
 #include <config.h>
 
 #include "Server.h"
-#include "support/debug.h"
 #include "FuncRequest.h"
 #include "LyXAction.h"
 #include "LyXFunc.h"
+
 #include "frontends/Application.h"
 
+#include "support/debug.h"
 #include "support/FileName.h"
 #include "support/lstrings.h"
-#include "support/lyxlib.h"
+#include "support/os.h"
 
 #include <boost/bind.hpp>
 
+#ifdef _WIN32
+#include <QCoreApplication>
+#endif
+
 #include <cerrno>
 #ifdef HAVE_SYS_STAT_H
 # include <sys/stat.h>
 #endif
 #include <fcntl.h>
 
+using namespace std;
+using namespace lyx::support;
+using os::external_path;
 
 namespace lyx {
 
-using support::compare;
-using support::FileName;
-using support::rtrim;
-using support::split;
-
-using std::endl;
-using std::string;
-
-
 /////////////////////////////////////////////////////////////////////
 //
 // LyXComm
 //
 /////////////////////////////////////////////////////////////////////
 
-#if !defined (HAVE_MKFIFO)
+#if defined(_WIN32)
+
+class PipeEvent : public QEvent {
+public:
+       ///
+       PipeEvent(HANDLE inpipe) : QEvent(QEvent::User), inpipe_(inpipe)
+       {}
+       ///
+       HANDLE pipe() const { return inpipe_; }
+
+private:
+       HANDLE inpipe_;
+};
+
+namespace {
+
+char * errormsg()
+{
+       void * msgbuf;
+       DWORD error = GetLastError();
+       FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                     FORMAT_MESSAGE_FROM_SYSTEM |
+                     FORMAT_MESSAGE_IGNORE_INSERTS,
+                     NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+                     (LPTSTR) &msgbuf, 0, NULL);
+       return static_cast<char *>(msgbuf);
+}
+
+
+extern "C" {
+
+DWORD WINAPI pipeServerWrapper(void * arg)
+{
+       LyXComm * lyxcomm = reinterpret_cast<LyXComm *>(arg);
+       lyxcomm->pipeServer();
+       return 1;
+}
+
+}
+
+} // namespace anon
+
+LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
+       : pipename_(pip), client_(cli), clientcb_(ccb)
+{
+       ready_ = false;
+       openConnection();
+}
+
+
+void LyXComm::pipeServer()
+{
+       int const bufsize = 1024;
+       HANDLE inpipe;
+
+       outpipe_ = CreateNamedPipe(external_path(outPipeName()).c_str(),
+                                  PIPE_ACCESS_OUTBOUND, PIPE_NOWAIT,
+                                  PIPE_UNLIMITED_INSTANCES,
+                                  bufsize, 0, 0, NULL);
+       if (outpipe_ == INVALID_HANDLE_VALUE) {
+               lyxerr << "LyXComm: Could not create pipe "
+                      << outPipeName() << '\n' << errormsg() << endl;
+               return;
+       }
+       ConnectNamedPipe(outpipe_, NULL);
+       // Now change to blocking mode
+       DWORD mode = PIPE_WAIT;
+       SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL);
+
+       while (!checkStopServerEvent()) {
+               inpipe = CreateNamedPipe(external_path(inPipeName()).c_str(),
+                                        PIPE_ACCESS_INBOUND, PIPE_WAIT,
+                                        PIPE_UNLIMITED_INSTANCES,
+                                        0, bufsize, 0, NULL);
+               if (inpipe == INVALID_HANDLE_VALUE) {
+                       lyxerr << "LyXComm: Could not create pipe "
+                              << inPipeName() << '\n' << errormsg() << endl;
+                       break;
+               }
+
+               BOOL connected = ConnectNamedPipe(inpipe, NULL);
+               // Check whether we are signaled to shutdown the pipe server.
+               if (checkStopServerEvent()) {
+                       CloseHandle(inpipe);
+                       break;
+               }
+               if (connected || GetLastError() == ERROR_PIPE_CONNECTED) {
+                       PipeEvent * event = new PipeEvent(inpipe);
+                       QCoreApplication::postEvent(this,
+                                       static_cast<QEvent *>(event));
+               } else
+                       CloseHandle(inpipe);
+       }
+       CloseHandle(outpipe_);
+}
+
+
+bool LyXComm::event(QEvent * e)
+{
+       if (e->type() == QEvent::User) {
+               read_ready(static_cast<PipeEvent *>(e)->pipe());
+               return true;
+       }
+       return false;
+}
+
+
+BOOL LyXComm::checkStopServerEvent()
+{
+       return WaitForSingleObject(stopserver_, 0) == WAIT_OBJECT_0;
+}
+
+
+void LyXComm::openConnection()
+{
+       LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection");
+
+       // If we are up, that's an error
+       if (ready_) {
+               lyxerr << "LyXComm: Already connected" << endl;
+               return;
+       }
+       // We assume that we don't make it
+       ready_ = false;
+
+       if (pipename_.empty()) {
+               LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
+               return;
+       }
+
+       stopserver_ = CreateEvent(NULL, TRUE, FALSE, NULL);
+       DWORD tid;
+       HANDLE thread = CreateThread(NULL, 0, pipeServerWrapper,
+                                    static_cast<void *>(this), 0, &tid);
+       if (!thread) {
+               lyxerr << "LyXComm: Could not create pipe server thread: "
+                      << errormsg() << endl;
+               return;
+       } else
+               CloseHandle(thread);
+
+       // We made it!
+       ready_ = true;
+       LYXERR(Debug::LYXSERVER, "LyXComm: Connection established");
+}
+
+
+/// Close pipes
+void LyXComm::closeConnection()
+{
+       LYXERR(Debug::LYXSERVER, "LyXComm: Closing connection");
+
+       if (pipename_.empty()) {
+               LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do");
+               return;
+       }
+
+       if (!ready_) {
+               LYXERR0("LyXComm: Already disconnected");
+               return;
+       }
+
+       SetEvent(stopserver_);
+       HANDLE hpipe = CreateFile(external_path(inPipeName()).c_str(),
+                                 GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+                                 FILE_ATTRIBUTE_NORMAL, NULL);
+       if (hpipe != INVALID_HANDLE_VALUE)
+               CloseHandle(hpipe);
+
+       ready_ = false;
+}
+
+
+void LyXComm::emergencyCleanup()
+{
+       if (!pipename_.empty()) {
+               SetEvent(stopserver_);
+               HANDLE hpipe = CreateFile(external_path(inPipeName()).c_str(),
+                                         GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+                                         FILE_ATTRIBUTE_NORMAL, NULL);
+               if (hpipe != INVALID_HANDLE_VALUE)
+                       CloseHandle(hpipe);
+       }
+}
+
+void LyXComm::read_ready(HANDLE inpipe)
+{
+       string read_buffer_;
+       read_buffer_.erase();
+
+       int const charbuf_size = 100;
+       char charbuf[charbuf_size];
+
+       DWORD status;
+       while (ReadFile(inpipe, charbuf, charbuf_size - 1, &status, NULL)) {
+               if (status > 0) {
+                       charbuf[status] = '\0'; // turn it into a c string
+                       read_buffer_ += rtrim(charbuf, "\r");
+                       // commit any commands read
+                       while (read_buffer_.find('\n') != string::npos) {
+                               // split() grabs the entire string if
+                               // the delim /wasn't/ found. ?:-P
+                               string cmd;
+                               read_buffer_= split(read_buffer_, cmd, '\n');
+                               cmd = rtrim(cmd, "\r");
+                               LYXERR(Debug::LYXSERVER, "LyXComm: status:" << status
+                                       << ", read_buffer_:" << read_buffer_
+                                       << ", cmd:" << cmd);
+                               if (!cmd.empty())
+                                       clientcb_(client_, cmd);
+                                       //\n or not \n?
+                       }
+               }
+       }
+       if (GetLastError() != ERROR_BROKEN_PIPE) {
+               // An error occurred
+               LYXERR0("LyXComm: " << errormsg());
+               if (!read_buffer_.empty()) {
+                       LYXERR0("LyXComm: truncated command: " << read_buffer_);
+                       read_buffer_.erase();
+               }
+       }
+       // Client closed the pipe, so disconnect and close.
+       DisconnectNamedPipe(inpipe);
+       CloseHandle(inpipe);
+       FlushFileBuffers(outpipe_);
+       DisconnectNamedPipe(outpipe_);
+       // Temporarily change to non-blocking mode otherwise
+       // ConnectNamedPipe() would block waiting for a connection.
+       DWORD mode = PIPE_NOWAIT;
+       SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL);
+       ConnectNamedPipe(outpipe_, NULL);
+       mode = PIPE_WAIT;
+       SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL);
+}
+
+
+void LyXComm::send(string const & msg)
+{
+       if (msg.empty()) {
+               LYXERR0("LyXComm: Request to send empty string. Ignoring.");
+               return;
+       }
+
+       LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\'');
+
+       if (pipename_.empty()) return;
+
+       if (!ready_) {
+               LYXERR0("LyXComm: Pipes are closed. Could not send " << msg);
+               return;
+       }
+
+       bool success;
+       int count = 0;
+       do {
+               DWORD sent;
+               success = WriteFile(outpipe_, msg.c_str(), msg.length(), &sent, NULL);
+               if (!success) {
+                       DWORD error = GetLastError();
+                       if (error == ERROR_NO_DATA) {
+                               DisconnectNamedPipe(outpipe_);
+                               DWORD mode = PIPE_NOWAIT;
+                               SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL);
+                               ConnectNamedPipe(outpipe_, NULL);
+                               mode = PIPE_WAIT;
+                               SetNamedPipeHandleState(outpipe_, &mode, NULL, NULL);
+                       } else if (error != ERROR_PIPE_LISTENING)
+                               break;
+               }
+               Sleep(100);
+               ++count;
+       } while (!success && count < 100);
+
+       if (!success) {
+               lyxerr << "LyXComm: Error sending message: " << msg
+                      << '\n' << errormsg()
+                      << "\nLyXComm: Resetting connection" << endl;
+               closeConnection();
+               openConnection();
+       }
+}
+
+
+#elif !defined (HAVE_MKFIFO)
 // We provide a stub class that disables the lyxserver.
 
-LyXComm::LyXComm(std::string const &, Server *, ClientCallbackfct)
+LyXComm::LyXComm(string const &, Server *, ClientCallbackfct)
 {}
 
 void LyXComm::openConnection()
@@ -114,7 +398,7 @@ void LyXComm::send(string const & msg)
 #else // defined (HAVE_MKFIFO)
 
 
-LyXComm::LyXComm(std::string const & pip, Server * cli, ClientCallbackfct ccb)
+LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb)
        : pipename_(pip), client_(cli), clientcb_(ccb)
 {
        ready_ = false;
@@ -185,13 +469,39 @@ void LyXComm::closeConnection()
 
 int LyXComm::startPipe(string const & file, bool write)
 {
+       static bool stalepipe = false;
        FileName const filename(file);
-       if (::access(filename.toFilesystemEncoding().c_str(), F_OK) == 0) {
-               lyxerr << "LyXComm: Pipe " << filename << " already exists.\n"
-                      << "If no other LyX program is active, please delete"
-                       " the pipe by hand and try again." << endl;
-               pipename_.erase();
-               return -1;
+       if (filename.exists()) {
+               if (!write) {
+                       // Let's see whether we have a stale pipe.
+                       int fd = ::open(filename.toFilesystemEncoding().c_str(),
+                                       O_WRONLY | O_NONBLOCK);
+                       if (fd >= 0) {
+                               // Another LyX instance is using it.
+                               ::close(fd);
+                       } else if (errno == ENXIO) {
+                               // No process is reading from the other end.
+                               stalepipe = true;
+                               LYXERR(Debug::LYXSERVER,
+                                       "LyXComm: trying to remove "
+                                       << filename);
+                               filename.removeFile();
+                       }
+               } else if (stalepipe) {
+                       LYXERR(Debug::LYXSERVER, "LyXComm: trying to remove "
+                               << filename);
+                       filename.removeFile();
+                       stalepipe = false;
+               }
+               if (filename.exists()) {
+                       lyxerr << "LyXComm: Pipe " << filename
+                              << " already exists.\nIf no other LyX program"
+                                 " is active, please delete the pipe by hand"
+                                 " and try again."
+                              << endl;
+                       pipename_.erase();
+                       return -1;
+               }
        }
 
        if (::mkfifo(filename.toFilesystemEncoding().c_str(), 0600) < 0) {
@@ -259,6 +569,12 @@ void LyXComm::read_ready()
        int const charbuf_size = 100;
        char charbuf[charbuf_size];
 
+       // As O_NONBLOCK is set, until no data is available for reading,
+       // read() doesn't block but returns -1 and set errno to EAGAIN.
+       // After a client that opened the pipe for writing, closes it
+       // (and no other client is using the pipe), read() would always
+       // return 0 and thus the connection has to be reset.
+
        errno = 0;
        int status;
        // the single = is intended here.
@@ -280,12 +596,13 @@ void LyXComm::read_ready()
                                        clientcb_(client_, cmd);
                                        //\n or not \n?
                        }
-               }
-               if (errno == EAGAIN) {
-                       errno = 0;
-                       return;
-               }
-               if (errno != 0) {
+               } else {
+                       if (errno == EAGAIN) {
+                               // Nothing to read, continue
+                               errno = 0;
+                               return;
+                       }
+                       // An error occurred, better bailing out
                        LYXERR0("LyXComm: " << strerror(errno));
                        if (!read_buffer_.empty()) {
                                LYXERR0("LyXComm: truncated command: " << read_buffer_);
@@ -295,8 +612,9 @@ void LyXComm::read_ready()
                }
        }
 
-       // The connection gets reset in errno != EAGAIN
-       // Why does it need to be reset if errno == 0?
+       // The connection gets reset when read() returns 0 (meaning that the
+       // last client closed the pipe) or an error occurred, in which case
+       // read() returns -1 and errno != EAGAIN.
        closeConnection();
        openConnection();
        errno = 0;
@@ -351,7 +669,7 @@ void ServerCallback(Server * server, string const & msg)
        server->callback(msg);
 }
 
-Server::Server(LyXFunc * f, std::string const & pipes)
+Server::Server(LyXFunc * f, string const & pipes)
        : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback)
 {}
 
@@ -502,7 +820,7 @@ void Server::callback(string const & msg)
 }
 
 
-// Send a notify messge to a client, called by WorkAreaKeyPress
+// Send a notify message to a client, called by WorkAreaKeyPress
 void Server::notifyClient(string const & s)
 {
        pipes_.send("NOTIFY:" + s + "\n");
@@ -510,3 +828,7 @@ void Server::notifyClient(string const & s)
 
 
 } // namespace lyx
+
+#ifdef _WIN32
+#include "moc_Server.cpp"
+#endif