[Libreoffice-commits] online.git: 3 commits - common/Protocol.cpp common/Protocol.hpp kit/Kit.cpp loleaflet/src test/TileQueueTests.cpp test/WhiteBoxTests.cpp wsd/protocol.txt wsd/TileDesc.hpp

Tor Lillqvist tml at collabora.com
Wed Jan 11 21:35:53 UTC 2017


 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 <tml at 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 <tml at 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 <tml at 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
  */


More information about the Libreoffice-commits mailing list