common/Protocol.cpp | 36 +++++++ common/Protocol.hpp | 8 + kit/Kit.cpp | 39 ++++++-- loleaflet/src/core/Socket.js | 3 loleaflet/src/layer/tile/TileLayer.js | 3 loleaflet/src/layer/tile/WriterTileLayer.js | 14 ++ test/TileQueueTests.cpp | 28 ++--- test/WhiteBoxTests.cpp | 10 -- wsd/TileDesc.hpp | 135 +++++++++++++++++++++------- wsd/protocol.txt | 24 ++-- 10 files changed, 221 insertions(+), 79 deletions(-)
New commits: commit bc19f90dd48a1792fca9d5b7bbf13b8f1b35134b Author: Tor Lillqvist <t...@collabora.com> Date: Wed Jan 4 14:36:13 2017 +0200 Don't send a tile that hasn't changed even if client asks for it The server tells the client the hash of each tile it sends (calculated from the contents of the tile, not its PNG encoding). When the client asks for a tile to be refreshed, it tells the server what the hash of the existing tile is. If the server notices that the tile contents hasn't actually changed, it doesn't PNG encode it and doesn't send it to the client. The intent is that this will reduce load on the server and also avoid unnecessary tile traffic. Change-Id: Ia06ca68655ea984ed4319f24f4470afda322eccf diff --git a/common/Protocol.cpp b/common/Protocol.cpp index ad02f0f..f2e6fc3 100644 --- a/common/Protocol.cpp +++ b/common/Protocol.cpp @@ -58,6 +58,20 @@ namespace LOOLProtocol return true; } + bool stringToUInt64(const std::string& input, uint64_t& value) + { + try + { + value = std::stoull(input); + } + catch (std::invalid_argument&) + { + return false; + } + + return true; + } + bool getTokenInteger(const std::string& token, const std::string& name, int& value) { size_t nextIdx; @@ -80,6 +94,28 @@ namespace LOOLProtocol return true; } + bool getTokenUInt64(const std::string& token, const std::string& name, uint64_t& value) + { + size_t nextIdx; + try + { + if (token.size() < name.size() + 2 || + token.substr(0, name.size()) != name || + token[name.size()] != '=' || + (value = std::stoull(token.substr(name.size() + 1), &nextIdx), false) || + nextIdx != token.size() - name.size() - 1) + { + return false; + } + } + catch (std::invalid_argument&) + { + return false; + } + + return true; + } + bool getTokenString(const std::string& token, const std::string& name, std::string& value) { try diff --git a/common/Protocol.hpp b/common/Protocol.hpp index 0a0a0fe..0e544b0 100644 --- a/common/Protocol.hpp +++ b/common/Protocol.hpp @@ -10,6 +10,7 @@ #ifndef INCLUDED_LOOLPROTOCOL_HPP #define INCLUDED_LOOLPROTOCOL_HPP +#include <cstdint> #include <cstring> #include <map> #include <sstream> @@ -41,6 +42,8 @@ namespace LOOLProtocol std::tuple<int, int, std::string> ParseVersion(const std::string& version); bool stringToInteger(const std::string& input, int& value); + bool stringToUInt64(const std::string& input, uint64_t& value); + inline bool parseNameValuePair(const std::string& token, std::string& name, std::string& value, const char delim = '=') { @@ -63,6 +66,7 @@ namespace LOOLProtocol } bool getTokenInteger(const std::string& token, const std::string& name, int& value); + bool getTokenUInt64(const std::string& token, const std::string& name, uint64_t& value); bool getTokenString(const std::string& token, const std::string& name, std::string& value); bool getTokenKeyword(const std::string& token, const std::string& name, const std::map<std::string, int>& map, int& value); diff --git a/kit/Kit.cpp b/kit/Kit.cpp index db96997..c2e2281 100644 --- a/kit/Kit.cpp +++ b/kit/Kit.cpp @@ -380,9 +380,9 @@ public: } bool encodeBufferToPNG(unsigned char* pixmap, int width, int height, - std::vector<char>& output, LibreOfficeKitTileMode mode) + std::vector<char>& output, LibreOfficeKitTileMode mode, + uint64_t hash) { - const uint64_t hash = Png::hashBuffer(pixmap, width, height); if (cacheTest(hash, output)) { return true; @@ -395,10 +395,9 @@ public: bool encodeSubBufferToPNG(unsigned char* pixmap, size_t startX, size_t startY, int width, int height, int bufferWidth, int bufferHeight, - std::vector<char>& output, LibreOfficeKitTileMode mode) + std::vector<char>& output, LibreOfficeKitTileMode mode, + uint64_t hash) { - const uint64_t hash = Png::hashSubBuffer(pixmap, startX, startY, width, height, - bufferWidth, bufferHeight); if (cacheTest(hash, output)) { return true; @@ -622,7 +621,15 @@ public: " ms (" << area / elapsed << " MP/s)."); const auto mode = static_cast<LibreOfficeKitTileMode>(_loKitDocument->getTileMode()); - if (!_pngCache.encodeBufferToPNG(pixmap.data(), tile.getWidth(), tile.getHeight(), output, mode)) + const uint64_t hash = Png::hashBuffer(pixmap.data(), tile.getWidth(), tile.getHeight()); + if (hash != 0 && tile.getOldHash() == hash) + { + // The tile content is identical to what the client already has, so skip it + LOG_TRC("Match oldhash==hash (" << hash << "), skipping"); + return; + } + + if (!_pngCache.encodeBufferToPNG(pixmap.data(), tile.getWidth(), tile.getHeight(), output, mode, hash)) { //FIXME: Return error. //sendTextFrame("error: cmd=tile kind=failure"); @@ -707,8 +714,20 @@ public: const auto oldSize = output.size(); const auto pixelWidth = tileCombined.getWidth(); const auto pixelHeight = tileCombined.getHeight(); + + const uint64_t hash = Png::hashSubBuffer(pixmap.data(), positionX * pixelWidth, positionY * pixelHeight, + pixelWidth, pixelHeight, pixmapWidth, pixmapHeight); + + if (hash != 0 && tiles[tileIndex].getOldHash() == hash) + { + // The tile content is identical to what the client already has, so skip it + LOG_TRC("Match for tile #" << tileIndex << " at (" << positionX << "," << positionY << ") oldhash==hash (" << hash << "), skipping"); + tiles.erase(tiles.begin() + tileIndex); + continue; + } + if (!_pngCache.encodeSubBufferToPNG(pixmap.data(), positionX * pixelWidth, positionY * pixelHeight, - pixelWidth, pixelHeight, pixmapWidth, pixmapHeight, output, mode)) + pixelWidth, pixelHeight, pixmapWidth, pixmapHeight, output, mode, hash)) { //FIXME: Return error. //sendTextFrame("error: cmd=tile kind=failure"); @@ -717,8 +736,10 @@ public: } const auto imgSize = output.size() - oldSize; - LOG_TRC("Encoded tile #" << tileIndex << " in " << imgSize << " bytes."); - tiles[tileIndex++].setImgSize(imgSize); + LOG_TRC("Encoded tile #" << tileIndex << " at (" << positionX << "," << positionY << ") with oldhash=" << tiles[tileIndex].getOldHash() << ", hash=" << hash << " in " << imgSize << " bytes."); + tiles[tileIndex].setHash(hash); + tiles[tileIndex].setImgSize(imgSize); + tileIndex++; } #if ENABLE_DEBUG diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js index e778df9..4e4c935b 100644 --- a/loleaflet/src/core/Socket.js +++ b/loleaflet/src/core/Socket.js @@ -602,6 +602,9 @@ L.Socket = L.Class.extend({ else if (tokens[i].substring(0, 12) === 'rendercount=') { command.rendercount = parseInt(tokens[i].substring(12)); } + else if (tokens[i].startsWith('hash=')) { + command.hash = tokens[i].substring(tokens[i].indexOf('=')+1); + } } if (command.tileWidth && command.tileHeight && this._map._docLayer) { var defaultZoom = this._map.options.zoom; diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 5c5a2d3..b7fd4a0 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -1135,6 +1135,9 @@ L.TileLayer = L.GridLayer.extend({ }); } else if (tile) { + if (command.hash != undefined) { + tile.oldhash = command.hash; + } if (this._tiles[key]._invalidCount > 0) { this._tiles[key]._invalidCount -= 1; } diff --git a/loleaflet/src/layer/tile/WriterTileLayer.js b/loleaflet/src/layer/tile/WriterTileLayer.js index b7165dc..9a4966b 100644 --- a/loleaflet/src/layer/tile/WriterTileLayer.js +++ b/loleaflet/src/layer/tile/WriterTileLayer.js @@ -28,6 +28,7 @@ L.WriterTileLayer = L.TileLayer.extend({ var visibleArea = new L.Bounds(visibleTopLeft, visibleBottomRight); var tilePositionsX = ''; var tilePositionsY = ''; + var oldHashes = ''; var needsNewTiles = false; for (var key in this._tiles) { var coords = this._tiles[key].coords; @@ -50,6 +51,15 @@ L.WriterTileLayer = L.TileLayer.extend({ tilePositionsY += ','; } tilePositionsY += tileTopLeft.y; + if (oldHashes !== '') { + oldHashes += ','; + } + if (this._tiles[key].oldhash === undefined) { + oldHashes += '0'; + } + else { + oldHashes += this._tiles[key].oldhash; + } needsNewTiles = true; if (this._debug) { this._debugAddInvalidationData(this._tiles[key]); @@ -75,7 +85,8 @@ L.WriterTileLayer = L.TileLayer.extend({ 'tileposx=' + tilePositionsX + ' ' + 'tileposy=' + tilePositionsY + ' ' + 'tilewidth=' + this._tileWidthTwips + ' ' + - 'tileheight=' + this._tileHeightTwips; + 'tileheight=' + this._tileHeightTwips + ' ' + + 'oldhash=' + oldHashes; this._map._socket.sendMessage(message, ''); diff --git a/test/TileQueueTests.cpp b/test/TileQueueTests.cpp index 8f7b575..747686c 100644 --- a/test/TileQueueTests.cpp +++ b/test/TileQueueTests.cpp @@ -63,11 +63,11 @@ class TileQueueTests : public CPPUNIT_NS::TestFixture void TileQueueTests::testTileQueuePriority() { - const std::string reqHigh = "tile part=0 width=256 height=256 tileposx=0 tileposy=0 tilewidth=3840 tileheight=3840 ver=-1"; - const std::string resHigh = "tile part=0 width=256 height=256 tileposx=0 tileposy=0 tilewidth=3840 tileheight=3840 ver=-1"; + const std::string reqHigh = "tile part=0 width=256 height=256 tileposx=0 tileposy=0 tilewidth=3840 tileheight=3840 oldhash=0 hash=0 ver=-1"; + const std::string resHigh = "tile part=0 width=256 height=256 tileposx=0 tileposy=0 tilewidth=3840 tileheight=3840 oldhash=0 hash=0 ver=-1"; const TileQueue::Payload payloadHigh(resHigh.data(), resHigh.data() + resHigh.size()); - const std::string reqLow = "tile part=0 width=256 height=256 tileposx=0 tileposy=253440 tilewidth=3840 tileheight=3840 ver=-1"; - const std::string resLow = "tile part=0 width=256 height=256 tileposx=0 tileposy=253440 tilewidth=3840 tileheight=3840 ver=-1"; + const std::string reqLow = "tile part=0 width=256 height=256 tileposx=0 tileposy=253440 tilewidth=3840 tileheight=3840 oldhash=0 hash=0 ver=-1"; + const std::string resLow = "tile part=0 width=256 height=256 tileposx=0 tileposy=253440 tilewidth=3840 tileheight=3840 oldhash=0 hash=0 ver=-1"; const TileQueue::Payload payloadLow(resLow.data(), resLow.data() + resLow.size()); TileQueue queue; @@ -114,11 +114,11 @@ void TileQueueTests::testTileCombinedRendering() const std::string req3 = "tile part=0 width=256 height=256 tileposx=0 tileposy=3840 tilewidth=3840 tileheight=3840"; const std::string req4 = "tile part=0 width=256 height=256 tileposx=3840 tileposy=3840 tilewidth=3840 tileheight=3840"; - const std::string resHor = "tilecombine part=0 width=256 height=256 tileposx=0,3840 tileposy=0,0 imgsize=0,0 tilewidth=3840 tileheight=3840 ver=-1,-1,"; + const std::string resHor = "tilecombine part=0 width=256 height=256 tileposx=0,3840 tileposy=0,0 imgsize=0,0 tilewidth=3840 tileheight=3840 ver=-1,-1 oldhash=0,0 hash=0,0"; const TileQueue::Payload payloadHor(resHor.data(), resHor.data() + resHor.size()); - const std::string resVer = "tilecombine part=0 width=256 height=256 tileposx=0,0 tileposy=0,3840 imgsize=0,0 tilewidth=3840 tileheight=3840 ver=-1,-1,"; + const std::string resVer = "tilecombine part=0 width=256 height=256 tileposx=0,0 tileposy=0,3840 imgsize=0,0 tilewidth=3840 tileheight=3840 ver=-1,-1 oldhash=0,0 hash=0,0"; const TileQueue::Payload payloadVer(resVer.data(), resVer.data() + resVer.size()); - const std::string resFull = "tilecombine part=0 width=256 height=256 tileposx=0,3840,0 tileposy=0,0,3840 imgsize=0,0,0 tilewidth=3840 tileheight=3840 ver=-1,-1,-1,"; + const std::string resFull = "tilecombine part=0 width=256 height=256 tileposx=0,3840,0 tileposy=0,0,3840 imgsize=0,0,0 tilewidth=3840 tileheight=3840 ver=-1,-1,-1 oldhash=0,0,0 hash=0,0,0"; const TileQueue::Payload payloadFull(resFull.data(), resFull.data() + resFull.size()); TileQueue queue; @@ -162,7 +162,7 @@ void TileQueueTests::testTileRecombining() // but when we later extract that, it is just one "tilecombine" message std::string message(payloadAsString(queue.get())); - CPPUNIT_ASSERT_EQUAL(std::string("tilecombine part=0 width=256 height=256 tileposx=7680,0,3840 tileposy=0,0,0 imgsize=0,0,0 tilewidth=3840 tileheight=3840 ver=-1,-1,-1,"), message); + CPPUNIT_ASSERT_EQUAL(std::string("tilecombine part=0 width=256 height=256 tileposx=7680,0,3840 tileposy=0,0,0 imgsize=0,0,0 tilewidth=3840 tileheight=3840 ver=-1,-1,-1 oldhash=0,0,0 hash=0,0,0"), message); // and nothing remains in the queue CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(queue._queue.size())); @@ -182,10 +182,10 @@ void TileQueueTests::testViewOrder() const std::vector<std::string> tiles = { - "tile part=0 width=256 height=256 tileposx=0 tileposy=0 tilewidth=3840 tileheight=3840 ver=-1", - "tile part=0 width=256 height=256 tileposx=0 tileposy=7680 tilewidth=3840 tileheight=3840 ver=-1", - "tile part=0 width=256 height=256 tileposx=0 tileposy=15360 tilewidth=3840 tileheight=3840 ver=-1", - "tile part=0 width=256 height=256 tileposx=0 tileposy=23040 tilewidth=3840 tileheight=3840 ver=-1" + "tile part=0 width=256 height=256 tileposx=0 tileposy=0 tilewidth=3840 tileheight=3840 oldhash=0 hash=0 ver=-1", + "tile part=0 width=256 height=256 tileposx=0 tileposy=7680 tilewidth=3840 tileheight=3840 oldhash=0 hash=0 ver=-1", + "tile part=0 width=256 height=256 tileposx=0 tileposy=15360 tilewidth=3840 tileheight=3840 oldhash=0 hash=0 ver=-1", + "tile part=0 width=256 height=256 tileposx=0 tileposy=23040 tilewidth=3840 tileheight=3840 oldhash=0 hash=0 ver=-1" }; for (auto &tile : tiles) @@ -230,8 +230,8 @@ void TileQueueTests::testPreviewsDeprioritization() // the previews const std::vector<std::string> tiles = { - "tile part=0 width=256 height=256 tileposx=0 tileposy=0 tilewidth=3840 tileheight=3840 ver=-1", - "tile part=0 width=256 height=256 tileposx=0 tileposy=7680 tilewidth=3840 tileheight=3840 ver=-1" + "tile part=0 width=256 height=256 tileposx=0 tileposy=0 tilewidth=3840 tileheight=3840 oldhash=0 hash=0 ver=-1", + "tile part=0 width=256 height=256 tileposx=0 tileposy=7680 tilewidth=3840 tileheight=3840 oldhash=0 hash=0 ver=-1" }; for (auto &preview : previews) diff --git a/wsd/TileDesc.hpp b/wsd/TileDesc.hpp index 0ed9269..ef5420b 100644 --- a/wsd/TileDesc.hpp +++ b/wsd/TileDesc.hpp @@ -36,7 +36,9 @@ public: _ver(ver), _imgSize(imgSize), _id(id), - _broadcast(broadcast) + _broadcast(broadcast), + _oldHash(0), + _hash(0) { if (_part < 0 || _width <= 0 || @@ -64,6 +66,10 @@ public: void setImgSize(const int imgSize) { _imgSize = imgSize; } int getId() const { return _id; } bool getBroadcast() const { return _broadcast; } + void setOldHash(uint64_t hash) { _oldHash = hash; } + uint64_t getOldHash() const { return _oldHash; } + void setHash(uint64_t hash) { _hash = hash; } + uint64_t getHash() const { return _hash; } bool operator==(const TileDesc& other) const { @@ -133,7 +139,9 @@ public: << " tileposx=" << _tilePosX << " tileposy=" << _tilePosY << " tilewidth=" << _tileWidth - << " tileheight=" << _tileHeight; + << " tileheight=" << _tileHeight + << " oldhash=" << _oldHash + << " hash=" << _hash; // Anything after ver is optional. oss << " ver=" << _ver; @@ -168,13 +176,22 @@ public: pairs["imgsize"] = 0; pairs["id"] = -1; + uint64_t oldHash = 0; + uint64_t hash = 0; for (size_t i = 0; i < tokens.count(); ++i) { - std::string name; - int value = -1; - if (LOOLProtocol::parseNameIntegerPair(tokens[i], name, value)) + if (LOOLProtocol::getTokenUInt64(tokens[i], "oldhash", oldHash)) + ; + else if (LOOLProtocol::getTokenUInt64(tokens[i], "hash", hash)) + ; + else { - pairs[name] = value; + std::string name; + int value = -1; + if (LOOLProtocol::parseNameIntegerPair(tokens[i], name, value)) + { + pairs[name] = value; + } } } @@ -182,11 +199,15 @@ public: const bool broadcast = (LOOLProtocol::getTokenString(tokens, "broadcast", s) && s == "yes"); - return TileDesc(pairs["part"], pairs["width"], pairs["height"], - pairs["tileposx"], pairs["tileposy"], - pairs["tilewidth"], pairs["tileheight"], - pairs["ver"], - pairs["imgsize"], pairs["id"], broadcast); + auto result = TileDesc(pairs["part"], pairs["width"], pairs["height"], + pairs["tileposx"], pairs["tileposy"], + pairs["tilewidth"], pairs["tileheight"], + pairs["ver"], + pairs["imgsize"], pairs["id"], broadcast); + result.setOldHash(oldHash); + result.setHash(hash); + + return result; } /// Deserialize a TileDesc from a string format. @@ -209,6 +230,8 @@ private: int _imgSize; //< Used for responses. int _id; bool _broadcast; + uint64_t _oldHash; + uint64_t _hash; }; /// One or more tile header. @@ -220,7 +243,9 @@ private: TileCombined(int part, int width, int height, const std::string& tilePositionsX, const std::string& tilePositionsY, int tileWidth, int tileHeight, const std::string& vers, - const std::string& imgSizes, int id) : + const std::string& imgSizes, int id, + const std::string& oldHashes, + const std::string& hashes) : _part(part), _width(width), _height(height), @@ -239,17 +264,21 @@ private: Poco::StringTokenizer positionXtokens(tilePositionsX, ",", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); Poco::StringTokenizer positionYtokens(tilePositionsY, ",", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); - Poco::StringTokenizer sizeTokens(imgSizes, ",", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); + Poco::StringTokenizer imgSizeTokens(imgSizes, ",", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); Poco::StringTokenizer verTokens(vers, ",", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); + Poco::StringTokenizer oldHashTokens(oldHashes, ",", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); + Poco::StringTokenizer hashTokens(hashes, ",", Poco::StringTokenizer::TOK_IGNORE_EMPTY | Poco::StringTokenizer::TOK_TRIM); - const auto numberOfPositions = positionYtokens.count(); + const auto numberOfPositions = positionXtokens.count(); - // check that number of positions for X and Y is the same - if (numberOfPositions != positionXtokens.count() || - (!imgSizes.empty() && numberOfPositions != sizeTokens.count()) || - (!vers.empty() && numberOfPositions != verTokens.count())) + // check that the comma-separated strings have the same number of elements + if (numberOfPositions != positionYtokens.count() || + (!imgSizes.empty() && numberOfPositions != imgSizeTokens.count()) || + (!vers.empty() && numberOfPositions != verTokens.count()) || + (!oldHashes.empty() && numberOfPositions != oldHashTokens.count()) || + (!hashes.empty() && numberOfPositions != hashTokens.count())) { - throw BadArgumentException("Invalid tilecombine descriptor. Uneven number of tiles."); + throw BadArgumentException("Invalid tilecombine descriptor. Unequal number of tiles in parameters."); } for (size_t i = 0; i < numberOfPositions; ++i) @@ -266,8 +295,8 @@ private: throw BadArgumentException("Invalid 'tileposy' in tilecombine descriptor."); } - int size = 0; - if (sizeTokens.count() && !LOOLProtocol::stringToInteger(sizeTokens[i], size)) + int imgSize = 0; + if (imgSizeTokens.count() && !LOOLProtocol::stringToInteger(imgSizeTokens[i], imgSize)) { throw BadArgumentException("Invalid 'imgsize' in tilecombine descriptor."); } @@ -278,7 +307,21 @@ private: throw BadArgumentException("Invalid 'ver' in tilecombine descriptor."); } - _tiles.emplace_back(_part, _width, _height, x, y, _tileWidth, _tileHeight, ver, size, id, false); + uint64_t oldHash = 0; + if (oldHashTokens.count() && !LOOLProtocol::stringToUInt64(oldHashTokens[i], oldHash)) + { + throw BadArgumentException("Invalid tilecombine descriptor."); + } + + uint64_t hash = 0; + if (hashTokens.count() && !LOOLProtocol::stringToUInt64(hashTokens[i], hash)) + { + throw BadArgumentException("Invalid tilecombine descriptor."); + } + + _tiles.emplace_back(_part, _width, _height, x, y, _tileWidth, _tileHeight, ver, imgSize, id, false); + _tiles.back().setOldHash(oldHash); + _tiles.back().setHash(hash); } } @@ -306,24 +349,21 @@ public: { oss << tile.getTilePosX() << ','; } - - oss.seekp(-1, std::ios_base::cur); // Remove last comma. + oss.seekp(-1, std::ios_base::cur); // Seek back over last comma, overwritten below. oss << " tileposy="; for (const auto& tile : _tiles) { oss << tile.getTilePosY() << ','; } - - oss.seekp(-1, std::ios_base::cur); // Remove last comma. + oss.seekp(-1, std::ios_base::cur); // Ditto. oss << " imgsize="; for (const auto& tile : _tiles) { - oss << tile.getImgSize() << ','; + oss << tile.getImgSize() << ','; // Ditto. } - - oss.seekp(-1, std::ios_base::cur); // Remove last comma. + oss.seekp(-1, std::ios_base::cur); oss << " tilewidth=" << _tileWidth << " tileheight=" << _tileHeight; @@ -333,15 +373,30 @@ public: { oss << tile.getVersion() << ','; } + oss.seekp(-1, std::ios_base::cur); // Ditto. - oss.seekp(-1, std::ios_base::cur); // Remove last comma. + oss << " oldhash="; + for (const auto& tile : _tiles) + { + oss << tile.getOldHash() << ','; + } + oss.seekp(-1, std::ios_base::cur); // Ditto + + oss << " hash="; + for (const auto& tile : _tiles) + { + oss << tile.getHash() << ','; + } + oss.seekp(-1, std::ios_base::cur); // See beow. if (_id >= 0) { oss << " id=" << _id; } - return oss.str(); + // Make sure we don't return a potential trailing comma that + // we have seeked back over but not overwritten after all. + return oss.str().substr(0, oss.tellp()); } /// Deserialize a TileDesc from a tokenized string. @@ -358,6 +413,8 @@ public: std::string tilePositionsY; std::string imgSizes; std::string versions; + std::string oldhashes; + std::string hashes; for (size_t i = 0; i < tokens.count(); ++i) { std::string name; @@ -380,6 +437,14 @@ public: { versions = value; } + else if (name == "oldhash") + { + oldhashes = value; + } + else if (name == "hash") + { + hashes = value; + } else { int v = 0; @@ -395,7 +460,7 @@ public: tilePositionsX, tilePositionsY, pairs["tilewidth"], pairs["tileheight"], versions, - imgSizes, pairs["id"]); + imgSizes, pairs["id"], oldhashes, hashes); } /// Deserialize a TileDesc from a string format. @@ -413,18 +478,22 @@ public: std::ostringstream xs; std::ostringstream ys; std::ostringstream vers; + std::ostringstream oldhs; + std::ostringstream hs; for (const auto& tile : tiles) { xs << tile.getTilePosX() << ','; ys << tile.getTilePosY() << ','; vers << tile.getVersion() << ','; + oldhs << tile.getOldHash() << ','; + hs << tile.getHash() << ','; } vers.seekp(-1, std::ios_base::cur); // Remove last comma. return TileCombined(tiles[0].getPart(), tiles[0].getWidth(), tiles[0].getHeight(), xs.str(), ys.str(), tiles[0].getTileWidth(), tiles[0].getTileHeight(), - vers.str(), "", -1); + vers.str(), "", -1, oldhs.str(), hs.str()); } private: diff --git a/wsd/protocol.txt b/wsd/protocol.txt index e314fa3..bb135e9 100644 --- a/wsd/protocol.txt +++ b/wsd/protocol.txt @@ -126,10 +126,11 @@ status styles -tile part=<partNumber> width=<width> height=<height> tileposx=<xpos> tileposy=<ypos> tilewidth=<tileWidth> -tileheight=<tileHeight> [timestamp=<time>] [id=<id> broadcast=<yesOrNo>] +tile part=<partNumber> width=<width> height=<height> tileposx=<xpos> tileposy=<ypos> tilewidth=<tileWidth> tileheight=<tileHeight> [timestamp=<time>] [id=<id> broadcast=<yesOrNo>] [oldhash=<hash>] - Parameters are numbers except broadcast which is 'yes' or 'no'. + Parameters are numbers except broadcast which is 'yes' or 'no' and + hash which is a 64-bit hash. (There is no need for the client to + parse it into a number, it can be treated as an opaque string.) Note: id must be echoed back in the response verbatim. It and the following parameter, broadcast, are used when rendering slide @@ -139,8 +140,10 @@ tileheight=<tileHeight> [timestamp=<time>] [id=<id> broadcast=<yesOrNo>] tilecombine <parameters> - Accept same parameters as 'tile' message except parameters 'tileposx' and 'tileposy' - can be a comma separated list, and number of elements in both must be same. + Accepts same parameters as the 'tile' message except that + parameters 'tileposx', 'tileposy' and 'oldhash' are + comma-separated lists, and the number of elements in each must be + same. uno <command> @@ -305,15 +308,16 @@ textselectioncontent: <content> Current selection's content -tile: part=<partNumber> width=<width> height=<height> tileposx=<xpos> tileposy=<ypos> tilewidth=<tileWidth> tileheight=<tileHeight> [timestamp=<time>] [renderid=<id>] +tile: part=<partNumber> width=<width> height=<height> tileposx=<xpos> tileposy=<ypos> tilewidth=<tileWidth> tileheight=<tileHeight> [timestamp=<time>] [renderid=<id>] [hash=<hash>] <binaryPngImage> The parameters from the corresponding 'tile' command. - Additionally, in a debug build, the renderid is either a unique - identifier, different for each actual call to LibreOfficeKit to - render a tile, or the string 'cached' if the tile was found in the - cache. + In a debug build, the renderid is either a unique identifier, + different for each actual call to LibreOfficeKit to render a tile, + or the string 'cached' if the tile was found in the cache. hash is + a hash of the tile contents, and can be included by the client in + the next 'tile' message requesting the same tile. Each LOK_CALLBACK_FOO_BAR callback except LOK_CALLBACK_INVALIDATE_TILES causes a corresponding message to the commit 29fc49acf21639d5b7b9bea78b2c26dbb76fc3df Author: Tor Lillqvist <t...@collabora.com> Date: Wed Jan 11 16:13:46 2017 +0200 Bump the message first line abbreviation limit to 500 characters If we are logging a message, we want to see the first line of it in its entirety if possible. Especially now with more parameters being added to tile messages, 120 was not enough to see the added interesting ones. Bin the silly test that used knowledge of what the limit is. We should not test a coindidental arbitrary number that is not a documented part of an API. If we want to test the default abbreviation functionality, we need to at least make that default limit (now 500) public in Protocol.hpp. Change-Id: Iea59ba46e8331e2a839c792146f123fed9df2b82 diff --git a/common/Protocol.hpp b/common/Protocol.hpp index f9ccd6a..0a0a0fe 100644 --- a/common/Protocol.hpp +++ b/common/Protocol.hpp @@ -230,7 +230,7 @@ namespace LOOLProtocol return std::string(); } - const auto firstLine = getFirstLine(message, std::min(length, 120)); + const auto firstLine = getFirstLine(message, std::min(length, 500)); // If first line is less than the length (minus newline), add ellipsis. if (firstLine.size() < static_cast<std::string::size_type>(length) - 1) @@ -243,7 +243,7 @@ namespace LOOLProtocol inline std::string getAbbreviatedMessage(const std::string& message) { - const auto pos = getDelimiterPosition(message.data(), std::min(message.size(), 120UL), '\n'); + const auto pos = getDelimiterPosition(message.data(), std::min(message.size(), 500UL), '\n'); // If first line is less than the length (minus newline), add ellipsis. if (pos < static_cast<std::string::size_type>(message.size()) - 1) diff --git a/test/WhiteBoxTests.cpp b/test/WhiteBoxTests.cpp index fae37b6..703a9b2 100644 --- a/test/WhiteBoxTests.cpp +++ b/test/WhiteBoxTests.cpp @@ -152,16 +152,6 @@ void WhiteBoxTests::testMessageAbbreviation() abbr = "1234567890123..."; CPPUNIT_ASSERT_EQUAL(abbr, LOOLProtocol::getAbbreviatedMessage(s.data(), s.size())); CPPUNIT_ASSERT_EQUAL(abbr, LOOLProtocol::getAbbreviatedMessage(s)); - - // 120 characters. Change when the abbreviation max-length changes. - s = "123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"; - CPPUNIT_ASSERT_EQUAL(s, LOOLProtocol::getAbbreviatedMessage(s.data(), s.size())); - CPPUNIT_ASSERT_EQUAL(s, LOOLProtocol::getAbbreviatedMessage(s)); - - abbr = s + "..."; - s += "more data"; - CPPUNIT_ASSERT_EQUAL(abbr, LOOLProtocol::getAbbreviatedMessage(s.data(), s.size())); - CPPUNIT_ASSERT_EQUAL(abbr, LOOLProtocol::getAbbreviatedMessage(s)); } void WhiteBoxTests::testTokenizer() commit d12a0258d843cb8b56120fb780d3b8a23ac9cbf1 Author: Tor Lillqvist <t...@collabora.com> Date: Wed Jan 11 15:20:44 2017 +0200 Add Emacs mode line Change-Id: I5ba2b8d5bc3c8b9b75fd357224422079f7ec91f9 diff --git a/loleaflet/src/layer/tile/WriterTileLayer.js b/loleaflet/src/layer/tile/WriterTileLayer.js index 0e546d0..b7165dc 100644 --- a/loleaflet/src/layer/tile/WriterTileLayer.js +++ b/loleaflet/src/layer/tile/WriterTileLayer.js @@ -1,3 +1,4 @@ +/* -*- js-indent-level: 8 -*- */ /* * Writer tile layer is used to display a text document */ _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits