[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