[Libreoffice-commits] online.git: loolwsd/LOOLSession.cpp loolwsd/LOOLSession.hpp loolwsd/LOOLWSD.cpp loolwsd/Makefile.am loolwsd/MasterProcessSession.cpp loolwsd/MasterProcessSession.hpp
Henry Castro
hcastro at collabora.com
Wed Dec 23 09:03:15 PST 2015
loolwsd/LOOLSession.cpp | 595 ------------------------------------
loolwsd/LOOLSession.hpp | 69 ----
loolwsd/LOOLWSD.cpp | 1
loolwsd/Makefile.am | 4
loolwsd/MasterProcessSession.cpp | 640 +++++++++++++++++++++++++++++++++++++++
loolwsd/MasterProcessSession.hpp | 89 +++++
6 files changed, 732 insertions(+), 666 deletions(-)
New commits:
commit 894ab66d8cf00d05f0e2b122b32e7b5a72734cdb
Author: Henry Castro <hcastro at collabora.com>
Date: Sat Dec 12 13:50:12 2015 -0500
loolwsd: Refactored MasterProcessSession
MasterProcessSession class now moved to own files.
Change-Id: Ic1a980295b9bb4b28ec9e205de1544fb98ad98f8
Reviewed-on: https://gerrit.libreoffice.org/20893
Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
Tested-by: Henry Castro <hcastro at collabora.com>
diff --git a/loolwsd/LOOLSession.cpp b/loolwsd/LOOLSession.cpp
index f5299aa..7c208bb 100644
--- a/loolwsd/LOOLSession.cpp
+++ b/loolwsd/LOOLSession.cpp
@@ -169,601 +169,6 @@ void LOOLSession::parseDocOptions(const StringTokenizer& tokens, int& part, std:
}
}
-std::map<Process::PID, UInt64> MasterProcessSession::_childProcesses;
-
-std::set<std::shared_ptr<MasterProcessSession>> MasterProcessSession::_availableChildSessions;
-std::mutex MasterProcessSession::_availableChildSessionMutex;
-std::condition_variable MasterProcessSession::_availableChildSessionCV;
-Random MasterProcessSession::_rng;
-std::mutex MasterProcessSession::_rngMutex;
-
-MasterProcessSession::MasterProcessSession(std::shared_ptr<WebSocket> ws, Kind kind) :
- LOOLSession(ws, kind),
- _childId(0),
- _pidChild(0),
- _curPart(0),
- _loadPart(-1)
-{
- std::cout << Util::logPrefix() << "MasterProcessSession ctor this=" << this << " ws=" << _ws.get() << std::endl;
-}
-
-MasterProcessSession::~MasterProcessSession()
-{
- std::cout << Util::logPrefix() << "MasterProcessSession dtor this=" << this << " _peer=" << _peer.lock().get() << std::endl;
-
- auto peer = _peer.lock();
- if (_kind == Kind::ToClient && peer)
- {
- Util::shutdownWebSocket(*(peer->_ws));
- }
-}
-
-bool MasterProcessSession::handleInput(const char *buffer, int length)
-{
- Application::instance().logger().information(Util::logPrefix() + _kindString + ",Input," + getAbbreviatedMessage(buffer, length));
-
- std::string firstLine = getFirstLine(buffer, length);
- StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
-
- if (haveSeparateProcess())
- {
- // Note that this handles both forwarding requests from the client to the child process, and
- // forwarding replies from the child process to the client. Or does it?
-
- // Snoop at some messages and manipulate tile cache information as needed
- auto peer = _peer.lock();
-
- if (_kind == Kind::ToPrisoner)
- {
- if (tokens[0] == "curpart:" &&
- tokens.count() == 2 &&
- getTokenInteger(tokens[1], "part", _curPart))
- {
- return true;
- }
-
- if (tokens.count() == 2 && tokens[0] == "saveas:")
- {
- std::string url;
- if (!getTokenString(tokens[1], "url", url))
- return true;
-
- if (peer)
- {
- // Save as completed, inform the other (Kind::ToClient)
- // MasterProcessSession about it.
-
- const std::string filePrefix("file:///");
- if (url.find(filePrefix) == 0)
- {
- // Rewrite file:// URLs, as they are visible to the outside world.
- Path path(MasterProcessSession::getJailPath(_childId), url.substr(filePrefix.length()));
- url = filePrefix + path.toString().substr(1);
- }
- peer->_saveAsQueue.put(url);
- }
-
- return true;
- }
- }
-
- if (_kind == Kind::ToPrisoner && peer && peer->_tileCache)
- {
- if (tokens[0] == "tile:")
- {
- int part, width, height, tilePosX, tilePosY, tileWidth, tileHeight;
- if (tokens.count() < 8 ||
- !getTokenInteger(tokens[1], "part", part) ||
- !getTokenInteger(tokens[2], "width", width) ||
- !getTokenInteger(tokens[3], "height", height) ||
- !getTokenInteger(tokens[4], "tileposx", tilePosX) ||
- !getTokenInteger(tokens[5], "tileposy", tilePosY) ||
- !getTokenInteger(tokens[6], "tilewidth", tileWidth) ||
- !getTokenInteger(tokens[7], "tileheight", tileHeight))
- assert(false);
-
- assert(firstLine.size() < static_cast<std::string::size_type>(length));
- peer->_tileCache->saveTile(part, width, height, tilePosX, tilePosY, tileWidth, tileHeight, buffer + firstLine.size() + 1, length - firstLine.size() - 1);
- }
- else if (tokens[0] == "status:")
- {
- peer->_tileCache->saveTextFile(std::string(buffer, length), "status.txt");
- }
- else if (tokens[0] == "commandvalues:")
- {
- std::string stringMsg(buffer, length);
- std::string stringJSON = stringMsg.substr(stringMsg.find_first_of("{"));
- Parser parser;
- Var result = parser.parse(stringJSON);
- Object::Ptr object = result.extract<Object::Ptr>();
- std::string commandName = object->get("commandName").toString();
- if (commandName.find(".uno:CharFontName") != std::string::npos ||
- commandName.find(".uno:StyleApply") != std::string::npos)
- {
- // other commands should not be cached
- peer->_tileCache->saveTextFile(std::string(buffer, length), "cmdValues" + commandName + ".txt");
- }
- }
- else if (tokens[0] == "partpagerectangles:")
- {
- peer->_tileCache->saveTextFile(std::string(buffer, length), "partpagerectangles.txt");
- }
- else if (tokens[0] == "invalidatecursor:")
- {
- peer->_tileCache->setEditing(true);
- }
- else if (tokens[0] == "invalidatetiles:")
- {
- // FIXME temporarily, set the editing on the 1st invalidate, TODO extend
- // the protocol so that the client can set the editing or view only.
- peer->_tileCache->setEditing(true);
-
- assert(firstLine.size() == static_cast<std::string::size_type>(length));
- peer->_tileCache->invalidateTiles(firstLine);
- }
- else if (tokens[0] == "renderfont:")
- {
- std::string font;
- if (tokens.count() < 2 ||
- !getTokenString(tokens[1], "font", font))
- assert(false);
-
- assert(firstLine.size() < static_cast<std::string::size_type>(length));
- peer->_tileCache->saveRendering(font, "font", buffer + firstLine.size() + 1, length - firstLine.size() - 1);
- }
- }
-
- forwardToPeer(buffer, length);
- return true;
- }
-
- if (tokens[0] == "child")
- {
- if (_kind != Kind::ToPrisoner)
- {
- sendTextFrame("error: cmd=child kind=invalid");
- return false;
- }
- if (!_peer.expired())
- {
- sendTextFrame("error: cmd=child kind=invalid");
- return false;
- }
- if (tokens.count() != 3)
- {
- sendTextFrame("error: cmd=child kind=syntax");
- return false;
- }
-
- UInt64 childId = std::stoull(tokens[1]);
- Process::PID pidChild = std::stoull(tokens[2]);
-
- std::unique_lock<std::mutex> lock(_availableChildSessionMutex);
- _availableChildSessions.insert(shared_from_this());
- std::cout << Util::logPrefix() << "Inserted " << this << " id=" << childId << " into _availableChildSessions, size=" << _availableChildSessions.size() << std::endl;
- _childId = childId;
- _pidChild = pidChild;
- lock.unlock();
- _availableChildSessionCV.notify_one();
-
- // log first lokit child pid information
- if ( LOOLWSD::doTest )
- {
- Poco::FileOutputStream filePID(LOOLWSD::LOKIT_PIDLOG);
- if (filePID.good())
- filePID << pidChild;
- }
- }
- else if (_kind == Kind::ToPrisoner)
- {
- // Message from child process to be forwarded to client.
-
- // I think we should never get here
- assert(false);
- }
- else if (tokens[0] == "load")
- {
- if (_docURL != "")
- {
- sendTextFrame("error: cmd=load kind=docalreadyloaded");
- return false;
- }
- return loadDocument(buffer, length, tokens);
- }
- else if (tokens[0] != "canceltiles" &&
- tokens[0] != "clientzoom" &&
- tokens[0] != "commandvalues" &&
- tokens[0] != "downloadas" &&
- tokens[0] != "getchildid" &&
- tokens[0] != "gettextselection" &&
- tokens[0] != "paste" &&
- tokens[0] != "insertfile" &&
- tokens[0] != "invalidatetiles" &&
- tokens[0] != "key" &&
- tokens[0] != "mouse" &&
- tokens[0] != "partpagerectangles" &&
- tokens[0] != "renderfont" &&
- tokens[0] != "requestloksession" &&
- tokens[0] != "resetselection" &&
- tokens[0] != "saveas" &&
- tokens[0] != "selectgraphic" &&
- tokens[0] != "selecttext" &&
- tokens[0] != "setclientpart" &&
- tokens[0] != "setpage" &&
- tokens[0] != "status" &&
- tokens[0] != "tile" &&
- tokens[0] != "uno")
- {
- sendTextFrame("error: cmd=" + tokens[0] + " kind=unknown");
- return false;
- }
- else if (_docURL == "")
- {
- sendTextFrame("error: cmd=" + tokens[0] + " kind=nodocloaded");
- return false;
- }
- else if (tokens[0] == "canceltiles")
- {
- if (!_peer.expired())
- forwardToPeer(buffer, length);
- }
- else if (tokens[0] == "commandvalues")
- {
- return getCommandValues(buffer, length, tokens);
- }
- else if (tokens[0] == "partpagerectangles")
- {
- return getPartPageRectangles(buffer, length);
- }
- else if (tokens[0] == "invalidatetiles")
- {
- return invalidateTiles(buffer, length, tokens);
- }
- else if (tokens[0] == "renderfont")
- {
- sendFontRendering(buffer, length, tokens);
- }
- else if (tokens[0] == "status")
- {
- return getStatus(buffer, length);
- }
- else if (tokens[0] == "tile")
- {
- sendTile(buffer, length, tokens);
- }
- else
- {
- // All other commands are such that they always require a
- // LibreOfficeKitDocument session, i.e. need to be handled in
- // a child process.
-
- if (_peer.expired())
- dispatchChild();
- if (tokens[0] != "requestloksession")
- {
- forwardToPeer(buffer, length);
- }
-
- if ((tokens.count() > 1 && tokens[0] == "uno" && tokens[1] == ".uno:Save"))
- {
- _tileCache->documentSaved();
- }
- }
- return true;
-}
-
-bool MasterProcessSession::haveSeparateProcess()
-{
- return _childId != 0;
-}
-
-Path MasterProcessSession::getJailPath(UInt64 childId)
-{
- return Path::forDirectory(LOOLWSD::childRoot + Path::separator() + std::to_string(childId));
-}
-
-bool MasterProcessSession::invalidateTiles(const char* /*buffer*/, int /*length*/, StringTokenizer& tokens)
-{
- int part, tilePosX, tilePosY, tileWidth, tileHeight;
-
- if (tokens.count() != 6 ||
- !getTokenInteger(tokens[1], "part", part) ||
- !getTokenInteger(tokens[2], "tileposx", tilePosX) ||
- !getTokenInteger(tokens[3], "tileposy", tilePosY) ||
- !getTokenInteger(tokens[4], "tilewidth", tileWidth) ||
- !getTokenInteger(tokens[5], "tileheight", tileHeight))
- {
- sendTextFrame("error: cmd=invalidatetiles kind=syntax");
- return false;
- }
-
- // FIXME temporarily, set the editing on the 1st invalidate, TODO extend
- // the protocol so that the client can set the editing or view only.
- _tileCache->setEditing(true);
-
- _tileCache->invalidateTiles(_curPart, tilePosX, tilePosY, tileWidth, tileHeight);
- return true;
-}
-
-bool MasterProcessSession::loadDocument(const char* /*buffer*/, int /*length*/, StringTokenizer& tokens)
-{
- if (tokens.count() < 2)
- {
- sendTextFrame("error: cmd=load kind=syntax");
- return false;
- }
-
- std::string timestamp;
- parseDocOptions(tokens, _loadPart, timestamp);
-
- try
- {
- URI aUri(_docURL);
- }
- catch(Poco::SyntaxException&)
- {
- sendTextFrame("error: cmd=load kind=uriinvalid");
- return false;
- }
-
- _tileCache.reset(new TileCache(_docURL, timestamp));
-
- return true;
-}
-
-bool MasterProcessSession::getStatus(const char *buffer, int length)
-{
- std::string status;
-
- status = _tileCache->getTextFile("status.txt");
- if (status.size() > 0)
- {
- sendTextFrame(status);
- return true;
- }
-
- if (_peer.expired())
- dispatchChild();
- forwardToPeer(buffer, length);
- return true;
-}
-
-bool MasterProcessSession::getCommandValues(const char *buffer, int length, StringTokenizer& tokens)
-{
- std::string command;
- if (tokens.count() != 2 || !getTokenString(tokens[1], "command", command))
- {
- sendTextFrame("error: cmd=commandvalues kind=syntax");
- return false;
- }
-
- std::string cmdValues = _tileCache->getTextFile("cmdValues" + command + ".txt");
- if (cmdValues.size() > 0)
- {
- sendTextFrame(cmdValues);
- return true;
- }
-
- if (_peer.expired())
- dispatchChild();
- forwardToPeer(buffer, length);
- return true;
-}
-
-bool MasterProcessSession::getPartPageRectangles(const char *buffer, int length)
-{
- std::string partPageRectangles = _tileCache->getTextFile("partpagerectangles.txt");
- if (partPageRectangles.size() > 0)
- {
- sendTextFrame(partPageRectangles);
- return true;
- }
-
- if (_peer.expired())
- dispatchChild();
- forwardToPeer(buffer, length);
- return true;
-}
-
-std::string MasterProcessSession::getSaveAs()
-{
- return _saveAsQueue.get();
-}
-
-void MasterProcessSession::sendFontRendering(const char *buffer, int length, StringTokenizer& tokens)
-{
- std::string font;
-
- if (tokens.count() < 2 ||
- !getTokenString(tokens[1], "font", font))
- {
- sendTextFrame("error: cmd=renderfont kind=syntax");
- return;
- }
-
- std::string response = "renderfont: " + Poco::cat(std::string(" "), tokens.begin() + 1, tokens.end()) + "\n";
-
- std::vector<char> output;
- output.resize(response.size());
- std::memcpy(output.data(), response.data(), response.size());
-
- std::unique_ptr<std::fstream> cachedRendering = _tileCache->lookupRendering(font, "font");
- if (cachedRendering && cachedRendering->is_open())
- {
- cachedRendering->seekg(0, std::ios_base::end);
- size_t pos = output.size();
- std::streamsize size = cachedRendering->tellg();
- output.resize(pos + size);
- cachedRendering->seekg(0, std::ios_base::beg);
- cachedRendering->read(output.data() + pos, size);
- cachedRendering->close();
-
- sendBinaryFrame(output.data(), output.size());
- return;
- }
-
- if (_peer.expired())
- dispatchChild();
- forwardToPeer(buffer, length);
-}
-
-void MasterProcessSession::sendTile(const char *buffer, int length, StringTokenizer& tokens)
-{
- int part, width, height, tilePosX, tilePosY, tileWidth, tileHeight;
-
- if (tokens.count() < 8 ||
- !getTokenInteger(tokens[1], "part", part) ||
- !getTokenInteger(tokens[2], "width", width) ||
- !getTokenInteger(tokens[3], "height", height) ||
- !getTokenInteger(tokens[4], "tileposx", tilePosX) ||
- !getTokenInteger(tokens[5], "tileposy", tilePosY) ||
- !getTokenInteger(tokens[6], "tilewidth", tileWidth) ||
- !getTokenInteger(tokens[7], "tileheight", tileHeight))
- {
- sendTextFrame("error: cmd=tile kind=syntax");
- return;
- }
-
- if (part < 0 ||
- width <= 0 ||
- height <= 0 ||
- tilePosX < 0 ||
- tilePosY < 0 ||
- tileWidth <= 0 ||
- tileHeight <= 0)
- {
- sendTextFrame("error: cmd=tile kind=invalid");
- return;
- }
-
- std::string response = "tile: " + Poco::cat(std::string(" "), tokens.begin() + 1, tokens.end()) + "\n";
-
- std::vector<char> output;
- output.reserve(4 * width * height);
- output.resize(response.size());
- std::memcpy(output.data(), response.data(), response.size());
-
- std::unique_ptr<std::fstream> cachedTile = _tileCache->lookupTile(part, width, height, tilePosX, tilePosY, tileWidth, tileHeight);
- if (cachedTile && cachedTile->is_open())
- {
- cachedTile->seekg(0, std::ios_base::end);
- size_t pos = output.size();
- std::streamsize size = cachedTile->tellg();
- output.resize(pos + size);
- cachedTile->seekg(0, std::ios_base::beg);
- cachedTile->read(output.data() + pos, size);
- cachedTile->close();
-
- sendBinaryFrame(output.data(), output.size());
-
- return;
- }
-
- if (_peer.expired())
- dispatchChild();
- forwardToPeer(buffer, length);
-}
-
-void MasterProcessSession::dispatchChild()
-{
- // Copy document into jail using the fixed name
-
- std::shared_ptr<MasterProcessSession> childSession;
- std::unique_lock<std::mutex> lock(_availableChildSessionMutex);
-
- std::cout << Util::logPrefix() << "_availableChildSessions size=" << _availableChildSessions.size() << std::endl;
-
- if (_availableChildSessions.size() == 0)
- {
- std::cout << Util::logPrefix() << "waiting for a child session to become available" << std::endl;
- _availableChildSessionCV.wait(lock, [] { return _availableChildSessions.size() > 0; });
- std::cout << Util::logPrefix() << "waiting done" << std::endl;
- }
-
- childSession = *(_availableChildSessions.begin());
- _availableChildSessions.erase(childSession);
- lock.unlock();
-
- if (_availableChildSessions.size() == 0 && !LOOLWSD::doTest)
- {
- LOOLWSD::_namedMutexLOOL.lock();
- std::cout << Util::logPrefix() << "No available child sessions, queue new child session" << std::endl;
- LOOLWSD::_sharedForkChild.begin()[0] = LOOLWSD::_numPreSpawnedChildren;
- LOOLWSD::_namedMutexLOOL.unlock();
- }
-
- // Assume a valid URI
- URI aUri(_docURL);
-
- if (aUri.isRelative())
- aUri = URI( URI("file://"), aUri.toString() );
-
- if (!aUri.empty() && aUri.getScheme() == "file")
- {
- std::string aJailDoc = jailDocumentURL.substr(1) + Path::separator() + std::to_string(childSession->_pidChild);
- Path aSrcFile(aUri.getPath());
- Path aDstFile(Path(getJailPath(childSession->_childId), aJailDoc), aSrcFile.getFileName());
- Path aDstPath(getJailPath(childSession->_childId), aJailDoc);
- Path aJailFile(aJailDoc, aSrcFile.getFileName());
-
- try
- {
- File(aDstPath).createDirectories();
- }
- catch (Exception& exc)
- {
- Application::instance().logger().error( Util::logPrefix() +
- "createDirectories(\"" + aDstPath.toString() + "\") failed: " + exc.displayText() );
-
- }
-
- // cleanup potential leftovers from the last time
- File aToCleanup(aDstFile);
- if (aToCleanup.exists())
- aToCleanup.remove();
-
-#ifdef __linux
- Application::instance().logger().information(Util::logPrefix() + "Linking " + aSrcFile.toString() + " to " + aDstFile.toString());
- if (link(aSrcFile.toString().c_str(), aDstFile.toString().c_str()) == -1)
- {
- // Failed
- Application::instance().logger().error( Util::logPrefix() +
- "link(\"" + aSrcFile.toString() + "\",\"" + aDstFile.toString() + "\") failed: " + strerror(errno) );
- }
-#endif
-
- try
- {
- //fallback
- if (!File(aDstFile).exists())
- {
- Application::instance().logger().information(Util::logPrefix() + "Copying " + aSrcFile.toString() + " to " + aDstFile.toString());
- File(aSrcFile).copyTo(aDstFile.toString());
- }
- }
- catch (Exception& exc)
- {
- Application::instance().logger().error( Util::logPrefix() +
- "copyTo(\"" + aSrcFile.toString() + "\",\"" + aDstFile.toString() + "\") failed: " + exc.displayText());
- }
- }
-
- _peer = childSession;
- childSession->_peer = shared_from_this();
-
- std::string loadRequest = "load" + (_loadPart >= 0 ? " part=" + std::to_string(_loadPart) : "") + " url=" + _docURL + (!_docOptions.empty() ? " options=" + _docOptions : "");
- forwardToPeer(loadRequest.c_str(), loadRequest.size());
-}
-
-void MasterProcessSession::forwardToPeer(const char *buffer, int length)
-{
- Application::instance().logger().information(Util::logPrefix() + _kindString + ",forwardToPeer," + getAbbreviatedMessage(buffer, length));
- auto peer = _peer.lock();
- if (!peer)
- return;
- peer->sendBinaryFrame(buffer, length);
-}
-
ChildProcessSession::ChildProcessSession(std::shared_ptr<WebSocket> ws, LibreOfficeKit *loKit, std::string childId) :
LOOLSession(ws, Kind::ToMaster),
_loKitDocument(NULL),
diff --git a/loolwsd/LOOLSession.hpp b/loolwsd/LOOLSession.hpp
index 5f81fed..8b7175c 100644
--- a/loolwsd/LOOLSession.hpp
+++ b/loolwsd/LOOLSession.hpp
@@ -106,75 +106,6 @@ inline std::basic_ostream<charT, traits> & operator <<(std::basic_ostream<charT,
}
}
-class MasterProcessSession final : public LOOLSession, public std::enable_shared_from_this<MasterProcessSession>
-{
-public:
- MasterProcessSession(std::shared_ptr<Poco::Net::WebSocket> ws, Kind kind);
- virtual ~MasterProcessSession();
-
- virtual bool handleInput(const char *buffer, int length) override;
-
- bool haveSeparateProcess();
-
- static Poco::Path getJailPath(Poco::UInt64 childId);
-
- static std::map<Poco::Process::PID, Poco::UInt64> _childProcesses;
-
- virtual bool getStatus(const char *buffer, int length);
-
- virtual bool getCommandValues(const char *buffer, int length, Poco::StringTokenizer& tokens);
-
- virtual bool getPartPageRectangles(const char *buffer, int length) override;
-
- /**
- * Return the URL of the saved-as document when it's ready. If called
- * before it's ready, the call blocks till then.
- */
- std::string getSaveAs();
-
- // Sessions to pre-spawned child processes that have connected but are not yet assigned a
- // document to work on.
- static std::set<std::shared_ptr<MasterProcessSession>> _availableChildSessions;
-
- protected:
- bool invalidateTiles(const char *buffer, int length, Poco::StringTokenizer& tokens);
-
- virtual bool loadDocument(const char *buffer, int length, Poco::StringTokenizer& tokens) override;
-
- virtual void sendTile(const char *buffer, int length, Poco::StringTokenizer& tokens);
-
- virtual void sendFontRendering(const char *buffer, int length, Poco::StringTokenizer& tokens);
-
- void dispatchChild();
- void forwardToPeer(const char *buffer, int length);
-
- // If _kind==ToPrisoner and the child process has started and completed its handshake with the
- // parent process: Points to the WebSocketSession for the child process handling the document in
- // question, if any.
-
- // In the session to the child process, points to the LOOLSession for the LOOL client. This will
- // obvious have to be rethought when we add collaboration and there can be several LOOL clients
- // per document being edited (i.e., per child process).
- std::weak_ptr<MasterProcessSession> _peer;
-
- static std::mutex _availableChildSessionMutex;
- static std::condition_variable _availableChildSessionCV;
-
- std::unique_ptr<TileCache> _tileCache;
-
-private:
- // The id of the child process
- Poco::UInt64 _childId;
- // The pid of the child process
- Poco::Process::PID _pidChild;
- static Poco::Random _rng;
- static std::mutex _rngMutex;
- int _curPart;
- int _loadPart;
- /// Kind::ToClient instances store URLs of completed 'save as' documents.
- MessageQueue _saveAsQueue;
-};
-
class ChildProcessSession final : public LOOLSession
{
public:
diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index a74d867..42aa0b4 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -105,6 +105,7 @@ DEALINGS IN THE SOFTWARE.
#include "LOOLProtocol.hpp"
#include "LOOLSession.hpp"
+#include "MasterProcessSession.hpp"
#include "LOOLWSD.hpp"
#include "MessageQueue.hpp"
#include "Util.hpp"
diff --git a/loolwsd/Makefile.am b/loolwsd/Makefile.am
index cfc3f41..3a81c34 100644
--- a/loolwsd/Makefile.am
+++ b/loolwsd/Makefile.am
@@ -7,7 +7,7 @@ dist_bin_SCRIPTS = loolwsd-systemplate-setup
AM_CPPFLAGS = -pthread
AM_LDFLAGS = -pthread
-loolwsd_SOURCES = LOOLWSD.cpp LOOLSession.cpp MessageQueue.cpp TileCache.cpp Util.cpp LOOLProtocol.cpp
+loolwsd_SOURCES = LOOLWSD.cpp LOOLSession.cpp MasterProcessSession.cpp MessageQueue.cpp TileCache.cpp Util.cpp LOOLProtocol.cpp
noinst_PROGRAMS = loadtest connect lokitclient
@@ -17,7 +17,7 @@ connect_SOURCES = Connect.cpp Util.cpp LOOLProtocol.cpp
lokitclient_SOURCES = LOKitClient.cpp Util.cpp
-noinst_HEADERS = LOKitHelper.hpp LOOLProtocol.hpp LOOLSession.hpp LOOLWSD.hpp LoadTest.hpp MessageQueue.hpp TileCache.hpp Util.hpp Png.hpp \
+noinst_HEADERS = LOKitHelper.hpp LOOLProtocol.hpp LOOLSession.hpp MasterProcessSession.hpp LOOLWSD.hpp LoadTest.hpp MessageQueue.hpp TileCache.hpp Util.hpp Png.hpp \
bundled/include/LibreOfficeKit/LibreOfficeKit.h bundled/include/LibreOfficeKit/LibreOfficeKitEnums.h \
bundled/include/LibreOfficeKit/LibreOfficeKitInit.h bundled/include/LibreOfficeKit/LibreOfficeKitTypes.h
diff --git a/loolwsd/MasterProcessSession.cpp b/loolwsd/MasterProcessSession.cpp
new file mode 100644
index 0000000..ac39c92
--- /dev/null
+++ b/loolwsd/MasterProcessSession.cpp
@@ -0,0 +1,640 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <iostream>
+
+#include <Poco/JSON/Object.h>
+#include <Poco/JSON/Parser.h>
+#include <Poco/Process.h>
+#include <Poco/Random.h>
+#include <Poco/Util/Application.h>
+#include <Poco/FileStream.h>
+#include <Poco/URI.h>
+#include <Poco/URIStreamOpener.h>
+
+#include "MasterProcessSession.hpp"
+#include "LOOLSession.hpp"
+#include "Util.hpp"
+#include "LOOLProtocol.hpp"
+#include "LOOLWSD.hpp"
+
+using namespace LOOLProtocol;
+using Poco::Dynamic::Var;
+using Poco::File;
+using Poco::IOException;
+using Poco::JSON::Object;
+using Poco::JSON::Parser;
+using Poco::Net::WebSocket;
+using Poco::Path;
+using Poco::Process;
+using Poco::ProcessHandle;
+using Poco::Random;
+using Poco::StringTokenizer;
+using Poco::Thread;
+using Poco::UInt64;
+using Poco::URI;
+using Poco::Util::Application;
+using Poco::Exception;
+using Poco::Net::SocketAddress;
+
+std::map<Process::PID, UInt64> MasterProcessSession::_childProcesses;
+
+std::set<std::shared_ptr<MasterProcessSession>> MasterProcessSession::_availableChildSessions;
+std::mutex MasterProcessSession::_availableChildSessionMutex;
+std::condition_variable MasterProcessSession::_availableChildSessionCV;
+Random MasterProcessSession::_rng;
+std::mutex MasterProcessSession::_rngMutex;
+
+MasterProcessSession::MasterProcessSession(std::shared_ptr<WebSocket> ws, Kind kind) :
+ LOOLSession(ws, kind),
+ _childId(0),
+ _pidChild(0),
+ _curPart(0),
+ _loadPart(-1)
+{
+ std::cout << Util::logPrefix() << "MasterProcessSession ctor this=" << this << " ws=" << _ws.get() << std::endl;
+}
+
+MasterProcessSession::~MasterProcessSession()
+{
+ std::cout << Util::logPrefix() << "MasterProcessSession dtor this=" << this << " _peer=" << _peer.lock().get() << std::endl;
+
+ auto peer = _peer.lock();
+ if (_kind == Kind::ToClient && peer)
+ {
+ Util::shutdownWebSocket(*(peer->_ws));
+ }
+}
+
+bool MasterProcessSession::handleInput(const char *buffer, int length)
+{
+ Application::instance().logger().information(Util::logPrefix() + _kindString + ",Input," + getAbbreviatedMessage(buffer, length));
+
+ std::string firstLine = getFirstLine(buffer, length);
+ StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+
+ if (haveSeparateProcess())
+ {
+ // Note that this handles both forwarding requests from the client to the child process, and
+ // forwarding replies from the child process to the client. Or does it?
+
+ // Snoop at some messages and manipulate tile cache information as needed
+ auto peer = _peer.lock();
+
+ if (_kind == Kind::ToPrisoner)
+ {
+ if (tokens[0] == "curpart:" &&
+ tokens.count() == 2 &&
+ getTokenInteger(tokens[1], "part", _curPart))
+ {
+ return true;
+ }
+
+ if (tokens.count() == 2 && tokens[0] == "saveas:")
+ {
+ std::string url;
+ if (!getTokenString(tokens[1], "url", url))
+ return true;
+
+ if (peer)
+ {
+ // Save as completed, inform the other (Kind::ToClient)
+ // MasterProcessSession about it.
+
+ const std::string filePrefix("file:///");
+ if (url.find(filePrefix) == 0)
+ {
+ // Rewrite file:// URLs, as they are visible to the outside world.
+ Path path(MasterProcessSession::getJailPath(_childId), url.substr(filePrefix.length()));
+ url = filePrefix + path.toString().substr(1);
+ }
+ peer->_saveAsQueue.put(url);
+ }
+
+ return true;
+ }
+ }
+
+ if (_kind == Kind::ToPrisoner && peer && peer->_tileCache)
+ {
+ if (tokens[0] == "tile:")
+ {
+ int part, width, height, tilePosX, tilePosY, tileWidth, tileHeight;
+ if (tokens.count() < 8 ||
+ !getTokenInteger(tokens[1], "part", part) ||
+ !getTokenInteger(tokens[2], "width", width) ||
+ !getTokenInteger(tokens[3], "height", height) ||
+ !getTokenInteger(tokens[4], "tileposx", tilePosX) ||
+ !getTokenInteger(tokens[5], "tileposy", tilePosY) ||
+ !getTokenInteger(tokens[6], "tilewidth", tileWidth) ||
+ !getTokenInteger(tokens[7], "tileheight", tileHeight))
+ assert(false);
+
+ assert(firstLine.size() < static_cast<std::string::size_type>(length));
+ peer->_tileCache->saveTile(part, width, height, tilePosX, tilePosY, tileWidth, tileHeight, buffer + firstLine.size() + 1, length - firstLine.size() - 1);
+ }
+ else if (tokens[0] == "status:")
+ {
+ peer->_tileCache->saveTextFile(std::string(buffer, length), "status.txt");
+ }
+ else if (tokens[0] == "commandvalues:")
+ {
+ std::string stringMsg(buffer, length);
+ std::string stringJSON = stringMsg.substr(stringMsg.find_first_of("{"));
+ Parser parser;
+ Var result = parser.parse(stringJSON);
+ Object::Ptr object = result.extract<Object::Ptr>();
+ std::string commandName = object->get("commandName").toString();
+ if (commandName.find(".uno:CharFontName") != std::string::npos ||
+ commandName.find(".uno:StyleApply") != std::string::npos)
+ {
+ // other commands should not be cached
+ peer->_tileCache->saveTextFile(std::string(buffer, length), "cmdValues" + commandName + ".txt");
+ }
+ }
+ else if (tokens[0] == "partpagerectangles:")
+ {
+ peer->_tileCache->saveTextFile(std::string(buffer, length), "partpagerectangles.txt");
+ }
+ else if (tokens[0] == "invalidatecursor:")
+ {
+ peer->_tileCache->setEditing(true);
+ }
+ else if (tokens[0] == "invalidatetiles:")
+ {
+ // FIXME temporarily, set the editing on the 1st invalidate, TODO extend
+ // the protocol so that the client can set the editing or view only.
+ peer->_tileCache->setEditing(true);
+
+ assert(firstLine.size() == static_cast<std::string::size_type>(length));
+ peer->_tileCache->invalidateTiles(firstLine);
+ }
+ else if (tokens[0] == "renderfont:")
+ {
+ std::string font;
+ if (tokens.count() < 2 ||
+ !getTokenString(tokens[1], "font", font))
+ assert(false);
+
+ assert(firstLine.size() < static_cast<std::string::size_type>(length));
+ peer->_tileCache->saveRendering(font, "font", buffer + firstLine.size() + 1, length - firstLine.size() - 1);
+ }
+ }
+
+ forwardToPeer(buffer, length);
+ return true;
+ }
+
+ if (tokens[0] == "child")
+ {
+ if (_kind != Kind::ToPrisoner)
+ {
+ sendTextFrame("error: cmd=child kind=invalid");
+ return false;
+ }
+ if (!_peer.expired())
+ {
+ sendTextFrame("error: cmd=child kind=invalid");
+ return false;
+ }
+ if (tokens.count() != 3)
+ {
+ sendTextFrame("error: cmd=child kind=syntax");
+ return false;
+ }
+
+ UInt64 childId = std::stoull(tokens[1]);
+ Process::PID pidChild = std::stoull(tokens[2]);
+
+ std::unique_lock<std::mutex> lock(_availableChildSessionMutex);
+ _availableChildSessions.insert(shared_from_this());
+ std::cout << Util::logPrefix() << "Inserted " << this << " id=" << childId << " into _availableChildSessions, size=" << _availableChildSessions.size() << std::endl;
+ _childId = childId;
+ _pidChild = pidChild;
+ lock.unlock();
+ _availableChildSessionCV.notify_one();
+
+ // log first lokit child pid information
+ if ( LOOLWSD::doTest )
+ {
+ Poco::FileOutputStream filePID(LOOLWSD::LOKIT_PIDLOG);
+ if (filePID.good())
+ filePID << pidChild;
+ }
+ }
+ else if (_kind == Kind::ToPrisoner)
+ {
+ // Message from child process to be forwarded to client.
+
+ // I think we should never get here
+ assert(false);
+ }
+ else if (tokens[0] == "load")
+ {
+ if (_docURL != "")
+ {
+ sendTextFrame("error: cmd=load kind=docalreadyloaded");
+ return false;
+ }
+ return loadDocument(buffer, length, tokens);
+ }
+ else if (tokens[0] != "canceltiles" &&
+ tokens[0] != "clientzoom" &&
+ tokens[0] != "commandvalues" &&
+ tokens[0] != "downloadas" &&
+ tokens[0] != "getchildid" &&
+ tokens[0] != "gettextselection" &&
+ tokens[0] != "paste" &&
+ tokens[0] != "insertfile" &&
+ tokens[0] != "invalidatetiles" &&
+ tokens[0] != "key" &&
+ tokens[0] != "mouse" &&
+ tokens[0] != "partpagerectangles" &&
+ tokens[0] != "renderfont" &&
+ tokens[0] != "requestloksession" &&
+ tokens[0] != "resetselection" &&
+ tokens[0] != "saveas" &&
+ tokens[0] != "selectgraphic" &&
+ tokens[0] != "selecttext" &&
+ tokens[0] != "setclientpart" &&
+ tokens[0] != "setpage" &&
+ tokens[0] != "status" &&
+ tokens[0] != "tile" &&
+ tokens[0] != "uno")
+ {
+ sendTextFrame("error: cmd=" + tokens[0] + " kind=unknown");
+ return false;
+ }
+ else if (_docURL == "")
+ {
+ sendTextFrame("error: cmd=" + tokens[0] + " kind=nodocloaded");
+ return false;
+ }
+ else if (tokens[0] == "canceltiles")
+ {
+ if (!_peer.expired())
+ forwardToPeer(buffer, length);
+ }
+ else if (tokens[0] == "commandvalues")
+ {
+ return getCommandValues(buffer, length, tokens);
+ }
+ else if (tokens[0] == "partpagerectangles")
+ {
+ return getPartPageRectangles(buffer, length);
+ }
+ else if (tokens[0] == "invalidatetiles")
+ {
+ return invalidateTiles(buffer, length, tokens);
+ }
+ else if (tokens[0] == "renderfont")
+ {
+ sendFontRendering(buffer, length, tokens);
+ }
+ else if (tokens[0] == "status")
+ {
+ return getStatus(buffer, length);
+ }
+ else if (tokens[0] == "tile")
+ {
+ sendTile(buffer, length, tokens);
+ }
+ else
+ {
+ // All other commands are such that they always require a
+ // LibreOfficeKitDocument session, i.e. need to be handled in
+ // a child process.
+
+ if (_peer.expired())
+ dispatchChild();
+ if (tokens[0] != "requestloksession")
+ {
+ forwardToPeer(buffer, length);
+ }
+
+ if ((tokens.count() > 1 && tokens[0] == "uno" && tokens[1] == ".uno:Save"))
+ {
+ _tileCache->documentSaved();
+ }
+ }
+ return true;
+}
+
+bool MasterProcessSession::haveSeparateProcess()
+{
+ return _childId != 0;
+}
+
+Path MasterProcessSession::getJailPath(UInt64 childId)
+{
+ return Path::forDirectory(LOOLWSD::childRoot + Path::separator() + std::to_string(childId));
+}
+
+bool MasterProcessSession::invalidateTiles(const char* /*buffer*/, int /*length*/, StringTokenizer& tokens)
+{
+ int part, tilePosX, tilePosY, tileWidth, tileHeight;
+
+ if (tokens.count() != 6 ||
+ !getTokenInteger(tokens[1], "part", part) ||
+ !getTokenInteger(tokens[2], "tileposx", tilePosX) ||
+ !getTokenInteger(tokens[3], "tileposy", tilePosY) ||
+ !getTokenInteger(tokens[4], "tilewidth", tileWidth) ||
+ !getTokenInteger(tokens[5], "tileheight", tileHeight))
+ {
+ sendTextFrame("error: cmd=invalidatetiles kind=syntax");
+ return false;
+ }
+
+ // FIXME temporarily, set the editing on the 1st invalidate, TODO extend
+ // the protocol so that the client can set the editing or view only.
+ _tileCache->setEditing(true);
+
+ _tileCache->invalidateTiles(_curPart, tilePosX, tilePosY, tileWidth, tileHeight);
+ return true;
+}
+
+bool MasterProcessSession::loadDocument(const char* /*buffer*/, int /*length*/, StringTokenizer& tokens)
+{
+ if (tokens.count() < 2)
+ {
+ sendTextFrame("error: cmd=load kind=syntax");
+ return false;
+ }
+
+ std::string timestamp;
+ parseDocOptions(tokens, _loadPart, timestamp);
+
+ try
+ {
+ URI aUri(_docURL);
+ }
+ catch(Poco::SyntaxException&)
+ {
+ sendTextFrame("error: cmd=load kind=uriinvalid");
+ return false;
+ }
+
+ _tileCache.reset(new TileCache(_docURL, timestamp));
+
+ return true;
+}
+
+bool MasterProcessSession::getStatus(const char *buffer, int length)
+{
+ std::string status;
+
+ status = _tileCache->getTextFile("status.txt");
+ if (status.size() > 0)
+ {
+ sendTextFrame(status);
+ return true;
+ }
+
+ if (_peer.expired())
+ dispatchChild();
+ forwardToPeer(buffer, length);
+ return true;
+}
+
+bool MasterProcessSession::getCommandValues(const char *buffer, int length, StringTokenizer& tokens)
+{
+ std::string command;
+ if (tokens.count() != 2 || !getTokenString(tokens[1], "command", command))
+ {
+ sendTextFrame("error: cmd=commandvalues kind=syntax");
+ return false;
+ }
+
+ std::string cmdValues = _tileCache->getTextFile("cmdValues" + command + ".txt");
+ if (cmdValues.size() > 0)
+ {
+ sendTextFrame(cmdValues);
+ return true;
+ }
+
+ if (_peer.expired())
+ dispatchChild();
+ forwardToPeer(buffer, length);
+ return true;
+}
+
+bool MasterProcessSession::getPartPageRectangles(const char *buffer, int length)
+{
+ std::string partPageRectangles = _tileCache->getTextFile("partpagerectangles.txt");
+ if (partPageRectangles.size() > 0)
+ {
+ sendTextFrame(partPageRectangles);
+ return true;
+ }
+
+ if (_peer.expired())
+ dispatchChild();
+ forwardToPeer(buffer, length);
+ return true;
+}
+
+std::string MasterProcessSession::getSaveAs()
+{
+ return _saveAsQueue.get();
+}
+
+void MasterProcessSession::sendFontRendering(const char *buffer, int length, StringTokenizer& tokens)
+{
+ std::string font;
+
+ if (tokens.count() < 2 ||
+ !getTokenString(tokens[1], "font", font))
+ {
+ sendTextFrame("error: cmd=renderfont kind=syntax");
+ return;
+ }
+
+ std::string response = "renderfont: " + Poco::cat(std::string(" "), tokens.begin() + 1, tokens.end()) + "\n";
+
+ std::vector<char> output;
+ output.resize(response.size());
+ std::memcpy(output.data(), response.data(), response.size());
+
+ std::unique_ptr<std::fstream> cachedRendering = _tileCache->lookupRendering(font, "font");
+ if (cachedRendering && cachedRendering->is_open())
+ {
+ cachedRendering->seekg(0, std::ios_base::end);
+ size_t pos = output.size();
+ std::streamsize size = cachedRendering->tellg();
+ output.resize(pos + size);
+ cachedRendering->seekg(0, std::ios_base::beg);
+ cachedRendering->read(output.data() + pos, size);
+ cachedRendering->close();
+
+ sendBinaryFrame(output.data(), output.size());
+ return;
+ }
+
+ if (_peer.expired())
+ dispatchChild();
+ forwardToPeer(buffer, length);
+}
+
+void MasterProcessSession::sendTile(const char *buffer, int length, StringTokenizer& tokens)
+{
+ int part, width, height, tilePosX, tilePosY, tileWidth, tileHeight;
+
+ if (tokens.count() < 8 ||
+ !getTokenInteger(tokens[1], "part", part) ||
+ !getTokenInteger(tokens[2], "width", width) ||
+ !getTokenInteger(tokens[3], "height", height) ||
+ !getTokenInteger(tokens[4], "tileposx", tilePosX) ||
+ !getTokenInteger(tokens[5], "tileposy", tilePosY) ||
+ !getTokenInteger(tokens[6], "tilewidth", tileWidth) ||
+ !getTokenInteger(tokens[7], "tileheight", tileHeight))
+ {
+ sendTextFrame("error: cmd=tile kind=syntax");
+ return;
+ }
+
+ if (part < 0 ||
+ width <= 0 ||
+ height <= 0 ||
+ tilePosX < 0 ||
+ tilePosY < 0 ||
+ tileWidth <= 0 ||
+ tileHeight <= 0)
+ {
+ sendTextFrame("error: cmd=tile kind=invalid");
+ return;
+ }
+
+ std::string response = "tile: " + Poco::cat(std::string(" "), tokens.begin() + 1, tokens.end()) + "\n";
+
+ std::vector<char> output;
+ output.reserve(4 * width * height);
+ output.resize(response.size());
+ std::memcpy(output.data(), response.data(), response.size());
+
+ std::unique_ptr<std::fstream> cachedTile = _tileCache->lookupTile(part, width, height, tilePosX, tilePosY, tileWidth, tileHeight);
+ if (cachedTile && cachedTile->is_open())
+ {
+ cachedTile->seekg(0, std::ios_base::end);
+ size_t pos = output.size();
+ std::streamsize size = cachedTile->tellg();
+ output.resize(pos + size);
+ cachedTile->seekg(0, std::ios_base::beg);
+ cachedTile->read(output.data() + pos, size);
+ cachedTile->close();
+
+ sendBinaryFrame(output.data(), output.size());
+
+ return;
+ }
+
+ if (_peer.expired())
+ dispatchChild();
+ forwardToPeer(buffer, length);
+}
+
+void MasterProcessSession::dispatchChild()
+{
+ // Copy document into jail using the fixed name
+
+ std::shared_ptr<MasterProcessSession> childSession;
+ std::unique_lock<std::mutex> lock(_availableChildSessionMutex);
+
+ std::cout << Util::logPrefix() << "_availableChildSessions size=" << _availableChildSessions.size() << std::endl;
+
+ if (_availableChildSessions.size() == 0)
+ {
+ std::cout << Util::logPrefix() << "waiting for a child session to become available" << std::endl;
+ _availableChildSessionCV.wait(lock, [] { return _availableChildSessions.size() > 0; });
+ std::cout << Util::logPrefix() << "waiting done" << std::endl;
+ }
+
+ childSession = *(_availableChildSessions.begin());
+ _availableChildSessions.erase(childSession);
+ lock.unlock();
+
+ if (_availableChildSessions.size() == 0 && !LOOLWSD::doTest)
+ {
+ LOOLWSD::_namedMutexLOOL.lock();
+ std::cout << Util::logPrefix() << "No available child sessions, queue new child session" << std::endl;
+ LOOLWSD::_sharedForkChild.begin()[0] = LOOLWSD::_numPreSpawnedChildren;
+ LOOLWSD::_namedMutexLOOL.unlock();
+ }
+
+ // Assume a valid URI
+ URI aUri(_docURL);
+
+ if (aUri.isRelative())
+ aUri = URI( URI("file://"), aUri.toString() );
+
+ if (!aUri.empty() && aUri.getScheme() == "file")
+ {
+ std::string aJailDoc = jailDocumentURL.substr(1) + Path::separator() + std::to_string(childSession->_pidChild);
+ Path aSrcFile(aUri.getPath());
+ Path aDstFile(Path(getJailPath(childSession->_childId), aJailDoc), aSrcFile.getFileName());
+ Path aDstPath(getJailPath(childSession->_childId), aJailDoc);
+ Path aJailFile(aJailDoc, aSrcFile.getFileName());
+
+ try
+ {
+ File(aDstPath).createDirectories();
+ }
+ catch (Exception& exc)
+ {
+ Application::instance().logger().error( Util::logPrefix() +
+ "createDirectories(\"" + aDstPath.toString() + "\") failed: " + exc.displayText() );
+
+ }
+
+ // cleanup potential leftovers from the last time
+ File aToCleanup(aDstFile);
+ if (aToCleanup.exists())
+ aToCleanup.remove();
+
+#ifdef __linux
+ Application::instance().logger().information(Util::logPrefix() + "Linking " + aSrcFile.toString() + " to " + aDstFile.toString());
+ if (link(aSrcFile.toString().c_str(), aDstFile.toString().c_str()) == -1)
+ {
+ // Failed
+ Application::instance().logger().error( Util::logPrefix() +
+ "link(\"" + aSrcFile.toString() + "\",\"" + aDstFile.toString() + "\") failed: " + strerror(errno) );
+ }
+#endif
+
+ try
+ {
+ //fallback
+ if (!File(aDstFile).exists())
+ {
+ Application::instance().logger().information(Util::logPrefix() + "Copying " + aSrcFile.toString() + " to " + aDstFile.toString());
+ File(aSrcFile).copyTo(aDstFile.toString());
+ }
+ }
+ catch (Exception& exc)
+ {
+ Application::instance().logger().error( Util::logPrefix() +
+ "copyTo(\"" + aSrcFile.toString() + "\",\"" + aDstFile.toString() + "\") failed: " + exc.displayText());
+ }
+ }
+
+ _peer = childSession;
+ childSession->_peer = shared_from_this();
+
+ std::string loadRequest = "load" + (_loadPart >= 0 ? " part=" + std::to_string(_loadPart) : "") + " url=" + _docURL + (!_docOptions.empty() ? " options=" + _docOptions : "");
+ forwardToPeer(loadRequest.c_str(), loadRequest.size());
+}
+
+void MasterProcessSession::forwardToPeer(const char *buffer, int length)
+{
+ Application::instance().logger().information(Util::logPrefix() + _kindString + ",forwardToPeer," + getAbbreviatedMessage(buffer, length));
+ auto peer = _peer.lock();
+ if (!peer)
+ return;
+ peer->sendBinaryFrame(buffer, length);
+}
+
diff --git a/loolwsd/MasterProcessSession.hpp b/loolwsd/MasterProcessSession.hpp
new file mode 100644
index 0000000..8161144
--- /dev/null
+++ b/loolwsd/MasterProcessSession.hpp
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#ifndef INCLUDED_MASTERPROCESSSESSION_HPP
+#define INCLUDED_MASTERPROCESSSESSION_HPP
+
+
+#include <Poco/Random.h>
+
+#include "LOOLSession.hpp"
+#include "TileCache.hpp"
+
+class MasterProcessSession final : public LOOLSession, public std::enable_shared_from_this<MasterProcessSession>
+{
+public:
+ MasterProcessSession(std::shared_ptr<Poco::Net::WebSocket> ws, Kind kind);
+ virtual ~MasterProcessSession();
+
+ virtual bool handleInput(const char *buffer, int length) override;
+
+ bool haveSeparateProcess();
+
+ static Poco::Path getJailPath(Poco::UInt64 childId);
+
+ static std::map<Poco::Process::PID, Poco::UInt64> _childProcesses;
+
+ virtual bool getStatus(const char *buffer, int length);
+
+ virtual bool getCommandValues(const char *buffer, int length, Poco::StringTokenizer& tokens);
+
+ virtual bool getPartPageRectangles(const char *buffer, int length) override;
+
+ /**
+ * Return the URL of the saved-as document when it's ready. If called
+ * before it's ready, the call blocks till then.
+ */
+ std::string getSaveAs();
+
+ // Sessions to pre-spawned child processes that have connected but are not yet assigned a
+ // document to work on.
+ static std::set<std::shared_ptr<MasterProcessSession>> _availableChildSessions;
+
+ protected:
+ bool invalidateTiles(const char *buffer, int length, Poco::StringTokenizer& tokens);
+
+ virtual bool loadDocument(const char *buffer, int length, Poco::StringTokenizer& tokens) override;
+
+ virtual void sendTile(const char *buffer, int length, Poco::StringTokenizer& tokens);
+
+ virtual void sendFontRendering(const char *buffer, int length, Poco::StringTokenizer& tokens);
+
+ void dispatchChild();
+ void forwardToPeer(const char *buffer, int length);
+
+ // If _kind==ToPrisoner and the child process has started and completed its handshake with the
+ // parent process: Points to the WebSocketSession for the child process handling the document in
+ // question, if any.
+
+ // In the session to the child process, points to the LOOLSession for the LOOL client. This will
+ // obvious have to be rethought when we add collaboration and there can be several LOOL clients
+ // per document being edited (i.e., per child process).
+ std::weak_ptr<MasterProcessSession> _peer;
+
+ static std::mutex _availableChildSessionMutex;
+ static std::condition_variable _availableChildSessionCV;
+
+ std::unique_ptr<TileCache> _tileCache;
+
+private:
+ // The id of the child process
+ Poco::UInt64 _childId;
+ // The pid of the child process
+ Poco::Process::PID _pidChild;
+ static Poco::Random _rng;
+ static std::mutex _rngMutex;
+ int _curPart;
+ int _loadPart;
+ /// Kind::ToClient instances store URLs of completed 'save as' documents.
+ MessageQueue _saveAsQueue;
+};
+
+#endif
+
More information about the Libreoffice-commits
mailing list