loolwsd/LOOLSession.cpp | 64 ++++++++++++++++++++++++++++++------- loolwsd/LOOLSession.hpp | 6 ++- loolwsd/TileCache.cpp | 53 ++++++++++++++++++++++++++++++ loolwsd/TileCache.hpp | 6 +++ loolwsd/protocol.txt | 83 +++++++++++++++++++++++++++--------------------- 5 files changed, 164 insertions(+), 48 deletions(-)
New commits: commit bf985624f1cdf5f6299c2b64e1805edb79f719a1 Author: Tor Lillqvist <[email protected]> Date: Thu May 28 18:39:05 2015 +0300 Handle LOK_CALLBACK_INVALIDATE_TILES in the server too Remove any intersecting cached tiles. It is the parent process that handles the tile cache, so it must look for invalidatetiles: messages, too, before passing them on to the client. To know for which part we should remove tiles, add an "ephemeral" curpart: message that the child process sends to the parent process before the invalidatetils: message. diff --git a/loolwsd/LOOLSession.cpp b/loolwsd/LOOLSession.cpp index 9bbca5b..a98a577 100644 --- a/loolwsd/LOOLSession.cpp +++ b/loolwsd/LOOLSession.cpp @@ -106,7 +106,8 @@ std::mutex MasterProcessSession::_rngMutex; MasterProcessSession::MasterProcessSession(std::shared_ptr<WebSocket> ws, Kind kind) : LOOLSession(ws, kind), - _childId(0) + _childId(0), + _curPart(0) { std::cout << Util::logPrefix() << "MasterProcessSession ctor this=" << this << " ws=" << _ws.get() << std::endl; } @@ -134,8 +135,19 @@ bool MasterProcessSession::handleInput(char *buffer, int length) // 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 tile: and status: messages and (re-)cache them + // 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 (_kind == Kind::ToPrisoner && peer && peer->_tileCache) { if (tokens[0] == "tile:") @@ -159,6 +171,11 @@ bool MasterProcessSession::handleInput(char *buffer, int length) assert(firstLine.size() == static_cast<std::string::size_type>(length)); peer->_tileCache->saveStatus(firstLine); } + else if (tokens[0] == "invalidatetiles:") + { + assert(firstLine.size() == static_cast<std::string::size_type>(length)); + peer->_tileCache->invalidateTiles(_curPart, firstLine); + } } forwardToPeer(buffer, length); @@ -212,15 +229,16 @@ bool MasterProcessSession::handleInput(char *buffer, int length) } return loadDocument(buffer, length, tokens); } - else if (tokens[0] != "status" && - tokens[0] != "tile" && + else if (tokens[0] != "invalidatetiles" && tokens[0] != "key" && tokens[0] != "mouse" && - tokens[0] != "uno" && - tokens[0] != "selecttext" && - tokens[0] != "selectgraphic" && tokens[0] != "resetselection" && - tokens[0] != "saveas") + tokens[0] != "saveas" && + tokens[0] != "selectgraphic" && + tokens[0] != "selecttext" && + tokens[0] != "status" && + tokens[0] != "tile" && + tokens[0] != "uno") { sendTextFrame("error: cmd=" + tokens[0] + " kind=unknown"); return false; @@ -230,6 +248,10 @@ bool MasterProcessSession::handleInput(char *buffer, int length) sendTextFrame("error: cmd=" + tokens[0] + " kind=nodocloaded"); return false; } + else if (tokens[0] == "invalidatetiles") + { + return invalidateTiles(buffer, length, tokens); + } else if (tokens[0] == "status") { return getStatus(buffer, length); @@ -406,6 +428,25 @@ void MasterProcessSession::preSpawn() _childProcesses[child.id()] = 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; + } + + _tileCache->invalidateTiles(_curPart, tilePosX, tilePosY, tileWidth, tileHeight); + return true; +} + bool MasterProcessSession::loadDocument(const char *buffer, int length, StringTokenizer& tokens) { if (tokens.count() != 2) @@ -592,8 +633,8 @@ void MasterProcessSession::forwardToPeer(const char *buffer, int length) ChildProcessSession::ChildProcessSession(std::shared_ptr<WebSocket> ws, LibreOfficeKit *loKit) : LOOLSession(ws, Kind::ToMaster), - _loKit(loKit), - _loKitDocument(NULL) + _loKitDocument(NULL), + _loKit(loKit) { std::cout << Util::logPrefix() << "ChildProcessSession ctor this=" << this << " ws=" << _ws.get() << std::endl; } @@ -690,11 +731,12 @@ extern "C" { static void myCallback(int nType, const char* pPayload, void* pData) { - LOOLSession *srv = reinterpret_cast<LOOLSession *>(pData); + ChildProcessSession *srv = reinterpret_cast<ChildProcessSession *>(pData); switch ((LibreOfficeKitCallbackType) nType) { case LOK_CALLBACK_INVALIDATE_TILES: + srv->sendTextFrame("curpart: part=" + std::to_string(srv->_loKitDocument->pClass->getPart(srv->_loKitDocument))); srv->sendTextFrame("invalidatetiles: " + std::string(pPayload)); break; case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: diff --git a/loolwsd/LOOLSession.hpp b/loolwsd/LOOLSession.hpp index 2d780d7..c73456c 100644 --- a/loolwsd/LOOLSession.hpp +++ b/loolwsd/LOOLSession.hpp @@ -109,6 +109,8 @@ public: virtual bool getStatus(const char *buffer, int length); 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); @@ -141,6 +143,7 @@ private: Poco::UInt64 _childId; static Poco::Random _rng; static std::mutex _rngMutex; + int _curPart; }; class ChildProcessSession final : public LOOLSession @@ -153,6 +156,8 @@ public: virtual bool getStatus(const char *buffer, int length); + LibreOfficeKitDocument *_loKitDocument; + protected: virtual bool loadDocument(const char *buffer, int length, Poco::StringTokenizer& tokens) override; @@ -169,7 +174,6 @@ public: std::string _jail; std::string _loSubPath; LibreOfficeKit *_loKit; - LibreOfficeKitDocument *_loKitDocument; }; #endif diff --git a/loolwsd/TileCache.cpp b/loolwsd/TileCache.cpp index 3b673f0..c72888e 100644 --- a/loolwsd/TileCache.cpp +++ b/loolwsd/TileCache.cpp @@ -10,11 +10,13 @@ #include "config.h" #include <cassert> +#include <cstdio> #include <fstream> #include <iostream> #include <memory> #include <string> +#include <Poco/DirectoryIterator.h> #include <Poco/File.h> #include <Poco/Path.h> #include <Poco/SHA1Engine.h> @@ -118,6 +120,52 @@ void TileCache::saveStatus(const std::string& status) statusStream.close(); } +void TileCache::invalidateTiles(int part, int x, int y, int width, int height) +{ + std::string dirName = cacheDirName(); + + std::vector<std::string> toBeRemoved; + + for (auto tileIterator = Poco::DirectoryIterator(dirName); tileIterator != Poco::DirectoryIterator(); ++tileIterator) + { + std::string baseName = tileIterator.path().getBaseName(); + + int tilePart, tilePixelWidth, tilePixelHeight, tilePosX, tilePosY, tileWidth, tileHeight; + + if (parseCacheFileName(baseName, tilePart, tilePixelWidth, tilePixelHeight, tilePosX, tilePosY, tileWidth, tileHeight)) + { + std::cout << "Tile " << baseName << " is " << tileWidth << "x" << tileHeight << "@+" << tilePosX << "+" << tilePosY << std::endl; + if (tilePart == part && + tilePosX < x + width && tilePosX + tileWidth >= x && + tilePosY < y + height && tilePosY + tileHeight >= y) + { + std::cout << "Match!" << std::endl; + toBeRemoved.push_back(tileIterator.path().toString()); + } + } + } + + for (auto i: toBeRemoved) + std::remove(i.c_str()); +} + +void TileCache::invalidateTiles(int part, const std::string& tiles) +{ + Poco::StringTokenizer tokens(tiles, " ", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); + + assert(tokens[0] == "invalidateTiles:"); + + if (tokens.count() != 5) + return; + + int width(std::stoi(tokens[1])); + int height(std::stoi(tokens[2])); + int x(std::stoi(tokens[3])); + int y(std::stoi(tokens[4])); + + invalidateTiles(part, x, y, width, height); +} + std::string TileCache::cacheDirName() { Poco::SHA1Engine digestEngine; @@ -136,6 +184,11 @@ std::string TileCache::cacheFileName(int part, int width, int height, int tilePo std::to_string(tileWidth) + "x" + std::to_string(tileHeight) + ".png"); } +bool TileCache::parseCacheFileName(std::string& fileName, int& part, int& width, int& height, int& tilePosX, int& tilePosY, int& tileWidth, int& tileHeight) +{ + return (std::sscanf(fileName.c_str(), "%d_%dx%d.%d,%d.%dx%d", &part, &width, &height, &tilePosX, &tilePosY, &tileWidth, &tileHeight) == 7); +} + Poco::Timestamp TileCache::getLastModified() { std::fstream modTimeFile(cacheDirName() + "/modtime.txt", std::ios::in); diff --git a/loolwsd/TileCache.hpp b/loolwsd/TileCache.hpp index 28d8e3a..21704a2 100644 --- a/loolwsd/TileCache.hpp +++ b/loolwsd/TileCache.hpp @@ -28,9 +28,15 @@ public: // The parameter is a status: message void saveStatus(const std::string& status); + // The tiles parameter is an invalidatetiles: message as sent by the child process + void invalidateTiles(int part, const std::string& tiles); + + void invalidateTiles(int part, int x, int y, int width, int height); + private: std::string cacheDirName(); std::string cacheFileName(int part, int width, int height, int tilePosX, int tilePosY, int tileWidth, int tileHeight); + bool parseCacheFileName(std::string& fileName, int& part, int& width, int& height, int& tilePosX, int& tilePosY, int& tileWidth, int& tileHeight); Poco::Timestamp getLastModified(); const std::string& _docURL; diff --git a/loolwsd/protocol.txt b/loolwsd/protocol.txt index 9dce5cf..adb5950 100644 --- a/loolwsd/protocol.txt +++ b/loolwsd/protocol.txt @@ -108,6 +108,12 @@ child <id> parent has passed the id (a 64-bit random number) to the child when starting it, so this is how the child identificates itself. +curpart: part=<partNumber> + + Sent to the parent process before certain messages that the parent + needs to act on in addition to passing them on to the client, like + invalidatetiles: + nextmessage: size=<upperlimit> each tile: message sent from the child to the parent is preceded commit 1d75eacd6a2d124b46f601f4353a54980fb3a3e9 Author: Tor Lillqvist <[email protected]> Date: Thu May 28 17:57:29 2015 +0300 Add an invalidatetiles message from client to server Mainly as a debugging aid to make it easier to check the tile cache invalidation code, but might be useful for real, too. diff --git a/loolwsd/protocol.txt b/loolwsd/protocol.txt index 30f428e..9dce5cf 100644 --- a/loolwsd/protocol.txt +++ b/loolwsd/protocol.txt @@ -11,6 +11,11 @@ tiles proactively (guessing what the client might need). Etc. client -> server ================ +invalidatetiles part=<partNumber> tileposx=<xpos> tileposy=<ypos> tilewidth=<tileWidth> tileheight=<tileHeight> + + All parameters are numbers. Makes the server remove any cached + tiles intersecting with the given area (in twips). + key type=<type> char=<charcode> key=<keycode> <type> is 'input' or 'up', <charcode> and <keycode> are numbers. commit 5d9f5e715a73f3c2d85ebeeb0a5404606bfcd10f Author: Tor Lillqvist <[email protected]> Date: Thu May 28 17:55:49 2015 +0300 Sort the messages in the sections diff --git a/loolwsd/protocol.txt b/loolwsd/protocol.txt index 10aa581..30f428e 100644 --- a/loolwsd/protocol.txt +++ b/loolwsd/protocol.txt @@ -11,29 +11,26 @@ tiles proactively (guessing what the client might need). Etc. client -> server ================ +key type=<type> char=<charcode> key=<keycode> + + <type> is 'input' or 'up', <charcode> and <keycode> are numbers. + load <pathname> Deprecated. load url=<url> -status - -tile part=<partNumber> width=<width> height=<height> tileposx=<xpos> tileposy=<ypos> tilewidth=<tileWidth> tileheight=<tileHeight> - - All parameters are numbers. - -key type=<type> char=<charcode> key=<keycode> - - <type> is 'input' or 'up', <charcode> and <keycode> are numbers. - mouse type=<type> x=<x> y=<y> count=<count> <type> is 'buttondown', 'buttonup' or 'move', others are numbers. -uno <command> +resetselection - <command> is a line of text. +saveas url=<url> format=<format> options=<options> + + <url> is a URL, encoded. <format> is also URL-encoded, i.e. spaces as %20 + options are the whole rest of the line, not URL-encoded, and can be empty selecttext type=<type> x=<x> y=<y> @@ -43,12 +40,16 @@ selectgraphic type=<type> x=<x> y=<y> <type> is 'start' or 'end' <x> and <y> are numbers. -resetselection +status -saveas url=<url> format=<format> options=<options> +tile part=<partNumber> width=<width> height=<height> tileposx=<xpos> tileposy=<ypos> tilewidth=<tileWidth> tileheight=<tileHeight> + + All parameters are numbers. + +uno <command> + + <command> is a line of text. - <url> is a URL, encoded. <format> is also URL-encoded, i.e. spaces as %20 - options are the whole rest of the line, not URL-encoded, and can be empty server -> client ================ @@ -60,15 +61,6 @@ error: cmd=<command> kind=<kind> message that caused the error. <kind> is some single-word classification -status: type=<typeName> parts=<numberOfParts> current=<currentPartNumber> width=<width> height=<height> - - <typeName> is 'text, 'spreadsheet', 'presentation', 'drawing' or 'other. Others are numbers. - -tile: part=<partNumber> width=<width> height=<height> tileposx=<xpos> tileposy=<ypos> tilewidth=<tileWidth> tileheight=<tileHeight> -<binaryPngImage> - - The parameters from the corresponding 'tile' command. - nextmessage: size=<byteSize> <byteSize> is the size, in bytes, of the next message, in case it @@ -78,36 +70,44 @@ nextmessage: size=<byteSize> must be handled by clients that cannot (like those using Poco 1.6.0). +status: type=<typeName> parts=<numberOfParts> current=<currentPartNumber> width=<width> height=<height> + + <typeName> is 'text, 'spreadsheet', 'presentation', 'drawing' or 'other. Others are numbers. + +tile: part=<partNumber> width=<width> height=<height> tileposx=<xpos> tileposy=<ypos> tilewidth=<tileWidth> tileheight=<tileHeight> +<binaryPngImage> + + The parameters from the corresponding 'tile' command. + Each LOK_CALLBACK_FOO_BAR callback causes a corresponding message to the client, consisting of the FOO_BAR part in lowercase, without underscore, followed by a colon, space and the callback payload. For instance: -invalidatetiles: <payload> - invalidatecursor: - +invalidatetiles: <payload> The communication between the parent process (the one keeping open the Websocket connections to the clients) and a child process (handling one document through LibreOfficeKit) uses the same protocol, with the following additions and changes: + child -> parent =============== child <id> -Must be the first message sent from the child to the parent. The -parent has passed the id (a 64-bit random number) to the child when -starting it, so this is how the child identificates itself. + Must be the first message sent from the child to the parent. The + parent has passed the id (a 64-bit random number) to the child + when starting it, so this is how the child identificates itself. nextmessage: size=<upperlimit> -each tile: message sent from the child to the parent is preceded by a -nextmessage: message that gives an upper limit on the size of the -tile: message that will follow. (We assume it is only tile: messages -that can be "large".) Once we depend on Poco 1.6.1, where one doesn't -need to use a pre-allocated buffer when receiving WebSocket messages, -this will go away. + each tile: message sent from the child to the parent is preceded + by a nextmessage: message that gives an upper limit on the size of + the tile: message that will follow. (We assume it is only tile: + messages that can be "large".) Once we depend on Poco 1.6.1, where + one doesn't need to use a pre-allocated buffer when receiving + WebSocket messages, this will go away. _______________________________________________ Libreoffice-commits mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
