wsd/DocumentBroker.cpp |   33 ++++++++++++----
 wsd/DocumentBroker.hpp |    2 +
 wsd/Storage.cpp        |   96 ++++++++++++++++++++++++++++++++++++++++++++-----
 wsd/Storage.hpp        |   48 +++++++++++++++++++-----
 4 files changed, 154 insertions(+), 25 deletions(-)

New commits:
commit cabd30a1b83c5abea913dcbe8ce869d6b12ea404
Author:     Michael Meeks <michael.me...@collabora.com>
AuthorDate: Tue Nov 19 22:51:45 2019 +0000
Commit:     Michael Meeks <michael.me...@collabora.com>
CommitDate: Thu Nov 21 14:24:58 2019 +0000

    Initial wopi-like locking implementation.
    
    Change-Id: I3e16cfc40dbd29a24016b09e62afc9ec488300c7

diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 6b393d277..ef1ad7ed6 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -189,6 +189,7 @@ DocumentBroker::DocumentBroker(const std::string& uri,
     _poll(new DocumentBrokerPoll("docbroker_" + _docId, *this)),
     _stop(false),
     _closeReason("stopped"),
+    _lockCtx(new LockContext()),
     _tileVersion(0),
     _debugRenderedTileCount(0)
 {
@@ -563,7 +564,8 @@ bool DocumentBroker::load(const 
std::shared_ptr<ClientSession>& session, const s
     WopiStorage* wopiStorage = dynamic_cast<WopiStorage*>(_storage.get());
     if (wopiStorage != nullptr)
     {
-        std::unique_ptr<WopiStorage::WOPIFileInfo> wopifileinfo = 
wopiStorage->getWOPIFileInfo(session->getAuthorization());
+        std::unique_ptr<WopiStorage::WOPIFileInfo> wopifileinfo =
+            wopiStorage->getWOPIFileInfo(session->getAuthorization(), 
*_lockCtx);
         userId = wopifileinfo->getUserId();
         username = wopifileinfo->getUsername();
         userExtraInfo = wopifileinfo->getUserExtraInfo();
@@ -722,7 +724,11 @@ bool DocumentBroker::load(const 
std::shared_ptr<ClientSession>& session, const s
     // Let's load the document now, if not loaded.
     if (!_storage->isLoaded())
     {
-        std::string localPath = 
_storage->loadStorageFileToLocal(session->getAuthorization(), templateSource);
+        std::string localPath = _storage->loadStorageFileToLocal(
+            session->getAuthorization(), *_lockCtx, templateSource);
+
+        if (!_storage->updateLockState(session->getAuthorization(), *_lockCtx, 
true))
+            LOG_ERR("Failed to lock!");
 
 #if !MOBILEAPP
         // Check if we have a prefilter "plugin" for this document format
@@ -955,7 +961,8 @@ bool DocumentBroker::saveToStorageInternal(const 
std::string& sessionId, bool su
     LOG_DBG("Persisting [" << _docKey << "] after saving to URI [" << 
uriAnonym << "].");
 
     assert(_storage && _tileCache);
-    StorageBase::SaveResult storageSaveResult = 
_storage->saveLocalFileToStorage(auth, saveAsPath, saveAsFilename, isRename);
+    StorageBase::SaveResult storageSaveResult = 
_storage->saveLocalFileToStorage(
+        auth, *_lockCtx, saveAsPath, saveAsFilename, isRename);
     if (storageSaveResult.getResult() == StorageBase::SaveResult::OK)
     {
         if (!isSaveAs && !isRename)
@@ -1299,18 +1306,19 @@ size_t DocumentBroker::removeSession(const std::string& 
id)
             LOG_ERR("Invalid or unknown session [" << id << "] to remove.");
             return _sessions.size();
         }
+        std::shared_ptr<ClientSession> session = it->second;
 
         // Last view going away, can destroy.
         _markToDestroy = (_sessions.size() <= 1);
 
-        const bool lastEditableSession = !it->second->isReadOnly() && 
!haveAnotherEditableSession(id);
+        const bool lastEditableSession = !session->isReadOnly() && 
!haveAnotherEditableSession(id);
         static const bool dontSaveIfUnmodified = 
!LOOLWSD::getConfigValue<bool>("per_document.always_save_on_exit", false);
 
         LOG_INF("Removing session ["
                 << id << "] on docKey [" << _docKey << "]. Have " << 
_sessions.size()
-                << " sessions. IsReadOnly: " << it->second->isReadOnly()
-                << ", IsViewLoaded: " << it->second->isViewLoaded() << ", 
IsWaitDisconnected: "
-                << it->second->inWaitDisconnected() << ", MarkToDestroy: " << 
_markToDestroy
+                << " sessions. IsReadOnly: " << session->isReadOnly()
+                << ", IsViewLoaded: " << session->isViewLoaded() << ", 
IsWaitDisconnected: "
+                << session->inWaitDisconnected() << ", MarkToDestroy: " << 
_markToDestroy
                 << ", LastEditableSession: " << lastEditableSession
                 << ", dontSaveIfUnmodified: " << dontSaveIfUnmodified);
 
@@ -1341,7 +1349,16 @@ void DocumentBroker::disconnectSessionInternal(const 
std::string& id)
             LOOLWSD::dumpEndSessionTrace(getJailId(), id, _uriOrig);
 #endif
 
-            LOG_TRC("Disconnect session internal " << id);
+            LOG_TRC("Disconnect session internal " << id <<
+                    " destroy? " << _markToDestroy <<
+                    " locked? " << _lockCtx->_isLocked);
+
+            if (_markToDestroy && // last session to remove; FIXME: Editable?
+                _lockCtx->_isLocked && _storage)
+            {
+                if (!_storage->updateLockState(it->second->getAuthorization(), 
*_lockCtx, false))
+                    LOG_ERR("Failed to unlock!");
+            }
 
             bool hardDisconnect;
             if (it->second->inWaitDisconnected())
diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp
index 944120ca8..8641fe123 100644
--- a/wsd/DocumentBroker.hpp
+++ b/wsd/DocumentBroker.hpp
@@ -36,6 +36,7 @@
 // Forwards.
 class PrisonerRequestDispatcher;
 class DocumentBroker;
+class LockContext;
 class StorageBase;
 class TileCache;
 class Message;
@@ -488,6 +489,7 @@ private:
     std::unique_ptr<DocumentBrokerPoll> _poll;
     std::atomic<bool> _stop;
     std::string _closeReason;
+    std::unique_ptr<LockContext> _lockCtx;
 
     /// Versioning is used to prevent races between
     /// painting and invalidation.
diff --git a/wsd/Storage.cpp b/wsd/Storage.cpp
index 6b5b87daf..c68a5752f 100644
--- a/wsd/Storage.cpp
+++ b/wsd/Storage.cpp
@@ -300,7 +300,7 @@ std::unique_ptr<LocalStorage::LocalFileInfo> 
LocalStorage::getLocalFileInfo()
     return std::unique_ptr<LocalStorage::LocalFileInfo>(new 
LocalFileInfo({"localhost" + std::to_string(LastLocalStorageId), "LocalHost#" + 
std::to_string(LastLocalStorageId++)}));
 }
 
-std::string LocalStorage::loadStorageFileToLocal(const Authorization& 
/*auth*/, const std::string& /*templateUri*/)
+std::string LocalStorage::loadStorageFileToLocal(const Authorization& 
/*auth*/, LockContext & /*lockCtx*/, const std::string& /*templateUri*/)
 {
 #if !MOBILEAPP
     // /chroot/jailId/user/doc/childId/file.ext
@@ -364,7 +364,7 @@ std::string LocalStorage::loadStorageFileToLocal(const 
Authorization& /*auth*/,
 
 }
 
-StorageBase::SaveResult LocalStorage::saveLocalFileToStorage(const 
Authorization& /*auth*/, const std::string& /*saveAsPath*/, const std::string& 
/*saveAsFilename*/, bool /*isRename*/)
+StorageBase::SaveResult LocalStorage::saveLocalFileToStorage(const 
Authorization& /*auth*/, LockContext &/*lockCtx*/, const std::string& 
/*saveAsPath*/, const std::string& /*saveAsFilename*/, bool /*isRename*/)
 {
     try
     {
@@ -462,7 +462,17 @@ std::map<std::string, std::string> GetQueryParams(const 
Poco::URI& uri)
 
 } // anonymous namespace
 
-std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const 
Authorization& auth)
+void LockContext::initSupportsLocks()
+{
+    if (_supportsLocks)
+        return;
+
+    // first time token setup
+    _supportsLocks = true;
+    _lockToken = "lool-lock" + Util::rng::getHexString(8);
+}
+
+std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const 
Authorization& auth, LockContext &lockCtx)
 {
     // update the access_token to the one matching to the session
     Poco::URI uriObject(getUri());
@@ -659,6 +669,9 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> 
WopiStorage::getWOPIFileInfo(const Au
     const std::chrono::system_clock::time_point modifiedTime = 
Util::iso8601ToTimestamp(lastModifiedTime, "LastModifiedTime");
     setFileInfo(FileInfo({filename, ownerId, modifiedTime, size}));
 
+    if (supportsLocks)
+        lockCtx.initSupportsLocks();
+
     return std::unique_ptr<WopiStorage::WOPIFileInfo>(new WOPIFileInfo(
         {userId, obfuscatedUserId, userName, userExtraInfo, watermarkText, 
templateSaveAs, templateSource,
          canWrite, postMessageOrigin, hidePrintOption, hideSaveOption, 
hideExportOption,
@@ -669,8 +682,71 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> 
WopiStorage::getWOPIFileInfo(const Au
          userCanRename, callDuration}));
 }
 
+bool WopiStorage::updateLockState(const Authorization &auth, LockContext 
&lockCtx, bool lock)
+{
+    if (!lockCtx._supportsLocks)
+        return true;
+
+    Poco::URI uriObject(getUri());
+    auth.authorizeURI(uriObject);
+
+    std::string reuseStorageCookies = getReuseCookies(uriObject);
+
+    Poco::URI uriObjectAnonym(getUri());
+    uriObjectAnonym.setPath(LOOLWSD::anonymizeUrl(uriObjectAnonym.getPath()));
+    const std::string uriAnonym = uriObjectAnonym.toString();
+
+    const std::string wopiLog(lock ? "WOPI::Lock" : "WOPI::Unlock");
+    LOG_DBG(wopiLog << " requesting: " << uriAnonym);
+
+    try
+    {
+        std::unique_ptr<Poco::Net::HTTPClientSession> 
psession(getHTTPClientSession(uriObject));
+
+        Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, 
uriObject.getPathAndQuery(), Poco::Net::HTTPMessage::HTTP_1_1);
+        request.set("User-Agent", WOPI_AGENT_STRING);
+        auth.authorizeRequest(request);
+
+        request.set("X-WOPI-Override", lock ? "LOCK" : "UNLOCK");
+        request.set("X-WOPI-Lock", lockCtx._lockToken);
+        if (!getExtendedData().empty())
+            request.set("X-LOOL-WOPI-ExtendedData", getExtendedData());
+        addStorageDebugCookie(request);
+        if (_reuseCookies)
+            addStorageReuseCookie(request, reuseStorageCookies);
+
+        psession->sendRequest(request);
+        Poco::Net::HTTPResponse response;
+        std::istream& rs = psession->receiveResponse(response);
+
+        std::ostringstream oss;
+        Poco::StreamCopier::copyStream(rs, oss);
+        std::string responseString = oss.str();
+
+        LOG_INF(wopiLog << " response: " << responseString <<
+                " status " << response.getStatus());
+
+        if (response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK)
+        {
+            lockCtx._isLocked = lock;
+            return true;
+        }
+        else
+        {
+            LOG_WRN("Un-successfull " << wopiLog << " with status " << 
response.getStatus() <<
+                    " and response: " << responseString);
+        }
+    }
+    catch (const Poco::Exception& pexc)
+    {
+        LOG_ERR("Cannot " << wopiLog << " uri [" << uriAnonym << "]. Error: " 
<<
+                pexc.displayText() << (pexc.nested() ? " (" + 
pexc.nested()->displayText() + ")" : ""));
+    }
+    return false;
+}
+
 /// uri format: http://server/<...>/wopi*/files/<id>/content
-std::string WopiStorage::loadStorageFileToLocal(const Authorization& auth, 
const std::string& templateUri)
+std::string WopiStorage::loadStorageFileToLocal(const Authorization& auth, 
LockContext &/* lockCtx */, const std::string& templateUri)
 {
     // WOPI URI to download files ends in '/contents'.
     // Add it here to get the payload instead of file info.
@@ -744,7 +820,6 @@ std::string WopiStorage::loadStorageFileToLocal(const 
Authorization& auth, const
             ofs.close();
             LOG_INF("WOPI::GetFile downloaded " << 
getFileSize(getRootFilePath()) << " bytes from [" <<
                     uriAnonym << "] -> " << getRootFilePathAnonym() << " in " 
<< diff.count() << "s");
-
             setLoaded(true);
 
             // Now return the jailed path.
@@ -761,7 +836,7 @@ std::string WopiStorage::loadStorageFileToLocal(const 
Authorization& auth, const
     return "";
 }
 
-StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const 
Authorization& auth, const std::string& saveAsPath, const std::string& 
saveAsFilename, const bool isRename)
+StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const 
Authorization& auth, LockContext &lockCtx, const std::string& saveAsPath, const 
std::string& saveAsFilename, const bool isRename)
 {
     // TODO: Check if this URI has write permission (canWrite = true)
 
@@ -794,6 +869,8 @@ StorageBase::SaveResult 
WopiStorage::saveLocalFileToStorage(const Authorization&
         {
             // normal save
             request.set("X-WOPI-Override", "PUT");
+            if (lockCtx._supportsLocks)
+                request.set("X-WOPI-Lock", lockCtx._lockToken);
             request.set("X-LOOL-WOPI-IsModifiedByUser", isUserModified()? 
"true": "false");
             request.set("X-LOOL-WOPI-IsAutosave", getIsAutosave()? "true": 
"false");
             request.set("X-LOOL-WOPI-IsExitSave", isExitSave()? "true": 
"false");
@@ -974,14 +1051,17 @@ StorageBase::SaveResult 
WopiStorage::saveLocalFileToStorage(const Authorization&
     return saveResult;
 }
 
-std::string WebDAVStorage::loadStorageFileToLocal(const Authorization& 
/*auth*/, const std::string& /*templateUri*/)
+std::string WebDAVStorage::loadStorageFileToLocal(
+    const Authorization& /*auth*/, LockContext &/*lockCtx*/, const 
std::string& /*templateUri*/)
 {
     // TODO: implement webdav GET.
     setLoaded(true);
     return getUri().toString();
 }
 
-StorageBase::SaveResult WebDAVStorage::saveLocalFileToStorage(const 
Authorization& /*auth*/, const std::string& /*saveAsPath*/, const std::string& 
/*saveAsFilename*/, bool /*isRename*/)
+StorageBase::SaveResult WebDAVStorage::saveLocalFileToStorage(
+    const Authorization& /*auth*/, LockContext &/*lockCtx*/, const 
std::string& /*saveAsPath*/,
+    const std::string& /*saveAsFilename*/, bool /*isRename*/)
 {
     // TODO: implement webdav PUT.
     return StorageBase::SaveResult(StorageBase::SaveResult::OK);
diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp
index 3632cd6f3..655e089b4 100644
--- a/wsd/Storage.hpp
+++ b/wsd/Storage.hpp
@@ -24,10 +24,28 @@
 #include "Util.hpp"
 #include <common/Authorization.hpp>
 
+/// Represents whether the underlying file is locked
+/// and with what token.
+struct LockContext
+{
+    /// Do we have support for locking for a storage.
+    bool        _supportsLocks;
+    /// Do we own the (leased) lock currently
+    bool        _isLocked;
+    /// Name if we need it to use consistently for locking
+    std::string _lockToken;
+
+    LockContext() : _supportsLocks(false), _isLocked(false) { }
+
+    /// one-time setup for supporting locks & create token
+    void initSupportsLocks();
+};
+
 /// Base class of all Storage abstractions.
 class StorageBase
 {
 public:
+
     /// Represents basic file's attributes.
     /// Used for local and network files.
     class FileInfo
@@ -190,13 +208,16 @@ public:
 
     std::string getFileExtension() const { return 
Poco::Path(_fileInfo.getFilename()).getExtension(); }
 
+    /// Update the locking state (check-in/out) of the associated file
+    virtual bool updateLockState(const Authorization &auth, LockContext 
&lockCtx, bool lock) = 0;
+
     /// Returns a local file path for the given URI.
     /// If necessary copies the file locally first.
-    virtual std::string loadStorageFileToLocal(const Authorization& auth, 
const std::string& templateUri) = 0;
+    virtual std::string loadStorageFileToLocal(const Authorization& auth, 
LockContext &lockCtx, const std::string& templateUri) = 0;
 
     /// Writes the contents of the file back to the source.
     /// @param savedFile When the operation was saveAs, this is the path to 
the file that was saved.
-    virtual SaveResult saveLocalFileToStorage(const Authorization& auth, const 
std::string& saveAsPath, const std::string& saveAsFilename, const bool 
isRename) = 0;
+    virtual SaveResult saveLocalFileToStorage(const Authorization& auth, 
LockContext &lockCtx, const std::string& saveAsPath, const std::string& 
saveAsFilename, const bool isRename) = 0;
 
     static size_t getFileSize(const std::string& filename);
 
@@ -283,9 +304,11 @@ public:
     /// obtained using getFileInfo method
     std::unique_ptr<LocalFileInfo> getLocalFileInfo();
 
-    std::string loadStorageFileToLocal(const Authorization& auth, const 
std::string& templateUri) override;
+    bool updateLockState(const Authorization &, LockContext &, bool) override 
{ return true; }
+
+    std::string loadStorageFileToLocal(const Authorization& auth, LockContext 
&lockCtx, const std::string& templateUri) override;
 
-    SaveResult saveLocalFileToStorage(const Authorization& auth, const 
std::string& saveAsPath, const std::string& saveAsFilename, const bool 
isRename) override;
+    SaveResult saveLocalFileToStorage(const Authorization& auth, LockContext 
&lockCtx, const std::string& saveAsPath, const std::string& saveAsFilename, 
const bool isRename) override;
 
 private:
     /// True if the jailed file is not linked but copied.
@@ -511,12 +534,17 @@ public:
     /// provided during the initial creation of the WOPI storage.
     /// Also extracts the basic file information from the response
     /// which can then be obtained using getFileInfo()
-    std::unique_ptr<WOPIFileInfo> getWOPIFileInfo(const Authorization& auth);
+    /// Also sets up the locking context for future operations.
+    std::unique_ptr<WOPIFileInfo> getWOPIFileInfo(const Authorization& auth,
+                                                  LockContext &lockCtx);
+
+    /// Update the locking state (check-in/out) of the associated file
+    bool updateLockState(const Authorization &auth, LockContext &lockCtx, bool 
lock) override;
 
     /// uri format: http://server/<...>/wopi*/files/<id>/content
-    std::string loadStorageFileToLocal(const Authorization& auth, const 
std::string& templateUri) override;
+    std::string loadStorageFileToLocal(const Authorization& auth, LockContext 
&lockCtx, const std::string& templateUri) override;
 
-    SaveResult saveLocalFileToStorage(const Authorization& auth, const 
std::string& saveAsPath, const std::string& saveAsFilename, const bool 
isRename) override;
+    SaveResult saveLocalFileToStorage(const Authorization& auth, LockContext 
&lockCtx, const std::string& saveAsPath, const std::string& saveAsFilename, 
const bool isRename) override;
 
     /// Total time taken for making WOPI calls during load
     std::chrono::duration<double> getWopiLoadDuration() const { return 
_wopiLoadDuration; }
@@ -546,9 +574,11 @@ public:
     // Implement me
     // WebDAVFileInfo getWebDAVFileInfo(const Poco::URI& uriPublic);
 
-    std::string loadStorageFileToLocal(const Authorization& auth, const 
std::string& templateUri) override;
+    bool updateLockState(const Authorization &, LockContext &, bool) override 
{ return true; }
+
+    std::string loadStorageFileToLocal(const Authorization& auth, LockContext 
&lockCtx, const std::string& templateUri) override;
 
-    SaveResult saveLocalFileToStorage(const Authorization& auth, const 
std::string& saveAsPath, const std::string& saveAsFilename, const bool 
isRename) override;
+    SaveResult saveLocalFileToStorage(const Authorization& auth, LockContext 
&lockCtx, const std::string& saveAsPath, const std::string& saveAsFilename, 
const bool isRename) override;
 
 private:
     std::unique_ptr<AuthBase> _authAgent;
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to