common/Util.cpp | 9 ++ common/Util.hpp | 5 + loleaflet/src/control/Control.DownloadProgress.js | 2 loleaflet/src/layer/tile/TileLayer.js | 4 - loleaflet/src/map/Clipboard.js | 42 +++++++++---- loolwsd.xml.in | 4 + wsd/ClientSession.cpp | 5 + wsd/DocumentBroker.cpp | 68 ++++++++++++++++------ wsd/DocumentBroker.hpp | 4 + wsd/LOOLWSD.cpp | 1 wsd/Storage.cpp | 21 ++++++ wsd/Storage.hpp | 8 ++ 12 files changed, 136 insertions(+), 37 deletions(-)
New commits: commit 84c218786b29f7e8b0d3688980a09043041138a2 Author: Michael Meeks <[email protected]> AuthorDate: Wed Nov 27 19:05:57 2019 +0000 Commit: Michael Meeks <[email protected]> CommitDate: Wed Nov 27 19:17:27 2019 +0000 fix calc simple, single cell content copy; add origin to plain text. Change-Id: I7a6b0c90a74f6e3a91a840bf77c0935a300321f2 diff --git a/loleaflet/src/control/Control.DownloadProgress.js b/loleaflet/src/control/Control.DownloadProgress.js index 8df498997..2fce6a714 100644 --- a/loleaflet/src/control/Control.DownloadProgress.js +++ b/loleaflet/src/control/Control.DownloadProgress.js @@ -159,7 +159,7 @@ L.Control.DownloadProgress = L.Control.extend({ var idx = text.indexOf('<!DOCTYPE HTML'); if (idx > 0) text = text.substring(idx, text.length); - that._map._clip.setTextSelectionContent(text); + that._map._clip.setTextSelectionHTML(text); }; // TODO: failure to parse ? ... reader.readAsText(response); diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 25cf5ba5a..1be76a03c 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -597,7 +597,7 @@ L.TileLayer = L.GridLayer.extend({ this._onTextSelectionMsg(textMsg); } else if (textMsg.startsWith('textselectioncontent:')) { - this._map._clip.setTextSelectionContent(textMsg.substr(22)); + this._map._clip.setTextSelectionHTML(textMsg.substr(22)); } else if (textMsg.startsWith('textselectionend:')) { this._onTextSelectionEndMsg(textMsg); @@ -733,7 +733,7 @@ L.TileLayer = L.GridLayer.extend({ // message is received from lowsd, *then* a 'celladdress' message. var address = textMsg.substring(13); if (!this._map['wopi'].DisableCopy) { - this._map._clip.setTextSelectionContent(this._lastFormula); + this._map._clip.setTextSelectionText(this._lastFormula); } this._map.fire('celladdress', {address: address}); }, diff --git a/loleaflet/src/map/Clipboard.js b/loleaflet/src/map/Clipboard.js index 1acfff606..2808f2690 100644 --- a/loleaflet/src/map/Clipboard.js +++ b/loleaflet/src/map/Clipboard.js @@ -109,21 +109,30 @@ L.Clipboard = L.Class.extend({ return text.indexOf(this._getHtmlStubMarker()) > 0; }, + // wrap some content with our stub magic + _originWrapBody: function(body, isStub) { + var encodedOrigin = encodeURIComponent(this.getMetaPath()); + var text = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\n' + + '<html>\n' + + ' <head>\n'; + if (isStub) + text += ' ' + this._getHtmlStubMarker() + '\n'; + text += ' <meta http-equiv="content-type" content="text/html; charset=utf-8"/>\n' + + ' <meta name="origin" content="' + encodedOrigin + '"/>\n' + + ' </head>\n' + + body + + '</html>'; + return text; + }, + + // what an empty clipboard has on it _getStubHtml: function() { var lang = 'en_US'; // FIXME: l10n - var encodedOrigin = encodeURIComponent(this.getMetaPath()); - var stub = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">\n' + - '<html>\n' + - ' <head>\n' + - ' ' + this._getHtmlStubMarker() + '\n' + - ' <meta http-equiv="content-type" content="text/html; charset=utf-8"/>\n' + - ' <meta name="origin" content="' + encodedOrigin + '"/>\n' + - ' </head>\n' + + return this._substProductName(this._originWrapBody( ' <body lang="' + lang + '" dir="ltr">\n' + ' <p>' + _('To paste outside %productName, please first click the \'download\' button') + '</p>\n' + - ' </body>\n' + - '</html>'; - return this._substProductName(stub); + ' </body>\n', true + )); }, _getMetaOrigin: function (html) { @@ -688,9 +697,16 @@ L.Clipboard = L.Class.extend({ }, // textselectioncontent: message - setTextSelectionContent: function(text) { + setTextSelectionHTML: function(html) { + this._selectionType = 'text'; + this._selectionContent = html; + }, + + // sets the selection to some (cell formula) text) + setTextSelectionText: function(text) { this._selectionType = 'text'; - this._selectionContent = text; + this._selectionContent = this._originWrapBody( + '<body>' + text + '</body>'); }, // complexselection: message diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp index db51d5836..51ec92ef5 100644 --- a/wsd/ClientSession.cpp +++ b/wsd/ClientSession.cpp @@ -1006,7 +1006,10 @@ void ClientSession::postProcessCopyPayload(std::shared_ptr<Message> payload) { // Insert our meta origin if we can payload->rewriteDataBody([=](std::vector<char>& data) { - const size_t pos = Util::findInVector(data, "<meta name=\"generator\" content=\""); + size_t pos = Util::findInVector(data, "<meta name=\"generator\" content=\""); + + if (pos == std::string::npos) + pos = Util::findInVector(data, "<meta http-equiv=\"content-type\" content=\"text/html;"); // cf. TileLayer.js /_dataTransferToDocument/ if (pos != std::string::npos) // assume text/html commit 0d97efbfccc680e961086705e8a855a38a7af6c4 Author: Michael Meeks <[email protected]> AuthorDate: Wed Nov 27 08:43:05 2019 +0000 Commit: Michael Meeks <[email protected]> CommitDate: Wed Nov 27 19:17:27 2019 +0000 locking: renew lock after timeout. Change-Id: I6191ee34239b978292aeb6795be74312a954e240 diff --git a/loolwsd.xml.in b/loolwsd.xml.in index 416dd9b2a..d8e19b9c9 100644 --- a/loolwsd.xml.in +++ b/loolwsd.xml.in @@ -124,6 +124,10 @@ <host desc="Regex pattern of hostname to allow or deny." allow="false">192\.168\.1\.1</host> <max_file_size desc="Maximum document size in bytes to load. 0 for unlimited." type="uint">0</max_file_size> <reuse_cookies desc="When enabled, cookies from the browser will be captured and set on WOPI requests." type="bool" default="false">false</reuse_cookies> + <locking desc="Locking settings"> + <refresh desc="How frequently we should re-acquire a lock with the storage server, in seconds (default 15 mins) or 0 for no refresh" + type="int" default="900">900</refresh> + </locking> </wopi> <webdav desc="Allow/deny webdav storage. Mutually exclusive with wopi." allow="false"> <host desc="Hostname to allow" allow="false">localhost</host> diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index 8cb159e4d..ddfaa922d 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -323,6 +323,9 @@ void DocumentBroker::pollThread() adminRecv = recv; LOG_TRC("Doc [" << _docKey << "] added stats sent: " << sent << ", recv: " << recv << " bytes to totals."); } + + if (_storage && _lockCtx->needsRefresh(now)) + refreshLock(); #endif if (isSaving() && @@ -1078,23 +1081,10 @@ void DocumentBroker::setLoaded() } } -bool DocumentBroker::autoSave(const bool force, const bool dontSaveIfUnmodified) +std::string DocumentBroker::getWriteableSessionId() const { assertCorrectThread(); - LOG_TRC("autoSave(): forceful? " << force); - if (_sessions.empty() || _storage == nullptr || !_isLoaded || - !_childProcess->isAlive() || (!_isModified && !force)) - { - // Nothing to do. - LOG_TRC("Nothing to autosave [" << _docKey << "]."); - return false; - } - - // Remember the last save time, since this is the predicate. - LOG_TRC("Checking to autosave [" << _docKey << "]."); - - // Which session to use when auto saving ? std::string savingSessionId; for (auto& sessionIt : _sessions) { @@ -1113,6 +1103,45 @@ bool DocumentBroker::autoSave(const bool force, const bool dontSaveIfUnmodified) break; } } + return savingSessionId; +} + +void DocumentBroker::refreshLock() +{ + assertCorrectThread(); + + std::string savingSessionId = getWriteableSessionId(); + LOG_TRC("Refresh lock " << _lockCtx->_lockToken << " with session " << savingSessionId); + + auto it = _sessions.find(savingSessionId); + if (it == _sessions.end()) + LOG_ERR("No write-able session to refresh lock with"); + else + { + std::shared_ptr<ClientSession> session = it->second; + if (!session || !_storage->updateLockState(session->getAuthorization(), *_lockCtx, true)) + LOG_ERR("Failed to refresh lock"); + } +} + +bool DocumentBroker::autoSave(const bool force, const bool dontSaveIfUnmodified) +{ + assertCorrectThread(); + + LOG_TRC("autoSave(): forceful? " << force); + if (_sessions.empty() || _storage == nullptr || !_isLoaded || + !_childProcess->isAlive() || (!_isModified && !force)) + { + // Nothing to do. + LOG_TRC("Nothing to autosave [" << _docKey << "]."); + return false; + } + + // Remember the last save time, since this is the predicate. + LOG_TRC("Checking to autosave [" << _docKey << "]."); + + // Which session to use when auto saving ? + std::string savingSessionId = getWriteableSessionId(); bool sent = false; if (force) @@ -2225,6 +2254,7 @@ void DocumentBroker::dumpState(std::ostream& os) os << "\n idle time: " << getIdleTimeSecs(); os << "\n cursor " << _cursorPosX << ", " << _cursorPosY << "( " << _cursorWidth << "," << _cursorHeight << ")\n"; + _lockCtx->dumpState(os); if (_tileCache) _tileCache->dumpState(os); diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp index f0a1ac061..91daee6a2 100644 --- a/wsd/DocumentBroker.hpp +++ b/wsd/DocumentBroker.hpp @@ -369,6 +369,10 @@ public: size_t getMemorySize() const; private: + /// get the session id of a session that can write the document for save / locking. + std::string getWriteableSessionId() const; + + void refreshLock(); /// Loads a document from the public URI into the jail. bool load(const std::shared_ptr<ClientSession>& session, const std::string& jailId); diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp index c3e22d240..221d179ef 100644 --- a/wsd/LOOLWSD.cpp +++ b/wsd/LOOLWSD.cpp @@ -860,6 +860,7 @@ void LOOLWSD::initialize(Application& self) { "storage.wopi.host[0][@allow]", "true" }, { "storage.wopi.max_file_size", "0" }, { "storage.wopi[@allow]", "true" }, + { "storage.wopi.locking.refresh", "900" }, { "sys_template_path", "systemplate" }, { "trace.path[@compress]", "true" }, { "trace.path[@snapshot]", "false" }, diff --git a/wsd/Storage.cpp b/wsd/Storage.cpp index efe0fc130..e8b769d3f 100644 --- a/wsd/Storage.cpp +++ b/wsd/Storage.cpp @@ -472,6 +472,24 @@ void LockContext::initSupportsLocks() _lockToken = "lool-lock" + Util::rng::getHexString(8); } +bool LockContext::needsRefresh(const std::chrono::steady_clock::time_point &now) const +{ + static int refreshSeconds = LOOLWSD::getConfigValue<int>("storage.wopi.locking.refresh", 900); + return _supportsLocks && _isLocked && refreshSeconds > 0 && + std::chrono::duration_cast<std::chrono::seconds> + (now - _lastLockTime).count() >= refreshSeconds; +} + +void LockContext::dumpState(std::ostream& os) +{ + if (!_supportsLocks) + return; + os << " lock:\n"; + os << " locked: " << _isLocked << "\n"; + os << " token: '" << _lockToken << "'\n"; + os << " last locked: " << Util::getSteadyClockAsString(_lastLockTime) << "\n"; +} + std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Authorization& auth, LockContext &lockCtx) { // update the access_token to the one matching to the session @@ -734,11 +752,12 @@ bool WopiStorage::updateLockState(const Authorization &auth, LockContext &lockCt if (response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK) { lockCtx._isLocked = lock; + lockCtx._lastLockTime = std::chrono::steady_clock::now(); return true; } else { - LOG_WRN("Un-successfull " << wopiLog << " with status " << response.getStatus() << + LOG_WRN("Un-successful " << wopiLog << " with status " << response.getStatus() << " and response: " << responseString); } } diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp index cad148c7e..1e6eb18d3 100644 --- a/wsd/Storage.hpp +++ b/wsd/Storage.hpp @@ -13,6 +13,7 @@ #include <set> #include <string> +#include <chrono> #include <Poco/URI.h> #include <Poco/Util/Application.h> @@ -34,11 +35,18 @@ struct LockContext bool _isLocked; /// Name if we need it to use consistently for locking std::string _lockToken; + /// Time of last successful lock (re-)acquisition + std::chrono::steady_clock::time_point _lastLockTime; LockContext() : _supportsLocks(false), _isLocked(false) { } /// one-time setup for supporting locks & create token void initSupportsLocks(); + + /// do we need to refresh our lock ? + bool needsRefresh(const std::chrono::steady_clock::time_point &now) const; + + void dumpState(std::ostream& os); }; /// Base class of all Storage abstractions. commit e3864a060e95f224edfed0cd35d4f12143bfba56 Author: Michael Meeks <[email protected]> AuthorDate: Wed Nov 27 09:12:59 2019 +0000 Commit: Michael Meeks <[email protected]> CommitDate: Wed Nov 27 19:17:27 2019 +0000 Dump DocumentBroker state more completely. Change-Id: I3477fe70ba25d6e9a95c12f30138c3353994e384 diff --git a/common/Util.cpp b/common/Util.cpp index 931de51d2..4dc0bdadf 100644 --- a/common/Util.cpp +++ b/common/Util.cpp @@ -900,6 +900,15 @@ namespace Util return timestamp; } + + std::string getSteadyClockAsString(const std::chrono::steady_clock::time_point &time) + { + auto now = std::chrono::steady_clock::now(); + const std::time_t t = std::chrono::system_clock::to_time_t( + std::chrono::time_point_cast<std::chrono::seconds>( + std::chrono::system_clock::now() + (time - now))); + return std::ctime(&t); + } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/common/Util.hpp b/common/Util.hpp index 66ce0b5b1..5dc39f942 100644 --- a/common/Util.hpp +++ b/common/Util.hpp @@ -939,9 +939,12 @@ int main(int argc, char**argv) /// Convert a time_point to iso8601 formatted string. std::string time_point_to_iso8601(std::chrono::system_clock::time_point tp); - //// Convert time from ISO8061 fraction format + /// Convert time from ISO8061 fraction format std::chrono::system_clock::time_point iso8601ToTimestamp(const std::string& iso8601Time, const std::string& logName); + /// conversion from steady_clock for debugging / tracing + std::string getSteadyClockAsString(const std::chrono::steady_clock::time_point &time); + /// Automatically execute code at end of current scope. /// Used for exception-safe code. class ScopeGuard diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp index 7d669e00c..8cb159e4d 100644 --- a/wsd/DocumentBroker.cpp +++ b/wsd/DocumentBroker.cpp @@ -2217,10 +2217,12 @@ void DocumentBroker::dumpState(std::ostream& os) os << "\n doc key: " << _docKey; os << "\n doc id: " << _docId; os << "\n num sessions: " << _sessions.size(); - const std::time_t t = std::chrono::system_clock::to_time_t( - std::chrono::time_point_cast<std::chrono::seconds>( - std::chrono::system_clock::now() + (_lastSaveTime - now))); - os << "\n last saved: " << std::ctime(&t); + os << "\n last saved: " << Util::getSteadyClockAsString(_lastSaveTime); + os << "\n last save request: " << Util::getSteadyClockAsString(_lastSaveRequestTime); + os << "\n last save response: " << Util::getSteadyClockAsString(_lastSaveResponseTime); + os << "\n last modifed: " << Util::getHttpTime(_documentLastModifiedTime); + os << "\n file last modifed: " << Util::getHttpTime(_lastFileModifiedTime); + os << "\n idle time: " << getIdleTimeSecs(); os << "\n cursor " << _cursorPosX << ", " << _cursorPosY << "( " << _cursorWidth << "," << _cursorHeight << ")\n"; if (_tileCache) _______________________________________________ Libreoffice-commits mailing list [email protected] https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
