[Libreoffice-commits] online.git: Branch 'distro/collabora/collabora-online-2-1' - loleaflet/src wsd/DocumentBroker.cpp wsd/Storage.cpp wsd/Storage.hpp
Pranav Kant
pranavk at collabora.co.uk
Thu Jun 22 11:03:09 UTC 2017
loleaflet/src/core/Socket.js | 4 +
wsd/DocumentBroker.cpp | 18 ++++++-
wsd/Storage.cpp | 100 +++++++++++++++++++++++++++++++------------
wsd/Storage.hpp | 7 +++
4 files changed, 100 insertions(+), 29 deletions(-)
New commits:
commit 4f60d5d359ace0c2671ebc68524ba5db7db08e34
Author: Pranav Kant <pranavk at collabora.co.uk>
Date: Wed May 31 23:18:33 2017 +0530
Inform all clients when document changed behind our back
Introduce a new header X-LOOL-WOPI-Timestamp
This is a WOPI header extension to detect any external document change. For
example, when the file that is already opened by LOOL is changed
in storage.
The WOPI host sends LastModifiedTime field (in WOPI specs) as part
of the CheckFileInfo response. It also expects wsd to send the
same timestamp in X-LOOL-WOPI-Timestamp header during WOPI::PutFile. If
this header is present, then WOPI host checks, before saving the
document, if the timestamp in the header is equal to the timestamp of
the file in its storage. Only upon meeting this condition, it saves the
file back to storage, otherwise it informs us about some change
to the document.
We are supposed to inform the user accordingly. If user is okay
with over-writing the document, then we can omit sending
X-LOOL-WOPI-Timestamp header, in which case, no check as mentioned above
would be performed while saving the file and document will be
overwritten.
Also, use a separate list of LOOL status codes to denote such a change.
It would be wrong to use HTTP_CONFLICT status code for denoting doc
changed in storage scenario. WOPI specs reserves that for WOPI locks
which are not yet implemented. Better to use a separate LOOL specific
status codes synced across WOPI hosts and us to denote scenario that we
expect and are not covered in WOPI specs.
(cherry picked from commit ba4e75cfae5eb174f8b56254f3c9513bab2afed5)
Change-Id: I61539dfae672bc104b8008f030f96e90f9ff48a5
Reviewed-on: https://gerrit.libreoffice.org/38527
Reviewed-by: Jan Holesovsky <kendy at collabora.com>
Tested-by: Jan Holesovsky <kendy at collabora.com>
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index e137acca..894725aa 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -343,6 +343,10 @@ L.Socket = L.Class.extend({
this._map.hideBusy();
this.close();
}
+ else if (command.errorKind === 'documentconflict')
+ {
+ storageError = errorMessages.storage.documentconflict;
+ }
// Parse the storage url as link
var tmpLink = document.createElement('a');
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 138b9bb7..2157e191 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -492,8 +492,12 @@ bool DocumentBroker::load(const std::shared_ptr<ClientSession>& session, const s
fileInfo._modifiedTime != Zero &&
_documentLastModifiedTime != fileInfo._modifiedTime)
{
- LOG_ERR("Document has been modified behind our back, URI [" << session->getPublicUri().toString() << "].");
- // What do do?
+ LOG_WRN("Document [" << _docKey << "] has been modified behind our back. Informing all clients.");
+ // Inform all clients
+ for (const auto& sessionIt : _sessions)
+ {
+ sessionIt.second->sendTextFrame("error: cmd=storage kind=documentconflict");
+ }
}
}
@@ -644,6 +648,16 @@ bool DocumentBroker::saveToStorageInternal(const std::string& sessionId,
LOG_ERR("Failed to save docKey [" << _docKey << "] to URI [" << uri << "]. Notifying client.");
it->second->sendTextFrame("error: cmd=storage kind=savefailed");
}
+ else if (storageSaveResult == StorageBase::SaveResult::DOC_CHANGED)
+ {
+ LOG_ERR("PutFile says that Document changed in storage");
+
+ // Inform all clients
+ for (const auto& sessionIt : _sessions)
+ {
+ sessionIt.second->sendTextFrame("error: cmd=storage kind=documentconflict");
+ }
+ }
return false;
}
diff --git a/wsd/Storage.cpp b/wsd/Storage.cpp
index 5725c58e..aee09ae9 100644
--- a/wsd/Storage.cpp
+++ b/wsd/Storage.cpp
@@ -351,6 +351,27 @@ int getLevenshteinDist(const std::string& string1, const std::string& string2) {
return matrix[string1.size()][string2.size()];
}
+// Gets value for `key` directly from the given JSON in `object`
+template <typename T>
+T getJSONValue(const Poco::JSON::Object::Ptr &object, const std::string& key)
+{
+ T value = T();
+ try
+ {
+ const Poco::Dynamic::Var valueVar = object->get(key);
+ value = valueVar.convert<T>();
+ }
+ catch (const Poco::Exception& exc)
+ {
+ LOG_ERR("getJSONValue: " << exc.displayText() <<
+ (exc.nested() ? " (" + exc.nested()->displayText() + ")" : ""));
+ }
+
+ return value;
+}
+
+// Function that searches `object` for `key` and warns if there are minor mis-spellings involved
+// Upon successfull search, fills `value` with value found in object.
template <typename T>
void getWOPIValue(const Poco::JSON::Object::Ptr &object, const std::string& key, T& value)
{
@@ -375,23 +396,31 @@ void getWOPIValue(const Poco::JSON::Object::Ptr &object, const std::string& key,
return;
}
- try
- {
- const Poco::Dynamic::Var valueVar = object->get(userInput);
- value = valueVar.convert<T>();
- }
- catch (const Poco::Exception& exc)
- {
- LOG_ERR("getWOPIValue: " << exc.displayText() <<
- (exc.nested() ? " (" + exc.nested()->displayText() + ")" : ""));
- }
-
+ value = getJSONValue<T>(object, userInput);
return;
}
LOG_WRN("Missing JSON property [" << key << "]");
}
+// Parse the json string and fill the Poco::JSON object
+// Returns true if parsing successful otherwise false
+bool parseJSON(const std::string& json, Poco::JSON::Object::Ptr& object)
+{
+ bool success = false;
+ const auto index = json.find_first_of("{");
+ if (index != std::string::npos)
+ {
+ const std::string stringJSON = json.substr(index);
+ Poco::JSON::Parser parser;
+ const auto result = parser.parse(stringJSON);
+ object = result.extract<Poco::JSON::Object::Ptr>();
+ success = true;
+ }
+
+ return success;
+}
+
void setQueryParameter(Poco::URI& uriObject, const std::string& key, const std::string& value)
{
Poco::URI::QueryParameters queryParams = uriObject.getQueryParameters();
@@ -447,6 +476,8 @@ Poco::Timestamp iso8601ToTimestamp(const std::string& iso8601Time)
return timestamp;
}
+
+
} // anonymous namespace
std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const std::string& accessToken)
@@ -519,13 +550,9 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const st
std::string lastModifiedTime;
LOG_DBG("WOPI::CheckFileInfo returned: " << resMsg << ". Call duration: " << callDuration.count() << "s");
- const auto index = resMsg.find_first_of('{');
- if (index != std::string::npos)
+ Poco::JSON::Object::Ptr object;
+ if (parseJSON(resMsg, object))
{
- const std::string stringJSON = resMsg.substr(index);
- Poco::JSON::Parser parser;
- const auto result = parser.parse(stringJSON);
- const auto& object = result.extract<Poco::JSON::Object::Ptr>();
getWOPIValue(object, "BaseFileName", filename);
getWOPIValue(object, "Size", size);
getWOPIValue(object, "OwnerId", ownerId);
@@ -549,7 +576,7 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const st
throw UnauthorizedRequestException("Access denied. WOPI::CheckFileInfo failed on: " + uriObject.toString());
}
- modifiedTime = iso8601ToTimestamp(lastModifiedTime);
+ const Poco::Timestamp modifiedTime = iso8601ToTimestamp(lastModifiedTime);
_fileInfo = FileInfo({filename, ownerId, modifiedTime, size});
return std::unique_ptr<WopiStorage::WOPIFileInfo>(new WOPIFileInfo({userId, userName, userExtraInfo, canWrite, postMessageOrigin, hidePrintOption, hideSaveOption, hideExportOption, enableOwnerTermination, disablePrint, disableExport, disableCopy, callDuration}));
@@ -641,6 +668,9 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const std::string& a
Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, uriObject.getPathAndQuery(), Poco::Net::HTTPMessage::HTTP_1_1);
request.set("X-WOPI-Override", "PUT");
+ request.set("X-LOOL-WOPI-Timestamp",
+ Poco::DateTimeFormatter::format(Poco::DateTime(_fileInfo._modifiedTime),
+ Poco::DateTimeFormat::ISO8601_FRAC_FORMAT));
request.setContentType("application/octet-stream");
request.setContentLength(size);
addStorageDebugCookie(request);
@@ -659,18 +689,17 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const std::string& a
if (response.getStatus() == Poco::Net::HTTPResponse::HTTP_OK)
{
saveResult = StorageBase::SaveResult::OK;
- const auto index = oss.str().find_first_of('{');
- if (index != std::string::npos)
+ Poco::JSON::Object::Ptr object;
+ if (parseJSON(oss.str(), object))
{
- const std::string stringJSON = oss.str().substr(index);
- Poco::JSON::Parser parser;
- const auto result = parser.parse(stringJSON);
- const auto& object = result.extract<Poco::JSON::Object::Ptr>();
-
- std::string lastModifiedTime;
- getWOPIValue(object, "LastFileModifiedTime", lastModifiedTime);
+ const std::string lastModifiedTime = getJSONValue<std::string>(object, "LastModifiedTime");
+ LOG_TRC("WOPI::PutFile returns LastModifiedTime [" << lastModifiedTime << "].");
_fileInfo._modifiedTime = iso8601ToTimestamp(lastModifiedTime);
}
+ else
+ {
+ LOG_WRN("Invalid/Missing JSON found in WOPI::PutFile response");
+ }
}
else if (response.getStatus() == Poco::Net::HTTPResponse::HTTP_REQUESTENTITYTOOLARGE)
{
@@ -680,6 +709,23 @@ StorageBase::SaveResult WopiStorage::saveLocalFileToStorage(const std::string& a
{
saveResult = StorageBase::SaveResult::UNAUTHORIZED;
}
+ else if (response.getStatus() == Poco::Net::HTTPResponse::HTTP_CONFLICT)
+ {
+ saveResult = StorageBase::SaveResult::CONFLICT;
+ Poco::JSON::Object::Ptr object;
+ if (parseJSON(oss.str(), object))
+ {
+ const unsigned loolStatusCode = getJSONValue<unsigned>(object, "LOOLStatusCode");
+ if (loolStatusCode == static_cast<unsigned>(LOOLStatusCode::DOC_CHANGED))
+ {
+ saveResult = StorageBase::SaveResult::DOC_CHANGED;
+ }
+ }
+ else
+ {
+ LOG_WRN("Invalid/missing JSON in WOPI::PutFile response");
+ }
+ }
}
catch(const Poco::Exception& pexc)
{
diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp
index ed0331c0..dc29685b 100644
--- a/wsd/Storage.hpp
+++ b/wsd/Storage.hpp
@@ -58,9 +58,16 @@ public:
OK,
DISKFULL,
UNAUTHORIZED,
+ DOC_CHANGED, /* Document changed in storage */
+ CONFLICT,
FAILED
};
+ enum class LOOLStatusCode
+ {
+ DOC_CHANGED = 1010 // Document changed externally in storage
+ };
+
/// localStorePath the absolute root path of the chroot.
/// jailPath the path within the jail that the child uses.
StorageBase(const Poco::URI& uri,
More information about the Libreoffice-commits
mailing list