loleaflet/dist/toolbar/toolbar.js | 36 ++++++++++++++++++++ loleaflet/src/layer/tile/TileLayer.js | 10 +++++ loolwsd/ChildProcessSession.cpp | 7 +++ loolwsd/DocumentBroker.cpp | 53 ++++++++++++++++++++++++++++++ loolwsd/DocumentBroker.hpp | 18 +++++++++- loolwsd/LOOLWSD.cpp | 60 ++++++++++++++++++---------------- loolwsd/MasterProcessSession.cpp | 22 +++++++++++- loolwsd/MasterProcessSession.hpp | 13 ++++++- 8 files changed, 188 insertions(+), 31 deletions(-)
New commits: commit b81abc4079047c2735b1f5160f18fffda70aed72 Author: Pranav Kant <pran...@collabora.com> Date: Thu Mar 24 00:48:21 2016 +0530 loleaflet: Edit lock implemention on front-end Change-Id: I97c2b2e0a4ef51c6335cefe3e9ec8f5904deb9f4 diff --git a/loleaflet/dist/toolbar/toolbar.js b/loleaflet/dist/toolbar/toolbar.js index 5f1da68..6e3e174 100644 --- a/loleaflet/dist/toolbar/toolbar.js +++ b/loleaflet/dist/toolbar/toolbar.js @@ -137,6 +137,10 @@ $(function () { { type: 'button', id: 'duplicatepage', img: 'duplicatepage', hint: _("Duplicate Page") }, { type: 'button', id: 'deletepage', img: 'deletepage', hint: _("Delete Page") }, { type: 'html', id: 'right' }, + { type: 'break' }, + { type: 'button', id: 'takeedit', img: 'edit', hint: _("Take edit lock (others can only view)")}, + { type: 'html', id: 'takeedit_text', html: '<div id="takeedit_text">VIEWING</div>' }, + { type: 'break' }, { type: 'button', id: 'prev', img: 'prev', hint: _("Previous page/part") }, { type: 'button', id: 'next', img: 'next', hint: _("Next page/part") }, { type: 'break' }, @@ -271,6 +275,11 @@ function onClick(id) { callback: onSaveAs }); } + else if (id === 'takeedit') { + if (!item.checked) { + map._socket.sendMessage('takeedit'); + } + } else if (id === 'searchprev') { map.search(L.DomUtil.get('search-input').value, true); } @@ -869,6 +878,33 @@ map.on('hyperlinkclicked', function (e) { window.open(e.url, '_blank'); }); +map.on('editlock', function (e) { + var toolbar = w2ui['toolbar-down']; + if (e.value) { + toolbar.check('takeedit'); + toolbar.disable('takeedit'); + toolbar.set('takeedit', {hint: _('You are editing (others can only view)')}); + + $('#takeedit_text') + .w2tag('You are editing now') + .html('EDITING'); + setTimeout(function() { + $('#takeedit_text').w2tag(''); + }, 5000); + } + else { + toolbar.uncheck('takeedit'); + toolbar.enable('takeedit'); + toolbar.set('takeedit', {hint: _('Take edit lock (others can only view)')}); + $('#takeedit_text') + .w2tag('You are viewing now') + .html('VIEWING'); + setTimeout(function() { + $('#takeedit_text').w2tag(''); + }, 5000); + } +}); + $(window).resize(function() { resizeToolbar(); }); diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 202fb0d..5eee647 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -330,6 +330,9 @@ L.TileLayer = L.GridLayer.extend({ else if (textMsg.startsWith('unocommandresult:')) { this._onUnoCommandResultMsg(textMsg); } + else if (textMsg.startsWith('editlock')) { + this._onEditLock(textMsg); + } }, _onCommandValuesMsg: function (textMsg) { @@ -1237,6 +1240,13 @@ L.TileLayer = L.GridLayer.extend({ } }, + _onEditLock: function (textMsg) { + var val = parseInt(textMsg.split(' ')[1]); + if (!isNaN(val)) { + this._map.fire('editlock', {value: val}); + } + }, + _invalidatePreviews: function () { if (this._map._docPreviews && this._previewInvalidations.length > 0) { var toInvalidate = {}; commit b8e9075f23f0f474874c52fd62bd6057de260c86 Author: Pranav Kant <pran...@collabora.com> Date: Wed Mar 23 22:25:28 2016 +0530 bccu#1621: Introduce an edit lock ... which can be taken only one at a time. Others can only view, not edit. When a session with edit lock exits, the edit lock is handed over to the next alive session. Change-Id: I712a4e70369f1d07c1d83af416a0f5c288b05c7d diff --git a/loolwsd/ChildProcessSession.cpp b/loolwsd/ChildProcessSession.cpp index 3c74b15..32bd228 100644 --- a/loolwsd/ChildProcessSession.cpp +++ b/loolwsd/ChildProcessSession.cpp @@ -334,7 +334,12 @@ bool ChildProcessSession::_handleInput(const char *buffer, int length) const std::string firstLine = getFirstLine(buffer, length); StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); - if (tokens[0] == "canceltiles") + if (tokens[0] == "dummymsg") + { + // Just to update the activity of view-only mode + return true; + } + else if (tokens[0] == "canceltiles") { // this command makes sense only on the command queue level, nothing // to do here diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp index 5e35a86..c36067e 100644 --- a/loolwsd/DocumentBroker.cpp +++ b/loolwsd/DocumentBroker.cpp @@ -137,4 +137,57 @@ std::string DocumentBroker::getJailRoot() const return Poco::Path(_childRoot, _jailId).toString(); } +void DocumentBroker::takeEditLock(const std::string id) +{ + std::lock_guard<std::mutex> sessionsLock(_wsSessionsMutex); + for (auto& it: _wsSessions) + { + if (it.first != id) + { + it.second->setEditLock(false); + it.second->sendTextFrame("editlock 0"); + } + else + { + it.second->setEditLock(true); + it.second->sendTextFrame("editlock 1"); + } + } +} + +void DocumentBroker::addWSSession(const std::string id, std::shared_ptr<MasterProcessSession>& ws) +{ + std::lock_guard<std::mutex> sessionsLock(_wsSessionsMutex); + auto ret = _wsSessions.emplace(id, ws); + if (!ret.second) + { + Log::warn("DocumentBroker: Trying to add already existed session."); + } +} + +void DocumentBroker::removeWSSession(const std::string id) +{ + std::lock_guard<std::mutex> sessionsLock(_wsSessionsMutex); + bool bEditLock = false; + auto it = _wsSessions.find(id); + if (it != _wsSessions.end()) + { + if (it->second->isEditLocked()) + bEditLock = true; + + _wsSessions.erase(it); + } + + if (bEditLock) + { + // pass the edit lock to first session in map + it = _wsSessions.begin(); + if (it != _wsSessions.end()) + { + it->second->setEditLock(true); + it->second->sendTextFrame("editlock 1"); + } + } +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/loolwsd/DocumentBroker.hpp b/loolwsd/DocumentBroker.hpp index c2fa43b..3d848de 100644 --- a/loolwsd/DocumentBroker.hpp +++ b/loolwsd/DocumentBroker.hpp @@ -14,10 +14,12 @@ #include <memory> #include <mutex> #include <string> +#include <map> #include <Poco/URI.h> -#include <Util.hpp> +#include "MasterProcessSession.hpp" +#include "Util.hpp" // Forwards. class StorageBase; @@ -68,6 +70,20 @@ public: std::string getJailRoot() const; + /// Ignore input events from all web socket sessions + /// except this one + void takeEditLock(const std::string id); + + void addWSSession(const std::string id, std::shared_ptr<MasterProcessSession>& ws); + + void removeWSSession(const std::string id); + + unsigned getWSSessionsCount() { return _wsSessions.size(); } + +public: + std::map<std::string, std::shared_ptr<MasterProcessSession>> _wsSessions; + std::mutex _wsSessionsMutex; + private: const Poco::URI _uriPublic; const std::string _docKey; diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp index 4f6cb52..8246a01 100644 --- a/loolwsd/LOOLWSD.cpp +++ b/loolwsd/LOOLWSD.cpp @@ -178,9 +178,6 @@ using Poco::XML::NodeList; static std::map<std::string, std::shared_ptr<DocumentBroker>> docBrokers; static std::mutex docBrokersMutex; -static std::unordered_set<std::shared_ptr<MasterProcessSession>> sessions; -static std::mutex sessionsMutex; - /// Handles the filename part of the convert-to POST request payload. class ConvertToPartHandler : public PartHandler { @@ -255,6 +252,7 @@ private: std::shared_ptr<WebSocket> ws; const LOOLSession::Kind kind = LOOLSession::Kind::ToClient; auto session = std::make_shared<MasterProcessSession>(id, kind, ws, docBroker); + session->setEditLock(true); docBroker->incSessions(); lock.unlock(); @@ -455,10 +453,11 @@ private: docBroker->incSessions(); docBrokersLock.unlock(); - std::unique_lock<std::mutex> sessionsLock(sessionsMutex); - sessions.insert(session); - Log::debug("sessions++: " + std::to_string(sessions.size())); - sessionsLock.unlock(); + docBroker->addWSSession(id, session); + unsigned wsSessionsCount = docBroker->getWSSessionsCount(); + Log::warn(docKey + ", ws_sessions++: " + std::to_string(wsSessionsCount)); + if (wsSessionsCount == 1) + session->setEditLock(true); // Request a kit process for this doc. const std::string aMessage = "request " + id + " " + docKey + "\n"; @@ -509,10 +508,9 @@ private: queue.clear(); } - sessionsLock.lock(); - sessions.erase(session); - Log::debug("sessions--: " + std::to_string(sessions.size())); - sessionsLock.unlock(); + docBroker->removeWSSession(id); + wsSessionsCount = docBroker->getWSSessionsCount(); + Log::warn(docKey + ", ws_sessions--: " + std::to_string(wsSessionsCount)); Log::info("Finishing GET request handler for session [" + id + "]. Joining the queue."); queue.put("eof"); @@ -1327,16 +1325,20 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/) Log::debug("30-second check"); last30SecCheck = now; - std::unique_lock<std::mutex> sessionsLock(sessionsMutex); - for (auto& it : sessions) + std::unique_lock<std::mutex> docBrokersLock(docBrokersMutex); + for (auto& brokerIt : docBrokers) { - if (it->_lastMessageTime > it->_idleSaveTime && - it->_lastMessageTime < now - 30) + std::unique_lock<std::mutex> sessionsLock(brokerIt.second->_wsSessionsMutex); + for (auto& sessionIt: brokerIt.second->_wsSessions) { - // Trigger a .uno:Save - Log::info("Idle save triggered for session " + it->getId()); - - it->_idleSaveTime = now; + if (sessionIt.second->_lastMessageTime > sessionIt.second->_idleSaveTime && + sessionIt.second->_lastMessageTime < now - 30) + { + // Trigger a .uno:Save + Log::info("Idle save triggered for session " + sessionIt.second->getId()); + + sessionIt.second->_idleSaveTime = now; + } } } } @@ -1345,16 +1347,20 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/) Log::debug("Five-minute check"); lastFiveMinuteCheck = now; - std::unique_lock<std::mutex> sessionsLock(sessionsMutex); - for (auto& it : sessions) + std::unique_lock<std::mutex> docBrokersLock(docBrokersMutex); + for (auto& brokerIt : docBrokers) { - if (it->_lastMessageTime >= it->_idleSaveTime && - it->_lastMessageTime >= it->_autoSaveTime) + std::unique_lock<std::mutex> sessionsLock(brokerIt.second->_wsSessionsMutex); + for (auto& sessionIt: brokerIt.second->_wsSessions) { - // Trigger a .uno:Save - Log::info("Auto-save triggered for session " + it->getId()); - - it->_autoSaveTime = now; + if (sessionIt.second->_lastMessageTime >= sessionIt.second->_idleSaveTime && + sessionIt.second->_lastMessageTime >= sessionIt.second->_autoSaveTime) + { + // Trigger a .uno:Save + Log::info("Auto-save triggered for session " + sessionIt.second->getId()); + + sessionIt.second->_autoSaveTime = now; + } } } } diff --git a/loolwsd/MasterProcessSession.cpp b/loolwsd/MasterProcessSession.cpp index 90a82af..a494c5e 100644 --- a/loolwsd/MasterProcessSession.cpp +++ b/loolwsd/MasterProcessSession.cpp @@ -232,6 +232,11 @@ bool MasterProcessSession::_handleInput(const char *buffer, int length) else if (tokens[0] == "status:") { _docBroker->tileCache().saveTextFile(std::string(buffer, length), "status.txt"); + + // let clients know if they hold the edit lock + std::string message = "editlock "; + message += std::to_string(peer->isEditLocked()); + forwardToPeer(message.c_str(), message.size()); } else if (tokens[0] == "commandvalues:") { @@ -294,6 +299,11 @@ bool MasterProcessSession::_handleInput(const char *buffer, int length) Log::error(getName() + ": Unexpected request [" + tokens[0] + "]."); assert(false); } + else if (tokens[0] == "takeedit") + { + _docBroker->takeEditLock(getId()); + return true; + } else if (tokens[0] == "load") { if (_docURL != "") @@ -389,7 +399,12 @@ bool MasterProcessSession::_handleInput(const char *buffer, int length) _docBroker->tileCache().removeFile("status.txt"); } - if (tokens[0] != "requestloksession") + if (_kind == Kind::ToClient && !isEditLocked()) + { + std::string dummyFrame = "dummymsg"; + forwardToPeer(dummyFrame.c_str(), dummyFrame.size()); + } + else if (tokens[0] != "requestloksession") { forwardToPeer(buffer, length); } @@ -460,6 +475,10 @@ bool MasterProcessSession::getStatus(const char *buffer, int length) if (status.size() > 0) { sendTextFrame(status); + // let clients know if they hold the edit lock + std::string message = "editlock "; + message += std::to_string(isEditLocked()); + sendTextFrame(message); return true; } @@ -812,6 +831,7 @@ void MasterProcessSession::forwardToPeer(const char *buffer, int length) Log::error(getName() + ": no peer to forward to."); return; } + peer->sendBinaryFrame(buffer, length); } diff --git a/loolwsd/MasterProcessSession.hpp b/loolwsd/MasterProcessSession.hpp index be09337..7b57709 100644 --- a/loolwsd/MasterProcessSession.hpp +++ b/loolwsd/MasterProcessSession.hpp @@ -14,10 +14,11 @@ #include <Poco/Random.h> -#include "DocumentBroker.hpp" #include "LOOLSession.hpp" #include "TileCache.hpp" +class DocumentBroker; + class MasterProcessSession final : public LOOLSession, public std::enable_shared_from_this<MasterProcessSession> { public: @@ -44,6 +45,11 @@ class MasterProcessSession final : public LOOLSession, public std::enable_shared std::shared_ptr<DocumentBroker> getDocumentBroker() const { return _docBroker; } + void setEditLock(const bool value) { _bEditLock = value; } + + bool isEditLocked() const { return _bEditLock; } + +public: // Sessions to pre-spawned child processes that have connected but are not yet assigned a // document to work on. static std::map<std::string, std::shared_ptr<MasterProcessSession>> AvailableChildSessions; @@ -88,6 +94,11 @@ class MasterProcessSession final : public LOOLSession, public std::enable_shared /// Kind::ToClient instances store URLs of completed 'save as' documents. MessageQueue _saveAsQueue; std::shared_ptr<DocumentBroker> _docBroker; + + // If this document holds the edit lock. + // An edit lock will only allow the current session to make edits, + // while other session opening the same document can only see + bool _bEditLock = false; }; #endif _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits