loolwsd/ChildSession.cpp | 152 +-- loolwsd/ChildSession.hpp | 30 loolwsd/LOOLKit.cpp | 72 - loolwsd/LibreOfficeKit.hpp | 646 -------------- loolwsd/Makefile.am | 1 loolwsd/bundled/include/LibreOfficeKit/LibreOfficeKit.h | 1 loolwsd/bundled/include/LibreOfficeKit/LibreOfficeKit.hxx | 610 +++++++++++++ loolwsd/test/WhiteBoxTests.cpp | 28 8 files changed, 773 insertions(+), 767 deletions(-)
New commits: commit 72a5f35f30d2e9250795abf910a1456d7b62b78e Author: Jan Holesovsky <ke...@collabora.com> Date: Tue Nov 15 12:50:58 2016 +0100 LibreOfficeKit.hpp changed meaning of getPart(), update accordingly. Change-Id: Ia346f4f838856040fa9aea26b3ac9c0b596b9218 diff --git a/loolwsd/ChildSession.cpp b/loolwsd/ChildSession.cpp index ecd98c9..07b3a4c 100644 --- a/loolwsd/ChildSession.cpp +++ b/loolwsd/ChildSession.cpp @@ -100,7 +100,9 @@ bool ChildSession::_handleInput(const char *buffer, int length) std::vector<int> viewIds(viewCount); getLOKitDocument()->getViewIds(viewIds.data(), viewCount); - const int curPart = getLOKitDocument()->getPart(); + int curPart = 0; + if (getLOKitDocument()->getDocumentType() != LOK_DOCTYPE_TEXT) + curPart = getLOKitDocument()->getPart(); lockLokDoc.unlock(); @@ -919,7 +921,7 @@ bool ChildSession::setClientPart(const char* /*buffer*/, int /*length*/, StringT getLOKitDocument()->setView(_viewId); - if (part != getLOKitDocument()->getPart()) + if (getLOKitDocument()->getDocumentType() != LOK_DOCTYPE_TEXT && part != getLOKitDocument()->getPart()) { getLOKitDocument()->setPart(part); } commit 2069650ba64bf9bbb8a4b8f28add7502e1d24dc5 Author: Jan Holesovsky <ke...@collabora.com> Date: Tue Nov 15 11:37:47 2016 +0100 Get rid of LibreOfficeKit.hpp, it's mostly a copy of LibreOfficeKit.hxx. Change-Id: I55f9c28a3ac1ef2a36c18c29cc16209bedd48770 diff --git a/loolwsd/ChildSession.cpp b/loolwsd/ChildSession.cpp index c28cfbf..ecd98c9 100644 --- a/loolwsd/ChildSession.cpp +++ b/loolwsd/ChildSession.cpp @@ -83,7 +83,7 @@ bool ChildSession::_handleInput(const char *buffer, int length) updateLastActivityTime(); } - if (tokens.count() > 0 && tokens[0] == "useractive" && _loKitDocument != nullptr) + if (tokens.count() > 0 && tokens[0] == "useractive" && getLOKitDocument() != nullptr) { LOG_DBG("Handling message after inactivity of " << getInactivityMS() << "ms."); setIsActive(true); @@ -91,16 +91,16 @@ bool ChildSession::_handleInput(const char *buffer, int length) // Client is getting active again. // Send invalidation and other sync-up messages. std::unique_lock<std::recursive_mutex> lock(Mutex); //TODO: Move to top of function? - auto lockLokDoc(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lockLokDoc(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); // Get the list of view ids from the core - const int viewCount = _loKitDocument->getViewsCount(); + const int viewCount = getLOKitDocument()->getViewsCount(); std::vector<int> viewIds(viewCount); - _loKitDocument->getViewIds(viewIds.data(), viewCount); + getLOKitDocument()->getViewIds(viewIds.data(), viewCount); - const int curPart = _loKitDocument->getPart(); + const int curPart = getLOKitDocument()->getPart(); lockLokDoc.unlock(); @@ -311,8 +311,8 @@ bool ChildSession::loadDocument(const char * /*buffer*/, int /*length*/, StringT std::unique_lock<std::recursive_mutex> lock(Mutex); - _loKitDocument = _docManager.onLoad(getId(), _jailedFilePath, _userName, _docPassword, renderOpts, _haveDocPassword); - if (!_loKitDocument || _viewId < 0) + bool loaded = _docManager.onLoad(getId(), _jailedFilePath, _userName, _docPassword, renderOpts, _haveDocPassword); + if (!loaded || _viewId < 0) { LOG_ERR("Failed to get LoKitDocument instance."); return false; @@ -321,19 +321,19 @@ bool ChildSession::loadDocument(const char * /*buffer*/, int /*length*/, StringT LOG_INF("Created new view with viewid: [" << _viewId << + "] for username: [" << _userName << "] in session: [" << getId() << "]."); - auto lockLokDoc(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lockLokDoc(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - _docType = LOKitHelper::getDocumentTypeAsString(_loKitDocument->get()); + _docType = LOKitHelper::getDocumentTypeAsString(getLOKitDocument()->get()); if (_docType != "text" && part != -1) { - _loKitDocument->setPart(part); + getLOKitDocument()->setPart(part); } // Respond by the document status LOG_DBG("Sending status after loading view " << _viewId << "."); - const auto status = LOKitHelper::documentStatus(_loKitDocument->get()); + const auto status = LOKitHelper::documentStatus(getLOKitDocument()->get()); if (status.empty() || !sendTextFrame("status: " + status)) { LOG_ERR("Failed to get/forward document status [" << status << "]."); @@ -341,9 +341,9 @@ bool ChildSession::loadDocument(const char * /*buffer*/, int /*length*/, StringT } // Get the list of view ids from the core - const int viewCount = _loKitDocument->getViewsCount(); + const int viewCount = getLOKitDocument()->getViewsCount(); std::vector<int> viewIds(viewCount); - _loKitDocument->getViewIds(viewIds.data(), viewCount); + getLOKitDocument()->getViewIds(viewIds.data(), viewCount); lockLokDoc.unlock(); @@ -379,11 +379,11 @@ bool ChildSession::sendFontRendering(const char* /*buffer*/, int /*length*/, Str unsigned char* ptrFont = nullptr; { - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - ptrFont = _loKitDocument->renderFont(decodedFont.c_str(), text.c_str(), &width, &height); + ptrFont = getLOKitDocument()->renderFont(decodedFont.c_str(), text.c_str(), &width, &height); } LOG_TRC("renderFont [" << font << "] rendered in " << (timestamp.elapsed()/1000.) << "ms"); @@ -403,11 +403,11 @@ bool ChildSession::getStatus(const char* /*buffer*/, int /*length*/) { std::string status; { - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - status = LOKitHelper::documentStatus(_loKitDocument->get()); + status = LOKitHelper::documentStatus(getLOKitDocument()->get()); } if (status.empty()) @@ -462,16 +462,16 @@ bool ChildSession::getCommandValues(const char* /*buffer*/, int /*length*/, Stri return false; } - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); if (command == ".uno:DocumentRepair") { char* pUndo; const std::string jsonTemplate("{\"commandName\":\".uno:DocumentRepair\",\"Redo\":%s,\"Undo\":%s}"); - pValues = _loKitDocument->getCommandValues(".uno:Redo"); - pUndo = _loKitDocument->getCommandValues(".uno:Undo"); + pValues = getLOKitDocument()->getCommandValues(".uno:Redo"); + pUndo = getLOKitDocument()->getCommandValues(".uno:Undo"); std::string json = Poco::format(jsonTemplate, std::string(pValues == nullptr ? "" : pValues), std::string(pUndo == nullptr ? "" : pUndo)); @@ -484,7 +484,7 @@ bool ChildSession::getCommandValues(const char* /*buffer*/, int /*length*/, Stri } else { - pValues = _loKitDocument->getCommandValues(command.c_str()); + pValues = getLOKitDocument()->getCommandValues(command.c_str()); success = sendTextFrame("commandvalues: " + std::string(pValues == nullptr ? "" : pValues)); std::free(pValues); } @@ -496,10 +496,10 @@ bool ChildSession::getPartPageRectangles(const char* /*buffer*/, int /*length*/) { char* partPage = nullptr; { - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); - partPage = _loKitDocument->getPartPageRectangles(); + getLOKitDocument()->setView(_viewId); + partPage = getLOKitDocument()->getPartPageRectangles(); } sendTextFrame("partpagerectangles: " + std::string(partPage)); @@ -521,11 +521,11 @@ bool ChildSession::clientZoom(const char* /*buffer*/, int /*length*/, StringToke return false; } - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - _loKitDocument->setClientZoom(tilePixelWidth, tilePixelHeight, tileTwipWidth, tileTwipHeight); + getLOKitDocument()->setClientZoom(tilePixelWidth, tilePixelHeight, tileTwipWidth, tileTwipHeight); return true; } @@ -546,11 +546,11 @@ bool ChildSession::clientVisibleArea(const char* /*buffer*/, int /*length*/, Str return false; } - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - _loKitDocument->setClientVisibleArea(x, y, width, height); + getLOKitDocument()->setClientVisibleArea(x, y, width, height); return true; } @@ -584,9 +584,9 @@ bool ChildSession::downloadAs(const char* /*buffer*/, int /*length*/, StringToke const auto url = JAILED_DOCUMENT_ROOT + tmpDir + "/" + filenameParam.getFileName(); { - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->saveAs(url.c_str(), + getLOKitDocument()->saveAs(url.c_str(), format.size() == 0 ? nullptr :format.c_str(), filterOptions.size() == 0 ? nullptr : filterOptions.c_str()); } @@ -615,11 +615,11 @@ bool ChildSession::getTextSelection(const char* /*buffer*/, int /*length*/, Stri char* textSelection = nullptr; { - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - textSelection = _loKitDocument->getTextSelection(mimeType.c_str(), nullptr); + textSelection = getLOKitDocument()->getTextSelection(mimeType.c_str(), nullptr); } sendTextFrame("textselectioncontent: " + std::string(textSelection)); @@ -641,11 +641,11 @@ bool ChildSession::paste(const char* buffer, int length, StringTokenizer& tokens const char* data = buffer + firstLine.size() + 1; const size_t size = length - firstLine.size() - 1; - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - _loKitDocument->paste(mimeType.c_str(), data, size); + getLOKitDocument()->paste(mimeType.c_str(), data, size); return true; } @@ -671,11 +671,11 @@ bool ChildSession::insertFile(const char* /*buffer*/, int /*length*/, StringToke "\"value\":\"" + fileName + "\"" "}}"; - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - _loKitDocument->postUnoCommand(command.c_str(), arguments.c_str(), false); + getLOKitDocument()->postUnoCommand(command.c_str(), arguments.c_str(), false); } return true; @@ -711,11 +711,11 @@ bool ChildSession::keyEvent(const char* /*buffer*/, int /*length*/, StringTokeni return true; } - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - _loKitDocument->postKeyEvent(type, charcode, keycode); + getLOKitDocument()->postKeyEvent(type, charcode, keycode); return true; } @@ -756,11 +756,11 @@ bool ChildSession::mouseEvent(const char* /*buffer*/, int /*length*/, StringToke return false; } - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - _loKitDocument->postMouseEvent(type, x, y, count, buttons, modifier); + getLOKitDocument()->postMouseEvent(type, x, y, count, buttons, modifier); return true; } @@ -776,9 +776,9 @@ bool ChildSession::unoCommand(const char* /*buffer*/, int /*length*/, StringToke // we need to get LOK_CALLBACK_UNO_COMMAND_RESULT callback when saving const bool bNotify = (tokens[1] == ".uno:Save"); - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); if (tokens.count() == 2 && tokens[1] == ".uno:fakeDiskFull") { @@ -786,11 +786,11 @@ bool ChildSession::unoCommand(const char* /*buffer*/, int /*length*/, StringToke } else if (tokens.count() == 2) { - _loKitDocument->postUnoCommand(tokens[1].c_str(), nullptr, bNotify); + getLOKitDocument()->postUnoCommand(tokens[1].c_str(), nullptr, bNotify); } else { - _loKitDocument->postUnoCommand(tokens[1].c_str(), + getLOKitDocument()->postUnoCommand(tokens[1].c_str(), Poco::cat(std::string(" "), tokens.begin() + 2, tokens.end()).c_str(), bNotify); } @@ -814,11 +814,11 @@ bool ChildSession::selectText(const char* /*buffer*/, int /*length*/, StringToke return false; } - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - _loKitDocument->setTextSelection(type, x, y); + getLOKitDocument()->setTextSelection(type, x, y); return true; } @@ -838,11 +838,11 @@ bool ChildSession::selectGraphic(const char* /*buffer*/, int /*length*/, StringT return false; } - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - _loKitDocument->setGraphicSelection(type, x, y); + getLOKitDocument()->setGraphicSelection(type, x, y); return true; } @@ -855,11 +855,11 @@ bool ChildSession::resetSelection(const char* /*buffer*/, int /*length*/, String return false; } - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - _loKitDocument->resetSelection(); + getLOKitDocument()->resetSelection(); return true; } @@ -887,11 +887,11 @@ bool ChildSession::saveAs(const char* /*buffer*/, int /*length*/, StringTokenize bool success = false; { - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - success = _loKitDocument->saveAs(url.c_str(), + success = getLOKitDocument()->saveAs(url.c_str(), format.size() == 0 ? nullptr :format.c_str(), filterOptions.size() == 0 ? nullptr : filterOptions.c_str()); } @@ -915,13 +915,13 @@ bool ChildSession::setClientPart(const char* /*buffer*/, int /*length*/, StringT return false; } - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - if (part != _loKitDocument->getPart()) + if (part != getLOKitDocument()->getPart()) { - _loKitDocument->setPart(part); + getLOKitDocument()->setPart(part); } return true; @@ -937,11 +937,11 @@ bool ChildSession::setPage(const char* /*buffer*/, int /*length*/, StringTokeniz return false; } - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_docManager.getDocumentMutex()); - _loKitDocument->setView(_viewId); + getLOKitDocument()->setView(_viewId); - _loKitDocument->setPart(page); + getLOKitDocument()->setPart(page); return true; } diff --git a/loolwsd/ChildSession.hpp b/loolwsd/ChildSession.hpp index 624090c..871de23 100644 --- a/loolwsd/ChildSession.hpp +++ b/loolwsd/ChildSession.hpp @@ -12,13 +12,15 @@ #include <mutex> +#define LOK_USE_UNSTABLE_API +#include <LibreOfficeKit/LibreOfficeKit.hxx> + #include <Poco/NotificationQueue.h> #include <Poco/Thread.h> #include "Common.hpp" #include "LOOLKit.hpp" #include "LOOLSession.hpp" -#include "LibreOfficeKit.hpp" class ChildSession; @@ -28,23 +30,31 @@ class IDocumentManager { public: /// Reqest loading a document, or a new view, if one exists. - virtual std::shared_ptr<lok::Document> onLoad(const std::string& sessionId, - const std::string& jailedFilePath, - const std::string& userName, - const std::string& docPassword, - const std::string& renderOpts, - const bool haveDocPassword) + virtual bool onLoad(const std::string& sessionId, + const std::string& jailedFilePath, + const std::string& userName, + const std::string& docPassword, + const std::string& renderOpts, + const bool haveDocPassword) = 0; /// Unload a client session, which unloads the document /// if it is the last and only. virtual void onUnload(const ChildSession& session) = 0; + /// Access to the document instance. + virtual std::shared_ptr<lok::Document> getLOKitDocument() = 0; + /// Send updated view info to all active sessions virtual void notifyViewInfo(const std::vector<int>& viewIds) = 0; /// Get a view ID <-> UserInfo map. virtual std::map<int, UserInfo> getViewInfo() = 0; virtual std::mutex& getMutex() = 0; + + /// Mutex guarding the document - so that we can lock operations like + /// setting a view followed by a tile render, etc. + virtual std::mutex& getDocumentMutex() = 0; + virtual std::shared_ptr<TileQueue>& getTileQueue() = 0; virtual bool sendTextFrame(const std::string& message) = 0; @@ -115,6 +125,11 @@ private: virtual void disconnect() override; virtual bool _handleInput(const char* buffer, int length) override; + std::shared_ptr<lok::Document> getLOKitDocument() + { + return _docManager.getLOKitDocument(); + } + private: const std::string _jailId; IDocumentManager& _docManager; @@ -125,7 +140,6 @@ private: /// Whether document has been opened succesfuly bool _isDocLoaded; - std::shared_ptr<lok::Document> _loKitDocument; std::string _docType; std::map<std::string, std::string> _lastDocStates; std::map<int, std::string> _lastDocEvents; diff --git a/loolwsd/LOOLKit.cpp b/loolwsd/LOOLKit.cpp index 48902cb..4577003 100644 --- a/loolwsd/LOOLKit.cpp +++ b/loolwsd/LOOLKit.cpp @@ -31,6 +31,7 @@ #define LOK_USE_UNSTABLE_API #include <LibreOfficeKit/LibreOfficeKitInit.h> +#include <LibreOfficeKit/LibreOfficeKit.hxx> #include <Poco/Exception.h> #include <Poco/JSON/Object.h> @@ -55,7 +56,6 @@ #include "LOOLKit.hpp" #include "LOOLProtocol.hpp" #include "LOOLWebSocket.hpp" -#include "LibreOfficeKit.hpp" #include "Log.hpp" #include "Png.hpp" #include "Rectangle.hpp" @@ -295,10 +295,11 @@ public: _docPasswordType(PasswordType::ToView), _stop(false), _mutex(), + _documentMutex(), _isLoading(0) { LOG_INF("Document ctor for url [" << _url << "] on child [" << _jailId << "]."); - assert(_loKit && _loKit->get()); + assert(_loKit); _callbackThread.start(*this); } @@ -479,7 +480,7 @@ public: return; } - std::unique_lock<std::mutex> lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_documentMutex); if (_loKitDocument->getViewsCount() <= 0) { LOG_ERR("Tile rendering requested without views."); @@ -550,7 +551,7 @@ public: return; } - std::unique_lock<std::mutex> lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_documentMutex); if (_loKitDocument->getViewsCount() <= 0) { LOG_ERR("Tile rendering requested without views."); @@ -739,12 +740,12 @@ private: } /// Load a document (or view) and register callbacks. - std::shared_ptr<lok::Document> onLoad(const std::string& sessionId, - const std::string& uri, - const std::string& userName, - const std::string& docPassword, - const std::string& renderOpts, - const bool haveDocPassword) override + bool onLoad(const std::string& sessionId, + const std::string& uri, + const std::string& userName, + const std::string& docPassword, + const std::string& renderOpts, + const bool haveDocPassword) override { std::unique_lock<std::mutex> lock(_mutex); @@ -765,13 +766,13 @@ private: load(sessionId, uri, userName, docPassword, renderOpts, haveDocPassword); if (!_loKitDocument || !_loKitDocument->get()) { - return nullptr; + return false; } } catch (const std::exception& exc) { LOG_ERR("Exception while loading [" << uri << "] : " << exc.what()); - return nullptr; + return false; } // Done loading, let the next one in (if any). @@ -780,7 +781,7 @@ private: --_isLoading; _cvLoading.notify_one(); - return _loKitDocument; + return true; } void onUnload(const ChildSession& session) override @@ -797,7 +798,7 @@ private: return; } - std::unique_lock<std::mutex> lockLokDoc(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lockLokDoc(_documentMutex); _loKitDocument->setView(viewId); _loKitDocument->registerCallback(nullptr, nullptr); @@ -907,7 +908,7 @@ private: std::map<std::string, int> viewColors; { - auto lock(_loKitDocument->getLock()); + std::unique_lock<std::mutex> lock(_documentMutex); char* pValues = _loKitDocument->getCommandValues(".uno:TrackedChangeAuthors"); colorValues = std::string(pValues == nullptr ? "" : pValues); @@ -965,16 +966,12 @@ private: // This is the first time we are loading the document LOG_INF("Loading new document from URI: [" << uri << "] for session [" << sessionId << "]."); - auto lock(_loKit->getLock()); + _loKit->registerCallback(GlobalCallback, this); - if (LIBREOFFICEKIT_HAS(_loKit->get(), registerCallback)) - { - _loKit->get()->pClass->registerCallback(_loKit->get(), GlobalCallback, this); - const auto flags = LOK_FEATURE_DOCUMENT_PASSWORD - | LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY - | LOK_FEATURE_PART_IN_INVALIDATION_CALLBACK; - _loKit->setOptionalFeatures(flags); - } + const auto flags = LOK_FEATURE_DOCUMENT_PASSWORD + | LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY + | LOK_FEATURE_PART_IN_INVALIDATION_CALLBACK; + _loKit->setOptionalFeatures(flags); // Save the provided password with us and the jailed url _haveDocPassword = haveDocPassword; @@ -983,9 +980,9 @@ private: _isDocPasswordProtected = false; LOG_DBG("Calling lokit::documentLoad."); - _loKitDocument = _loKit->documentLoad(uri.c_str()); + _loKitDocument.reset(_loKit->documentLoad(uri.c_str())); LOG_DBG("Returned lokit::documentLoad."); - auto l(_loKitDocument->getLock()); + std::unique_lock<std::mutex> l(_documentMutex); lockLokDoc.swap(l); if (!_loKitDocument || !_loKitDocument->get()) @@ -1022,7 +1019,7 @@ private: } else { - auto l(_loKitDocument->getLock()); + std::unique_lock<std::mutex> l(_documentMutex); lockLokDoc.swap(l); // Check if this document requires password @@ -1236,6 +1233,18 @@ private: LOG_DBG("Thread finished."); } + /// Return access to the lok::Document instance. + std::shared_ptr<lok::Document> getLOKitDocument() override + { + return _loKitDocument; + } + + /// Return access to the lok::Document instance. + std::mutex& getDocumentMutex() override + { + return _documentMutex; + } + private: std::shared_ptr<lok::Office> _loKit; const std::string _jailId; @@ -1259,6 +1268,11 @@ private: std::atomic<bool> _stop; mutable std::mutex _mutex; + + /// Mutex guarding the lok::Document so that we can lock operations + /// like setting a view followed by a tile render, etc. + std::mutex _documentMutex; + std::condition_variable _cvLoading; std::atomic_size_t _isLoading; std::map<int, std::unique_ptr<CallbackDescriptor>> _viewIdToCallbackDescr; @@ -1435,14 +1449,14 @@ void lokit_main(const std::string& childRoot, } loKit = std::make_shared<lok::Office>(kit); - if (!loKit || !loKit->get()) + if (!loKit) { LOG_FTL("LibreOfficeKit initialization failed. Exiting."); std::_Exit(Application::EXIT_SOFTWARE); } } - assert(loKit && loKit->get()); + assert(loKit); LOG_INF("Process is ready."); // Open websocket connection between the child process and WSD. diff --git a/loolwsd/Makefile.am b/loolwsd/Makefile.am index a87599d..865d3e7 100644 --- a/loolwsd/Makefile.am +++ b/loolwsd/Makefile.am @@ -98,7 +98,6 @@ noinst_HEADERS = Admin.hpp \ common/FileUtil.hpp \ common/SigUtil.hpp \ IoUtil.hpp \ - LibreOfficeKit.hpp \ Log.hpp \ LOKitHelper.hpp \ LOOLKit.hpp \ diff --git a/loolwsd/bundled/include/LibreOfficeKit/LibreOfficeKit.h b/loolwsd/bundled/include/LibreOfficeKit/LibreOfficeKit.h index c7a2130..0b9535a 100644 --- a/loolwsd/bundled/include/LibreOfficeKit/LibreOfficeKit.h +++ b/loolwsd/bundled/include/LibreOfficeKit/LibreOfficeKit.h @@ -61,6 +61,7 @@ struct _LibreOfficeKitClass void (*freeError) (char* pFree); #if defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY + /// @see lok::Office::registerCallback(). void (*registerCallback) (LibreOfficeKit* pThis, LibreOfficeKitCallback pCallback, void* pData); diff --git a/loolwsd/LibreOfficeKit.hpp b/loolwsd/bundled/include/LibreOfficeKit/LibreOfficeKit.hxx similarity index 69% rename from loolwsd/LibreOfficeKit.hpp rename to loolwsd/bundled/include/LibreOfficeKit/LibreOfficeKit.hxx index fe54938..447f44b 100644 --- a/loolwsd/LibreOfficeKit.hpp +++ b/loolwsd/bundled/include/LibreOfficeKit/LibreOfficeKit.hxx @@ -7,15 +7,20 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#ifndef INCLUDED_LIBREOFFICEKIT_HPP -#define INCLUDED_LIBREOFFICEKIT_HPP +#ifndef INCLUDED_LIBREOFFICEKIT_LIBREOFFICEKIT_HXX +#define INCLUDED_LIBREOFFICEKIT_LIBREOFFICEKIT_HXX -#define LOK_USE_UNSTABLE_API -#include <LibreOfficeKit/LibreOfficeKit.h> -#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <cstddef> -#include "Log.hpp" +#include "LibreOfficeKit.h" +#include "LibreOfficeKitInit.h" +/* + * The reasons this C++ code is not as pretty as it could be are: + * a) provide a pure C API - that's useful for some people + * b) allow ABI stability - C++ vtables are not good for that. + * c) avoid C++ types as part of the API. + */ namespace lok { @@ -23,31 +28,17 @@ namespace lok class Document { private: - LibreOfficeKitDocument* _pDoc; - std::mutex _mutex; + LibreOfficeKitDocument* mpDoc; public: /// A lok::Document is typically created by the lok::Office::documentLoad() method. inline Document(LibreOfficeKitDocument* pDoc) : - _pDoc(pDoc), - _mutex() - { - Log::trace("lok::Document ctor."); - } + mpDoc(pDoc) + {} inline ~Document() { - Log::trace("lok::~Document dtor."); - _pDoc->pClass->destroy(_pDoc); - } - - /// This lock must be held while calling - /// one or more members of this class if - /// the client is multi-threaded. - /// No member function takes this lock otherwise. - std::unique_lock<std::mutex> getLock() - { - return std::unique_lock<std::mutex>(_mutex); + mpDoc->pClass->destroy(mpDoc); } /** @@ -65,16 +56,11 @@ public: */ inline bool saveAs(const char* pUrl, const char* pFormat = NULL, const char* pFilterOptions = NULL) { - Log::trace() << "lok::Document: saveAs: URL: [" << pUrl << "], Format: [" << pFormat - << "], FilterOptions: " << pFilterOptions << "." << Log::end; - return _pDoc->pClass->saveAs(_pDoc, pUrl, pFormat, pFilterOptions) != 0; + return mpDoc->pClass->saveAs(mpDoc, pUrl, pFormat, pFilterOptions) != 0; } /// Gives access to the underlying C pointer. - inline LibreOfficeKitDocument *get() - { - return _pDoc; - } + inline LibreOfficeKitDocument *get() { return mpDoc; } #if defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY /** @@ -84,7 +70,7 @@ public: */ inline int getDocumentType() { - return _pDoc->pClass->getDocumentType(_pDoc); + return mpDoc->pClass->getDocumentType(mpDoc); } /** @@ -95,7 +81,7 @@ public: */ inline int getParts() { - return _pDoc->pClass->getParts(_pDoc); + return mpDoc->pClass->getParts(mpDoc); } /** @@ -109,40 +95,36 @@ public: */ inline char* getPartPageRectangles() { - return _pDoc->pClass->getPartPageRectangles(_pDoc); + return mpDoc->pClass->getPartPageRectangles(mpDoc); } /// Get the current part of the document. - /// Note: For Writer documents this always returns 0 - /// since text docs have a single coordinate system. inline int getPart() { - return getDocumentType() == LOK_DOCTYPE_TEXT ? 0 : _pDoc->pClass->getPart(_pDoc); + return mpDoc->pClass->getPart(mpDoc); } /// Set the current part of the document. inline void setPart(int nPart) { - Log::trace() << "lok::Document: setPart: Part: " << nPart << "." << Log::end; - _pDoc->pClass->setPart(_pDoc, nPart); + mpDoc->pClass->setPart(mpDoc, nPart); } /// Get the current part's name. inline char* getPartName(int nPart) { - return _pDoc->pClass->getPartName(_pDoc, nPart); + return mpDoc->pClass->getPartName(mpDoc, nPart); } /// Get the current part's hash. inline char* getPartHash(int nPart) { - return _pDoc->pClass->getPartHash(_pDoc, nPart); + return mpDoc->pClass->getPartHash(mpDoc, nPart); } inline void setPartMode(int nMode) { - Log::trace() << "lok::Document: setPartMode: Mode: " << nMode << "." << Log::end; - _pDoc->pClass->setPartMode(_pDoc, nMode); + mpDoc->pClass->setPartMode(mpDoc, nMode); } /** @@ -168,7 +150,7 @@ public: const int nTileWidth, const int nTileHeight) { - return _pDoc->pClass->paintTile(_pDoc, pBuffer, nCanvasWidth, nCanvasHeight, + return mpDoc->pClass->paintTile(mpDoc, pBuffer, nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight); } @@ -179,13 +161,13 @@ public: */ inline int getTileMode() { - return _pDoc->pClass->getTileMode(_pDoc); + return mpDoc->pClass->getTileMode(mpDoc); } /// Get the document sizes in TWIPs. inline void getDocumentSize(long* pWidth, long* pHeight) { - _pDoc->pClass->getDocumentSize(_pDoc, pWidth, pHeight); + mpDoc->pClass->getDocumentSize(mpDoc, pWidth, pHeight); } /** @@ -210,8 +192,7 @@ public: */ inline void initializeForRendering(const char* pArguments = NULL) { - Log::trace() << "lok::Document: initializeForRendering: Arguments: [" << pArguments << "]." << Log::end; - _pDoc->pClass->initializeForRendering(_pDoc, pArguments); + mpDoc->pClass->initializeForRendering(mpDoc, pArguments); } /** @@ -223,7 +204,7 @@ public: */ inline void registerCallback(LibreOfficeKitCallback pCallback, void* pData) { - _pDoc->pClass->registerCallback(_pDoc, pCallback, pData); + mpDoc->pClass->registerCallback(mpDoc, pCallback, pData); } /** @@ -235,9 +216,7 @@ public: */ inline void postKeyEvent(int nType, int nCharCode, int nKeyCode) { - Log::trace() << "lok::Document: postKeyEvent: Type=" << nType - << ", CharCode=" << nCharCode << ", KeyCode=" << nKeyCode << Log::end; - _pDoc->pClass->postKeyEvent(_pDoc, nType, nCharCode, nKeyCode); + mpDoc->pClass->postKeyEvent(mpDoc, nType, nCharCode, nKeyCode); } /** @@ -252,10 +231,7 @@ public: */ inline void postMouseEvent(int nType, int nX, int nY, int nCount, int nButtons, int nModifier) { - Log::trace() << "lok::Document: postMouseEvent: Type=" << nType - << ", X=" << nX << ", nY=" << nY << ", Count=" << nCount - << ", Buttons=" << nButtons << ", Modifier=" << nModifier << Log::end; - _pDoc->pClass->postMouseEvent(_pDoc, nType, nX, nY, nCount, nButtons, nModifier); + mpDoc->pClass->postMouseEvent(mpDoc, nType, nX, nY, nCount, nButtons, nModifier); } /** @@ -281,10 +257,7 @@ public: */ inline void postUnoCommand(const char* pCommand, const char* pArguments = NULL, bool bNotifyWhenFinished = false) { - Log::trace() << "lok::Document: postUnoCommand: Command=" << pCommand - << ", Args=" << (pArguments ? pArguments : "''") - << ", NotifyWhenFinished=" << bNotifyWhenFinished << Log::end; - _pDoc->pClass->postUnoCommand(_pDoc, pCommand, pArguments, bNotifyWhenFinished); + mpDoc->pClass->postUnoCommand(mpDoc, pCommand, pArguments, bNotifyWhenFinished); } /** @@ -296,7 +269,7 @@ public: */ inline void setTextSelection(int nType, int nX, int nY) { - _pDoc->pClass->setTextSelection(_pDoc, nType, nX, nY); + mpDoc->pClass->setTextSelection(mpDoc, nType, nX, nY); } /** @@ -307,7 +280,7 @@ public: */ inline char* getTextSelection(const char* pMimeType, char** pUsedMimeType = NULL) { - return _pDoc->pClass->getTextSelection(_pDoc, pMimeType, pUsedMimeType); + return mpDoc->pClass->getTextSelection(mpDoc, pMimeType, pUsedMimeType); } /** @@ -319,7 +292,7 @@ public: */ inline bool paste(const char* pMimeType, const char* pData, size_t nSize) { - return _pDoc->pClass->paste(_pDoc, pMimeType, pData, nSize); + return mpDoc->pClass->paste(mpDoc, pMimeType, pData, nSize); } /** @@ -331,7 +304,7 @@ public: */ inline void setGraphicSelection(int nType, int nX, int nY) { - _pDoc->pClass->setGraphicSelection(_pDoc, nType, nX, nY); + mpDoc->pClass->setGraphicSelection(mpDoc, nType, nX, nY); } /** @@ -339,7 +312,7 @@ public: */ inline void resetSelection() { - _pDoc->pClass->resetSelection(_pDoc); + mpDoc->pClass->resetSelection(mpDoc); } /** @@ -350,7 +323,7 @@ public: */ inline char* getCommandValues(const char* pCommand) { - return _pDoc->pClass->getCommandValues(_pDoc, pCommand); + return mpDoc->pClass->getCommandValues(mpDoc, pCommand); } /** @@ -367,11 +340,7 @@ public: int nTileTwipWidth, int nTileTwipHeight) { - Log::trace() << "lok::Document: setClientZoom: TilePixelWidth: " << nTilePixelWidth - << ", TilePixelHeight: " << nTileTwipHeight - << ", TileTwipWidth: " << nTileTwipWidth - << ", TileTwipHeight: " << nTileTwipHeight << "." << Log::end; - _pDoc->pClass->setClientZoom(_pDoc, nTilePixelWidth, nTilePixelHeight, nTileTwipWidth, nTileTwipHeight); + mpDoc->pClass->setClientZoom(mpDoc, nTilePixelWidth, nTilePixelHeight, nTileTwipWidth, nTileTwipHeight); } /** @@ -386,10 +355,7 @@ public: */ inline void setClientVisibleArea(int nX, int nY, int nWidth, int nHeight) { - Log::trace() << "lok::Document: setClientVisibleArea: X: " << nX - << ", Y: " << nY << ", Width: " << nWidth - << ", Height: " << nHeight << "." << Log::end; - _pDoc->pClass->setClientVisibleArea(_pDoc, nX, nY, nWidth, nHeight); + mpDoc->pClass->setClientVisibleArea(mpDoc, nX, nY, nWidth, nHeight); } /** @@ -399,8 +365,7 @@ public: */ int createView() { - Log::trace() << "lok::Document: createView" << Log::end; - return _pDoc->pClass->createView(_pDoc); + return mpDoc->pClass->createView(mpDoc); } /** @@ -409,8 +374,7 @@ public: */ void destroyView(int nId) { - Log::trace() << "lok::Document: destroyView: " << nId << Log::end; - _pDoc->pClass->destroyView(_pDoc, nId); + mpDoc->pClass->destroyView(mpDoc, nId); } /** @@ -419,9 +383,7 @@ public: */ void setView(int nId) { - Log::trace() << "lok::Document: setView: " << nId << Log::end; - assert(nId >= 0 && "ViewID must be non-negative."); - _pDoc->pClass->setView(_pDoc, nId); + mpDoc->pClass->setView(mpDoc, nId); } /** @@ -430,7 +392,7 @@ public: */ int getView() { - return _pDoc->pClass->getView(_pDoc); + return mpDoc->pClass->getView(mpDoc); } /** @@ -438,26 +400,11 @@ public: */ inline int getViewsCount() { - return _pDoc->pClass->getViewsCount(_pDoc); - } - - /** - * Returns the viewID for each existing view. Since viewIDs are not reused, - * viewIDs are not the same as the index of the view in the view array over - * time. Use getViewsCount() to know the minimal nSize that's large enough. - * - * @param pArray the array to write the viewIDs into - * @param nSize the size of pArray - * @returns true if pArray was large enough and result is written, false - * otherwise. - */ - inline int getViewIds(int* pArray, size_t nSize) - { - return _pDoc->pClass->getViewIds(_pDoc, pArray, nSize); + return mpDoc->pClass->getViewsCount(mpDoc); } /** - * Paints a font name to be displayed in the font list + * Paints a font name or character if provided to be displayed in the font list * @param pFontName the font to be painted */ inline unsigned char* renderFont(const char *pFontName, @@ -465,7 +412,7 @@ public: int *pFontWidth, int *pFontHeight) { - return _pDoc->pClass->renderFont(_pDoc, pFontName, pChar, pFontWidth, pFontHeight); + return mpDoc->pClass->renderFont(mpDoc, pFontName, pChar, pFontWidth, pFontHeight); } /** @@ -483,12 +430,28 @@ public: const int nTileWidth, const int nTileHeight) { - return _pDoc->pClass->paintPartTile(_pDoc, pBuffer, nPart, + return mpDoc->pClass->paintPartTile(mpDoc, pBuffer, nPart, nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight); } + /** + * Returns the viewID for each existing view. Since viewIDs are not reused, + * viewIDs are not the same as the index of the view in the view array over + * time. Use getViewsCount() to know the minimal nSize that's large enough. + * + * @param pArray the array to write the viewIDs into + * @param nSize the size of pArray + * @returns true if pArray was large enough and result is written, false + * otherwise. + */ + inline bool getViewIds(int* pArray, + size_t nSize) + { + return mpDoc->pClass->getViewIds(mpDoc, pArray, nSize); + } + #endif // defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY }; @@ -496,44 +459,17 @@ public: class Office { private: - LibreOfficeKit* _pOffice; - std::mutex _mutex; + LibreOfficeKit* mpThis; public: /// A lok::Office is typically created by the lok_cpp_init() function. inline Office(LibreOfficeKit* pThis) : - _pOffice(pThis), - _mutex() - { - Log::trace("lok::Office ctor."); - assert(_pOffice); - } + mpThis(pThis) + {} inline ~Office() { - std::unique_lock<std::mutex> lock(_mutex); - Log::trace("lok::~Office dtor."); - try - { - _pOffice->pClass->destroy(_pOffice); - } - catch (const std::exception& ex) - { - LOG_ERR("Exception while destroying LibreOfficeKit instance: " << ex.what()); - } - } - - /// This lock must be held while calling - /// one or more member of this class. - std::unique_lock<std::mutex> getLock() - { - return std::unique_lock<std::mutex>(_mutex); - } - - /// Gives access to the underlying C pointer. - inline LibreOfficeKit* get() - { - return _pOffice; + mpThis->pClass->destroy(mpThis); } /** @@ -541,37 +477,53 @@ public: * * @param pUrl the URL of the document to load * @param pFilterOptions options for the import filter, e.g. SkipImages. + * @since pFilterOptions argument added in LibreOffice 5.0 */ - inline std::shared_ptr<Document> documentLoad(const char* pUrl, const char* pFilterOptions = NULL) + inline Document* documentLoad(const char* pUrl, const char* pFilterOptions = NULL) { - Log::trace() << "lok::Office: documentLoad: URL: [" << pUrl - << "], FilterOptions: [" << pFilterOptions - << "]." << Log::end; LibreOfficeKitDocument* pDoc = NULL; - if (LIBREOFFICEKIT_HAS(_pOffice, documentLoadWithOptions)) - pDoc = _pOffice->pClass->documentLoadWithOptions(_pOffice, pUrl, pFilterOptions); + if (LIBREOFFICEKIT_HAS(mpThis, documentLoadWithOptions)) + pDoc = mpThis->pClass->documentLoadWithOptions(mpThis, pUrl, pFilterOptions); else - pDoc = _pOffice->pClass->documentLoad(_pOffice, pUrl); + pDoc = mpThis->pClass->documentLoad(mpThis, pUrl); - return std::make_shared<lok::Document>(pDoc); + if (pDoc == NULL) + return NULL; + + return new Document(pDoc); } /// Returns the last error as a string, the returned pointer has to be freed by the caller. inline char* getError() { - return _pOffice->pClass->getError(_pOffice); + return mpThis->pClass->getError(mpThis); } - /// Frees the memory pointed to by pFree. + /** + * Frees the memory pointed to by pFree. + * + * @since LibreOffice 5.2 + */ inline void freeError(char* pFree) { - _pOffice->pClass->freeError(pFree); + mpThis->pClass->freeError(pFree); } - #if defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY /** + * Registers a callback. LOK will invoke this function when it wants to + * inform the client about events. + * + * @param pCallback the callback to invoke + * @param pData the user data, will be passed to the callback on invocation + */ + inline void registerCallback(LibreOfficeKitCallback pCallback, void* pData) + { + mpThis->pClass->registerCallback(mpThis, pCallback, pData); + } + + /** * Returns details of filter types. * * Example returned string: @@ -587,7 +539,7 @@ public: */ inline char* getFilterTypes() { - return _pOffice->pClass->getFilterTypes(_pOffice); + return mpThis->pClass->getFilterTypes(mpThis); } /** @@ -597,7 +549,7 @@ public: */ void setOptionalFeatures(uint64_t features) { - return _pOffice->pClass->setOptionalFeatures(_pOffice, features); + return mpThis->pClass->setOptionalFeatures(mpThis, features); } /** @@ -621,26 +573,38 @@ public: */ inline void setDocumentPassword(char const* pURL, char const* pPassword) { - _pOffice->pClass->setDocumentPassword(_pOffice, pURL, pPassword); + mpThis->pClass->setDocumentPassword(mpThis, pURL, pPassword); } /** * Get version information of the LOKit process * - * @returns string containing version information in format: - * PRODUCT_NAME PRODUCT_VERSION PRODUCT_EXTENSION BUILD_ID + * @returns JSON string containing version information in format: + * {ProductName: <>, ProductVersion: <>, ProductExtension: <>, BuildId: <>} * - * Eg: LibreOffice 5.3 .0.0 alpha0 <commit hash> + * Eg: {"ProductName": "LibreOffice", + * "ProductVersion": "5.3", + * "ProductExtension": ".0.0.alpha0", + * "BuildId": "<full 40 char git hash>"} */ inline char* getVersionInfo() { - return _pOffice->pClass->getVersionInfo(_pOffice); + return mpThis->pClass->getVersionInfo(mpThis); } #endif // defined LOK_USE_UNSTABLE_API || defined LIBO_INTERNAL_ONLY }; +/// Factory method to create a lok::Office instance. +inline Office* lok_cpp_init(const char* pInstallPath, const char* pUserProfileUrl = NULL) +{ + LibreOfficeKit* pThis = lok_init_2(pInstallPath, pUserProfileUrl); + if (pThis == NULL || pThis->pClass->nSize == 0) + return NULL; + return new ::lok::Office(pThis); +} + } -#endif // INCLUDED_LIBREOFFICEKIT_HPP +#endif // INCLUDED_LIBREOFFICEKIT_LIBREOFFICEKIT_HXX /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/loolwsd/test/WhiteBoxTests.cpp b/loolwsd/test/WhiteBoxTests.cpp index fd3814a..2224619 100644 --- a/loolwsd/test/WhiteBoxTests.cpp +++ b/loolwsd/test/WhiteBoxTests.cpp @@ -154,26 +154,33 @@ class DummyDocument : public IDocumentManager { std::shared_ptr<TileQueue> _tileQueue; std::mutex _mutex; + std::mutex _documentMutex; public: DummyDocument() : _tileQueue(new TileQueue()), - _mutex() + _mutex(), + _documentMutex() { } - std::shared_ptr<lok::Document> onLoad(const std::string& /*sessionId*/, - const std::string& /*jailedFilePath*/, - const std::string& /*userName*/, - const std::string& /*docPassword*/, - const std::string& /*renderOpts*/, - const bool /*haveDocPassword*/) override + bool onLoad(const std::string& /*sessionId*/, + const std::string& /*jailedFilePath*/, + const std::string& /*userName*/, + const std::string& /*docPassword*/, + const std::string& /*renderOpts*/, + const bool /*haveDocPassword*/) override { - return nullptr; + return false; } void onUnload(const ChildSession& /*session*/) override { } + std::shared_ptr<lok::Document> getLOKitDocument() override + { + return nullptr; + } + void notifyViewInfo(const std::vector<int>& /*viewIds*/) override { } @@ -188,6 +195,11 @@ public: return _mutex; } + std::mutex& getDocumentMutex() override + { + return _mutex; + } + std::shared_ptr<TileQueue>& getTileQueue() override { return _tileQueue; _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits