X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FServer.cpp;h=d4a2120b6ea5e3c0550b5348a59766971259898a;hb=3f03f0a447e0f672025d78881d8a2790133ff47b;hp=a9233745afd1afca82edf7bea37a2109566f2088;hpb=23a672cf26c29a9fd24196961642f89498a444b5;p=lyx.git diff --git a/src/Server.cpp b/src/Server.cpp index a9233745af..d4a2120b6e 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -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. */ @@ -33,24 +34,36 @@ received LyX will inform the client that it's listening its messages, and 'bye' will inform that lyx is closing. - See development/server_monitor.c for an example client. + See development/lyxserver/server_monitor.cpp for an example client. Purpose: implement a client/server lib for LyX */ #include #include "Server.h" -#include "support/debug.h" + +#include "DispatchResult.h" #include "FuncRequest.h" +#include "LyX.h" #include "LyXAction.h" -#include "LyXFunc.h" + #include "frontends/Application.h" +#include "support/debug.h" #include "support/FileName.h" +#include "support/filetools.h" +#include "support/lassert.h" #include "support/lstrings.h" -#include "support/lyxlib.h" +#include "support/os.h" +#include "support/signals.h" -#include +#include + +#ifdef _WIN32 +#include +#include +#endif +#include #include #ifdef HAVE_SYS_STAT_H @@ -58,30 +71,632 @@ #endif #include +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 ReadReadyEvent : public QEvent { +public: + /// + ReadReadyEvent(DWORD inpipe) : QEvent(QEvent::User), inpipe_(inpipe) + {} + /// + DWORD inpipe() const { return inpipe_; } + +private: + DWORD inpipe_; +}; + +namespace { + +string errormsg(DWORD const error) +{ + void * msgbuf; + string message; + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &msgbuf, 0, NULL)) { + message = static_cast(msgbuf); + LocalFree(msgbuf); + } else + message = "Unknown error"; + + return message; +} + +} // namespace + + +DWORD WINAPI pipeServerWrapper(void * arg) +{ + LyXComm * lyxcomm = reinterpret_cast(arg); + if (!lyxcomm->pipeServer()) { + // Error exit; perform cleanup. + lyxcomm->ready_ = false; + lyxcomm->closeHandles(); + CloseHandle(lyxcomm->server_thread_); + CloseHandle(lyxcomm->stopserver_); + CloseHandle(lyxcomm->outbuf_mutex_); + lyxerr << "LyXComm: Closing connection" << endl; + } + return 1; +} + + +LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb) + : stopserver_(0), + ready_(false), pipename_(pip), client_(cli), clientcb_(ccb), + deferred_loading_(false) +{ + for (int i = 0; i < MAX_PIPES; ++i) { + event_[i] = 0; + pipe_[i].handle = INVALID_HANDLE_VALUE; + } + openConnection(); +} + + +bool LyXComm::pipeServer() +{ + DWORD i; + DWORD error; + + for (i = 0; i < MAX_PIPES; ++i) { + bool const is_outpipe = i >= MAX_CLIENTS; + DWORD const open_mode = is_outpipe ? PIPE_ACCESS_OUTBOUND + : PIPE_ACCESS_INBOUND; + string const pipename = external_path(pipeName(i)); + + // Manual-reset event, initial state = signaled + event_[i] = CreateEvent(NULL, TRUE, TRUE, NULL); + if (!event_[i]) { + error = GetLastError(); + lyxerr << "LyXComm: Could not create event for pipe " + << pipename << "\nLyXComm: " + << errormsg(error) << endl; + return false; + } + + pipe_[i].overlap.hEvent = event_[i]; + pipe_[i].iobuf.erase(); + pipe_[i].handle = CreateNamedPipe(pipename.c_str(), + open_mode | FILE_FLAG_OVERLAPPED, PIPE_WAIT, + MAX_CLIENTS, PIPE_BUFSIZE, PIPE_BUFSIZE, + PIPE_TIMEOUT, NULL); + + if (pipe_[i].handle == INVALID_HANDLE_VALUE) { + error = GetLastError(); + lyxerr << "LyXComm: Could not create pipe " + << pipename << "\nLyXComm: " + << errormsg(error) << endl; + return false; + } + + if (!startPipe(i)) + return false; + pipe_[i].state = pipe_[i].pending_io ? + CONNECTING_STATE : (is_outpipe ? WRITING_STATE + : READING_STATE); + } + + // Add the stopserver_ event + event_[MAX_PIPES] = stopserver_; + + // We made it! + LYXERR(Debug::LYXSERVER, "LyXComm: Connection established"); + ready_ = true; + outbuf_.erase(); + DWORD status = 0; + bool success = false; + + while (!checkStopServer()) { + // Indefinitely wait for the completion of an overlapped + // read, write, or connect operation. + DWORD wait = WaitForMultipleObjects(MAX_PIPES + 1, event_, + FALSE, INFINITE); + + // Determine which pipe instance completed the operation. + i = wait - WAIT_OBJECT_0; + LASSERT(i <= MAX_PIPES, /**/); + + // Check whether we were waked up for stopping the pipe server. + if (i == MAX_PIPES) + break; + + bool const is_outpipe = i >= MAX_CLIENTS; + + // Get the result if the operation was pending. + if (pipe_[i].pending_io) { + success = GetOverlappedResult(pipe_[i].handle, + &pipe_[i].overlap, &status, FALSE); + + switch (pipe_[i].state) { + case CONNECTING_STATE: + // Pending connect operation + if (!success) { + error = GetLastError(); + lyxerr << "LyXComm: " + << errormsg(error) << endl; + if (!resetPipe(i, true)) + return false; + continue; + } + pipe_[i].state = is_outpipe ? WRITING_STATE + : READING_STATE; + break; + + case READING_STATE: + // Pending read operation + LASSERT(!is_outpipe, /**/); + if (!success || status == 0) { + if (!resetPipe(i, !success)) + return false; + continue; + } + pipe_[i].nbytes = status; + pipe_[i].state = WRITING_STATE; + break; + + case WRITING_STATE: + // Pending write operation + LASSERT(is_outpipe, /**/); + // Let's see whether we have a reply + if (!outbuf_.empty()) { + // Yep. Deliver it to all pipe + // instances if we get ownership + // of the mutex, otherwise we'll + // try again the next round. + DWORD result = WaitForSingleObject( + outbuf_mutex_, 200); + if (result == WAIT_OBJECT_0) { + DWORD j = MAX_CLIENTS; + while (j < MAX_PIPES) { + pipe_[j].iobuf = outbuf_; + ++j; + } + outbuf_.erase(); + } + ReleaseMutex(outbuf_mutex_); + } + if (pipe_[i].iobuf.empty()) + pipe_[i].pending_io = false; + break; + } + } + + // Operate according to the pipe state + switch (pipe_[i].state) { + case READING_STATE: + // The pipe instance is connected to a client + // and is ready to read a request. + LASSERT(!is_outpipe, /**/); + success = ReadFile(pipe_[i].handle, + pipe_[i].readbuf, PIPE_BUFSIZE - 1, + &pipe_[i].nbytes, &pipe_[i].overlap); + + if (success && pipe_[i].nbytes != 0) { + // The read operation completed successfully. + pipe_[i].pending_io = false; + pipe_[i].state = WRITING_STATE; + continue; + } + + error = GetLastError(); + + if (!success && error == ERROR_IO_PENDING) { + // The read operation is still pending. + pipe_[i].pending_io = true; + continue; + } + + success = error == ERROR_BROKEN_PIPE; + + // Client closed connection (ERROR_BROKEN_PIPE) or + // an error occurred; in either case, reset the pipe. + if (!success) { + lyxerr << "LyXComm: " << errormsg(error) << endl; + if (!pipe_[i].iobuf.empty()) { + lyxerr << "LyXComm: truncated command: " + << pipe_[i].iobuf << endl; + pipe_[i].iobuf.erase(); + } + } + if (!resetPipe(i, !success)) + return false; + break; + + case WRITING_STATE: + if (!is_outpipe) { + // The request was successfully read + // from the client; commit it. + ReadReadyEvent * event = new ReadReadyEvent(i); + QCoreApplication::postEvent(this, + static_cast(event)); + // Wait for completion + while (pipe_[i].nbytes && !checkStopServer(100)) + ; + pipe_[i].pending_io = false; + pipe_[i].state = READING_STATE; + continue; + } + + // This is an output pipe instance. Initiate the + // overlapped write operation or monitor its progress. + + if (pipe_[i].pending_io) { + success = WriteFile(pipe_[i].handle, + pipe_[i].iobuf.c_str(), + pipe_[i].iobuf.length(), + &status, + &pipe_[i].overlap); + } + + if (success && !pipe_[i].iobuf.empty() + && status == pipe_[i].iobuf.length()) { + // The write operation completed successfully. + pipe_[i].iobuf.erase(); + pipe_[i].pending_io = false; + if (!resetPipe(i)) + return false; + continue; + } + + error = GetLastError(); + + if (success && (error == ERROR_IO_PENDING || error == NO_ERROR)) { + // The write operation is still pending. + // We get here when a reader is started + // well before a reply is ready, so delay + // a bit in order to not burden the cpu. + checkStopServer(100); + pipe_[i].pending_io = true; + continue; + } + + success = error == ERROR_NO_DATA; + + // Client closed connection (ERROR_NO_DATA) or + // an error occurred; in either case, reset the pipe. + if (!success) { + lyxerr << "LyXComm: Error sending message: " + << pipe_[i].iobuf << "\nLyXComm: " + << errormsg(error) << endl; + } + if (!resetPipe(i, !success)) + return false; + break; + } + } + + ready_ = false; + closeHandles(); + return true; +} + + +void LyXComm::closeHandles() +{ + for (int i = 0; i < MAX_PIPES; ++i) { + if (event_[i]) { + ResetEvent(event_[i]); + CloseHandle(event_[i]); + event_[i] = 0; + } + if (pipe_[i].handle != INVALID_HANDLE_VALUE) { + CloseHandle(pipe_[i].handle); + pipe_[i].handle = INVALID_HANDLE_VALUE; + } + } +} + + +bool LyXComm::event(QEvent * e) +{ + if (e->type() == QEvent::User) { + read_ready(static_cast(e)->inpipe()); + return true; + } + return false; +} + + +bool LyXComm::checkStopServer(DWORD timeout) +{ + return WaitForSingleObject(stopserver_, timeout) == WAIT_OBJECT_0; +} + + +bool LyXComm::startPipe(DWORD index) +{ + pipe_[index].pending_io = false; + pipe_[index].overlap.Offset = 0; + pipe_[index].overlap.OffsetHigh = 0; + + // Overlapped ConnectNamedPipe should return zero. + if (ConnectNamedPipe(pipe_[index].handle, &pipe_[index].overlap)) { + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Could not connect pipe " + << external_path(pipeName(index)) + << "\nLyXComm: " << errormsg(error) << endl; + return false; + } + + switch (GetLastError()) { + case ERROR_IO_PENDING: + // The overlapped connection is in progress. + pipe_[index].pending_io = true; + break; + + case ERROR_PIPE_CONNECTED: + // Client is already connected, so signal an event. + if (SetEvent(pipe_[index].overlap.hEvent)) + break; + // fall through + default: + // Anything else is an error. + DWORD const error = GetLastError(); + lyxerr << "LyXComm: An error occurred while connecting pipe " + << external_path(pipeName(index)) + << "\nLyXComm: " << errormsg(error) << endl; + return false; + } + + return true; +} + + +bool LyXComm::resetPipe(DWORD index, bool close_handle) +{ + // This method is called when an error occurs or when a client + // closes the connection. We first disconnect the pipe instance, + // then reconnect it, ready to wait for another client. + + if (!DisconnectNamedPipe(pipe_[index].handle)) { + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Could not disconnect pipe " + << external_path(pipeName(index)) + << "\nLyXComm: " << errormsg(error) << endl; + // What to do now? Let's try whether re-creating the pipe helps. + close_handle = true; + } + + bool const is_outpipe = index >= MAX_CLIENTS; + + if (close_handle) { + DWORD const open_mode = is_outpipe ? PIPE_ACCESS_OUTBOUND + : PIPE_ACCESS_INBOUND; + string const name = external_path(pipeName(index)); + + CloseHandle(pipe_[index].handle); + + pipe_[index].iobuf.erase(); + pipe_[index].handle = CreateNamedPipe(name.c_str(), + open_mode | FILE_FLAG_OVERLAPPED, PIPE_WAIT, + MAX_CLIENTS, PIPE_BUFSIZE, PIPE_BUFSIZE, + PIPE_TIMEOUT, NULL); + + if (pipe_[index].handle == INVALID_HANDLE_VALUE) { + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Could not reset pipe " << name + << "\nLyXComm: " << errormsg(error) << endl; + return false; + } + } + + if (!startPipe(index)) + return false; + pipe_[index].state = pipe_[index].pending_io ? + CONNECTING_STATE : (is_outpipe ? WRITING_STATE + : READING_STATE); + return true; +} + + +void LyXComm::openConnection() +{ + LYXERR(Debug::LYXSERVER, "LyXComm: Opening connection"); + + // If we are up, that's an error + if (ready_) { + LYXERR(Debug::LYXSERVER, "LyXComm: Already connected"); + return; + } + + if (pipename_.empty()) { + LYXERR(Debug::LYXSERVER, "LyXComm: server is disabled, nothing to do"); + return; + } + + // Check whether the pipe name is being used by some other instance. + if (!stopserver_ && WaitNamedPipe(inPipeName().c_str(), 0)) { + // Tell the running instance to load the files + if (run_mode == USE_REMOTE && loadFilesInOtherInstance()) { + deferred_loading_ = true; + pipename_.erase(); + return; + } + lyxerr << "LyXComm: Pipe " << external_path(inPipeName()) + << " already exists.\nMaybe another instance of LyX" + " is using it." << endl; + pipename_.erase(); + return; + } + + // Mutex with no initial owner for synchronized access to outbuf_ + outbuf_mutex_ = CreateMutex(NULL, FALSE, NULL); + if (!outbuf_mutex_) { + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Could not create output buffer mutex" + << "\nLyXComm: " << errormsg(error) << endl; + pipename_.erase(); + return; + } + + // Manual-reset event, initial state = not signaled + stopserver_ = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!stopserver_) { + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Could not create stop server event" + << "\nLyXComm: " << errormsg(error) << endl; + pipename_.erase(); + CloseHandle(outbuf_mutex_); + return; + } + + server_thread_ = CreateThread(NULL, 0, pipeServerWrapper, + static_cast(this), 0, NULL); + if (!server_thread_) { + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Could not create pipe server thread" + << "\nLyXComm: " << errormsg(error) << endl; + pipename_.erase(); + CloseHandle(stopserver_); + CloseHandle(outbuf_mutex_); + return; + } +} + + +/// 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_) { + LYXERR(Debug::LYXSERVER, "LyXComm: Already disconnected"); + return; + } + + SetEvent(stopserver_); + // Wait for the pipe server to finish + WaitForSingleObject(server_thread_, INFINITE); + CloseHandle(server_thread_); + ResetEvent(stopserver_); + CloseHandle(stopserver_); + CloseHandle(outbuf_mutex_); +} + + +void LyXComm::emergencyCleanup() +{ + if (ready_) { + SetEvent(stopserver_); + // Forcibly terminate the pipe server thread if it does + // not finish quickly. + if (WaitForSingleObject(server_thread_, 200) != WAIT_OBJECT_0) { + TerminateThread(server_thread_, 0); + ready_ = false; + closeHandles(); + } + CloseHandle(server_thread_); + ResetEvent(stopserver_); + CloseHandle(stopserver_); + CloseHandle(outbuf_mutex_); + } +} + + +void LyXComm::read_ready(DWORD inpipe) +{ + // Turn the pipe buffer into a C string + DWORD const nbytes = pipe_[inpipe].nbytes; + pipe_[inpipe].readbuf[nbytes] = '\0'; + + pipe_[inpipe].iobuf += rtrim(pipe_[inpipe].readbuf, "\r"); + + // Commit any commands read + while (pipe_[inpipe].iobuf.find('\n') != string::npos) { + // split() grabs the entire string if + // the delim /wasn't/ found. ?:-P + string cmd; + pipe_[inpipe].iobuf = split(pipe_[inpipe].iobuf, cmd, '\n'); + cmd = rtrim(cmd, "\r"); + LYXERR(Debug::LYXSERVER, "LyXComm: nbytes:" << nbytes + << ", iobuf:" << pipe_[inpipe].iobuf + << ", cmd:" << cmd); + if (!cmd.empty()) + clientcb_(client_, cmd); + //\n or not \n? + } + // Signal that we are done. + pipe_[inpipe].nbytes = 0; +} + + +void LyXComm::send(string const & msg) +{ + if (msg.empty()) { + lyxerr << "LyXComm: Request to send empty string. Ignoring." + << endl; + return; + } + + LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\''); + + if (pipename_.empty()) + return; + + if (!ready_) { + lyxerr << "LyXComm: Pipes are closed. Could not send " + << msg << endl; + return; + } + + // Request ownership of the outbuf_mutex_ + DWORD result = WaitForSingleObject(outbuf_mutex_, PIPE_TIMEOUT); + + if (result == WAIT_OBJECT_0) { + // If a client doesn't care to read a reply (JabRef is one + // such client), the output buffer could grow without limit. + // So, we empty it when its size is larger than PIPE_BUFSIZE. + if (outbuf_.size() > PIPE_BUFSIZE) + outbuf_.erase(); + outbuf_ += msg; + ReleaseMutex(outbuf_mutex_); + } else { + // Something is fishy, better resetting the connection. + DWORD const error = GetLastError(); + lyxerr << "LyXComm: Error sending message: " << msg + << "\nLyXComm: " << errormsg(error) + << "\nLyXComm: Resetting connection" << endl; + ReleaseMutex(outbuf_mutex_); + closeConnection(); + openConnection(); + } +} + + +string const LyXComm::pipeName(DWORD index) const +{ + return index < MAX_CLIENTS ? inPipeName() : outPipeName(); +} + + +#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() {} @@ -103,6 +718,7 @@ void LyXComm::endPipe(int & fd, string const & filename, bool write) void LyXComm::emergencyCleanup() {} + void LyXComm::read_ready() {} @@ -113,11 +729,11 @@ void LyXComm::send(string const & msg) #else // defined (HAVE_MKFIFO) - -LyXComm::LyXComm(std::string const & pip, Server * cli, ClientCallbackfct ccb) - : pipename_(pip), client_(cli), clientcb_(ccb) +LyXComm::LyXComm(string const & pip, Server * cli, ClientCallbackfct ccb) + : infd_(-1), outfd_(-1), + ready_(false), pipename_(pip), client_(cli), clientcb_(ccb), + deferred_loading_(false) { - ready_ = false; openConnection(); } @@ -185,13 +801,45 @@ 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); + // Tell the running instance to load the files + if (run_mode == USE_REMOTE && loadFilesInOtherInstance()) { + deferred_loading_ = true; + pipename_.erase(); + return -1; + } + } 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) { @@ -210,8 +858,12 @@ int LyXComm::startPipe(string const & file, bool write) } if (!write) { - theApp()->registerSocketCallback(fd, - boost::bind(&LyXComm::read_ready, this)); + // Make sure not to call read_ready after destruction. + weak_ptr tracker = tracker_.p(); + theApp()->registerSocketCallback(fd, [=](){ + if (!tracker.expired()) + read_ready(); + }); } return fd; @@ -231,7 +883,7 @@ void LyXComm::endPipe(int & fd, string const & filename, bool write) << '\n' << strerror(errno) << endl; } - if (FileName(filename).removeFile() < 0) { + if (!FileName(filename).removeFile()) { lyxerr << "LyXComm: Could not remove pipe " << filename << '\n' << strerror(errno) << endl; } @@ -259,11 +911,16 @@ 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. while ((status = ::read(infd_, charbuf, charbuf_size - 1))) { - if (status > 0) { charbuf[status] = '\0'; // turn it into a c string read_buffer_ += rtrim(charbuf, "\r"); @@ -280,12 +937,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 +953,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; @@ -312,7 +971,8 @@ void LyXComm::send(string const & msg) LYXERR(Debug::LYXSERVER, "LyXComm: Sending '" << msg << '\''); - if (pipename_.empty()) return; + if (pipename_.empty()) + return; if (!ready_) { LYXERR0("LyXComm: Pipes are closed. Could not send " << msg); @@ -327,6 +987,49 @@ void LyXComm::send(string const & msg) #endif // defined (HAVE_MKFIFO) +namespace { + +struct Sleep : QThread +{ + static void millisec(unsigned long ms) + { + QThread::usleep(ms * 1000); + } +}; + +} // namespace + + +bool LyXComm::loadFilesInOtherInstance() +{ + int pipefd; + int loaded_files = 0; + FileName const pipe(inPipeName()); + vector::iterator it = theFilesToLoad().begin(); + while (it != theFilesToLoad().end()) { + FileName fname = fileSearch(string(), os::internal_path(*it), + "lyx", may_not_exist); + if (fname.empty()) { + ++it; + continue; + } + // Wait a while to allow time for the other + // instance to reset the connection + Sleep::millisec(200); + pipefd = ::open(pipe.toFilesystemEncoding().c_str(), O_WRONLY); + if (pipefd < 0) + break; + string const cmd = "LYXCMD:pipe:file-open:" + + fname.absFileName() + '\n'; + if (::write(pipefd, cmd.c_str(), cmd.length()) < 0) + LYXERR0("Cannot write to pipe!"); + ::close(pipefd); + ++loaded_files; + it = theFilesToLoad().erase(it); + } + return loaded_files > 0; +} + string const LyXComm::inPipeName() const { @@ -351,8 +1054,8 @@ void ServerCallback(Server * server, string const & msg) server->callback(msg); } -Server::Server(LyXFunc * f, std::string const & pipes) - : numclients_(0), func_(f), pipes_(pipes, this, &ServerCallback) +Server::Server(string const & pipes) + : numclients_(0), pipes_(pipes, this, &ServerCallback) {} @@ -371,7 +1074,6 @@ Server::~Server() int compare(char const * a, char const * b, unsigned int len) { - using namespace std; return strncmp(a, b, len); } @@ -460,7 +1162,7 @@ void Server::callback(string const & msg) << client << " said goodbye"); } else { LYXERR(Debug::LYXSERVER, - "Server: ignoring bye messge from unregistered client" << client); + "Server: ignoring bye message from unregistered client" << client); } } else { LYXERR0("Server: Undefined server command " << cmd << '.'); @@ -473,17 +1175,20 @@ void Server::callback(string const & msg) // The correct solution would be to have a // specialized (non-gui) BufferView. But how do // we do it now? Probably we should just let it - // connect to the lyxfunc in the single LyXView we + // connect to the lyxfunc in the single GuiView we // support currently. (Lgb) - func_->dispatch(FuncRequest(lyxaction.lookupFunc(cmd), arg)); - string const rval = to_utf8(func_->getMessage()); + FuncRequest fr(lyxaction.lookupFunc(cmd), from_utf8(arg)); + fr.setOrigin(FuncRequest::LYXSERVER); + DispatchResult dr; + theApp()->dispatch(fr, dr); + string const rval = to_utf8(dr.message()); // all commands produce an INFO or ERROR message // in the output pipe, even if they do not return // anything. See chapter 4 of Customization doc. string buf; - if (func_->errorStat()) + if (dr.error()) buf = "ERROR:"; else buf = "INFO:"; @@ -502,7 +1207,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 +1215,7 @@ void Server::notifyClient(string const & s) } // namespace lyx + +#ifdef _WIN32 +#include "moc_Server.cpp" +#endif