wsd/ClientSession.cpp | 43 ++++++++++++++++++++++++++++++++- wsd/ClientSession.hpp | 12 ++++++++- wsd/LOOLWSD.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 110 insertions(+), 9 deletions(-)
New commits: commit 405b66c8db71c314c2d26c4b18e9d74806c76bf1 Author: Tor Lillqvist <[email protected]> Date: Thu Jul 12 15:50:04 2018 +0300 Add a cache of "thumbnails" (PNG images) generated using the convert-to API When asked to "convert" a document to a PNG image, i.e. what one can call a thumbnail, check if we have a cached PNG for the same docuemnt already, and in that case return it. When we have done such a convert-to operation to PNG, save the result in the cache for later re-use. This change adds no thumbnail cache cleanup mechanism. That will have to be implemented separately using a cron job or whatever. There are further improvement possibilities: For instance, if the document is of a type that contains an embedded thumbnail (like ODF), just extract and return that. For ODF that embedded thumbnail even already is in PNG format. Change-Id: I882efe97acc1d81041dc7a4ccb222995940e4836 Reviewed-on: https://gerrit.libreoffice.org/57345 Reviewed-by: Tor Lillqvist <[email protected]> Tested-by: Tor Lillqvist <[email protected]> diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index ee2a3f941..353b73263 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -13,6 +13,7 @@ #include <fstream> +#include <Poco/File.h> #include <Poco/Net/HTTPResponse.h> #include <Poco/StringTokenizer.h> #include <Poco/URI.h> @@ -35,15 +36,20 @@ using Poco::StringTokenizer; ClientSession::ClientSession(const std::string& id, const std::shared_ptr<DocumentBroker>& docBroker, const Poco::URI& uriPublic, - const bool readOnly) : + const bool readOnly, + const bool creatingPngThumbnail, + const std::string& thumbnailFile) : Session("ToClient-" + id, id, readOnly), _docBroker(docBroker), _uriPublic(uriPublic), + _creatingPngThumbnail(creatingPngThumbnail), + _thumbnailFile(thumbnailFile), _isDocumentOwner(false), _isAttached(false), _isViewLoaded(false), _keyEvents(1) { + assert(!creatingPngThumbnail || thumbnailFile != ""); const size_t curConnections = ++LOOLWSD::NumConnections; LOG_INF("ClientSession ctor [" << getName() << "], current number of connections: " << curConnections); } @@ -722,6 +728,41 @@ bool ClientSession::handleKitToClientMessage(const char* buffer, const int lengt response.set("Content-Disposition", "attachment; filename=\"" + fileName + "\""); HttpHelper::sendFile(_saveAsSocket, encodedFilePath, mimeType, response); + + if (_creatingPngThumbnail) + { + // Save the created PNG "thumbnail" under a name constructed from the SHA1 of + // the document contents. + + // FIXME: We could first try to simply hardlink the result as the thumbnail. + + Poco::File(Poco::Path(_thumbnailFile).parent()).createDirectories(); + std::ofstream thumbnail(_thumbnailFile, std::ios::binary); + + if (thumbnail.is_open() && thumbnail.good()) + { + std::ifstream result(resultURL.getPath(), std::ios::binary); + if (result.is_open() && result.good()) + { + Poco::StreamCopier::copyStream(result, thumbnail); + if (!result.bad() && thumbnail.good()) + { + LOG_TRC("Created cached thumbnail " << _thumbnailFile); + } + else + { + thumbnail.close(); + unlink(_thumbnailFile.c_str()); + } + } + } + else + { + if (thumbnail.is_open()) + thumbnail.close(); + unlink(_thumbnailFile.c_str()); + } + } } // Conversion is done, cleanup this fake session. diff --git a/wsd/ClientSession.hpp b/wsd/ClientSession.hpp index 2366ca78b..df7faa45f 100644 --- a/wsd/ClientSession.hpp +++ b/wsd/ClientSession.hpp @@ -26,7 +26,9 @@ public: ClientSession(const std::string& id, const std::shared_ptr<DocumentBroker>& docBroker, const Poco::URI& uriPublic, - const bool isReadOnly = false); + const bool isReadOnly = false, + const bool creatingPngThumbnail = false, + const std::string& thumbnailFile = ""); virtual ~ClientSession(); @@ -147,6 +149,14 @@ private: /// URI with which client made request to us const Poco::URI _uriPublic; + /// True iff this is a convert-to operation creating a PNG. We assume all such PNGs will be + /// usable as "thumbnails" for the document. + const bool _creatingPngThumbnail; + + /// The pathname of the thumbnail file being created. Valid only if _creatingPngThumbnail is + /// true. + const std::string _thumbnailFile; + /// Whether this session is the owner of currently opened document bool _isDocumentOwner; diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index 93bd56e3f..1854d8cc2 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -49,14 +49,15 @@ #include <sstream> #include <thread> +#include <Poco/DateTimeFormatter.h> +#include <Poco/DigestStream.h> +#include <Poco/DirectoryIterator.h> #include <Poco/DOM/AutoPtr.h> #include <Poco/DOM/DOMParser.h> #include <Poco/DOM/DOMWriter.h> #include <Poco/DOM/Document.h> #include <Poco/DOM/Element.h> #include <Poco/DOM/NodeList.h> -#include <Poco/DateTimeFormatter.h> -#include <Poco/DirectoryIterator.h> #include <Poco/Environment.h> #include <Poco/Exception.h> #include <Poco/File.h> @@ -76,6 +77,7 @@ #include <Poco/Pipe.h> #include <Poco/Process.h> #include <Poco/SAX/InputSource.h> +#include <Poco/SHA1Engine.h> #include <Poco/StreamCopier.h> #include <Poco/StringTokenizer.h> #include <Poco/TemporaryFile.h> @@ -1517,7 +1519,9 @@ static std::shared_ptr<ClientSession> createNewClientSession(const WebSocketHand const std::string& id, const Poco::URI& uriPublic, const std::shared_ptr<DocumentBroker>& docBroker, - const bool isReadOnly) + const bool isReadOnly, + const bool creatingPngThumbnail, + const std::string& thumbnailFile) { LOG_CHECK_RET(docBroker && "Null docBroker instance", nullptr); try @@ -1533,7 +1537,7 @@ static std::shared_ptr<ClientSession> createNewClientSession(const WebSocketHand // In case of WOPI, if this session is not set as readonly, it might be set so // later after making a call to WOPI host which tells us the permission on files // (UserCanWrite param). - return std::make_shared<ClientSession>(id, docBroker, uriPublic, isReadOnly); + return std::make_shared<ClientSession>(id, docBroker, uriPublic, isReadOnly, creatingPngThumbnail, thumbnailFile); } catch (const std::exception& exc) { @@ -2049,7 +2053,52 @@ private: bool sent = false; if (!fromPath.empty() && !format.empty()) { - LOG_INF("Conversion request for URI [" << fromPath << "]."); + LOG_INF("Conversion request for file [" << fromPath << "]."); + + std::string thumbnailFile; + if (format == "png") + { + // Check whether we already have a cached "thumbnail" for this document. + + // FIXME: We could here check if the document is such that already contains an + // easily extractable thumbnail, like Thubnails/thumbnail.png in ODF documents, + // and extract and return that. + + std::ifstream istr(fromPath, std::ios::binary); + if (istr.is_open() && istr.good()) + { + Poco::SHA1Engine sha1; + Poco::DigestOutputStream dos(sha1); + Poco::StreamCopier::copyStream(istr, dos); + if (!istr.bad()) + { + istr.close(); + dos.close(); + std::string digest = Poco::DigestEngine::digestToHex(sha1.digest()); + thumbnailFile = LOOLWSD::Cache + "/thumbnails/" + digest.substr(0, 2) + "/" + + digest.substr(2, 2) + "/" + digest.substr(4) + ".png"; + + istr.open(thumbnailFile, std::ios::binary); + if (istr.is_open() && istr.good()) + { + std::string png; + Poco::StreamCopier::copyToString(istr, png); + if (!istr.bad()) + { + LOG_TRC("Found cached thumbnail " << thumbnailFile); + + response.set("Content-Disposition", "attachment; filename=\"" + digest + ".png\""); + response.setContentType("image/png"); + response.setContentLength(png.size()); + socket->send(response); + socket->send(png.data(), png.size(), true); + + return; + } + } + } + } + } Poco::URI uriPublic = DocumentBroker::sanitizeURI(fromPath); const std::string docKey = DocumentBroker::getDocKey(uriPublic); @@ -2070,7 +2119,8 @@ private: // Load the document. // TODO: Move to DocumentBroker. const bool isReadOnly = true; - std::shared_ptr<ClientSession> clientSession = createNewClientSession(nullptr, _id, uriPublic, docBroker, isReadOnly); + std::shared_ptr<ClientSession> clientSession = createNewClientSession(nullptr, _id, uriPublic, docBroker, + isReadOnly, format == "png", thumbnailFile); if (clientSession) { disposition.setMove([docBroker, clientSession, format] @@ -2309,7 +2359,7 @@ private: std::shared_ptr<DocumentBroker> docBroker = findOrCreateDocBroker(ws, url, docKey, _id, uriPublic); if (docBroker) { - std::shared_ptr<ClientSession> clientSession = createNewClientSession(&ws, _id, uriPublic, docBroker, isReadOnly); + std::shared_ptr<ClientSession> clientSession = createNewClientSession(&ws, _id, uriPublic, docBroker, isReadOnly, false, ""); if (clientSession) { // Transfer the client socket to the DocumentBroker when we get back to the poll: _______________________________________________ Libreoffice-commits mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
