[Libreoffice-commits] online.git: loleaflet/reference.html loleaflet/src tools/KitClient.cpp wsd/ClientSession.cpp wsd/DocumentBroker.cpp wsd/DocumentBroker.hpp wsd/protocol.txt wsd/Storage.cpp wsd/Storage.hpp
Aditya Dewan
iit2015097 at iiita.ac.in
Thu Aug 17 17:26:04 UTC 2017
loleaflet/reference.html | 22 ++++++++
loleaflet/src/control/Control.Menubar.js | 3 +
loleaflet/src/control/Toolbar.js | 9 ++-
loleaflet/src/core/Socket.js | 9 +++
loleaflet/src/map/handler/Map.WOPI.js | 11 +++-
tools/KitClient.cpp | 1
wsd/ClientSession.cpp | 7 ++
wsd/DocumentBroker.cpp | 23 +++++++++
wsd/DocumentBroker.hpp | 3 +
wsd/Storage.cpp | 79 +++++++++++++++++++++++++++++++
wsd/Storage.hpp | 3 +
wsd/protocol.txt | 16 +++++-
12 files changed, 177 insertions(+), 9 deletions(-)
New commits:
commit c3711a4375ec15a25b47cd62c8137f57145dbef5
Author: Aditya Dewan <iit2015097 at iiita.ac.in>
Date: Thu Aug 10 04:32:03 2017 +0530
Extending WOPI implementaion to introduce 'Save As' feature
Change-Id: Ic4c80f4c4b54944143682c25a5878c1336787b27
Reviewed-on: https://gerrit.libreoffice.org/40946
Reviewed-by: pranavk <pranavk at collabora.co.uk>
Tested-by: pranavk <pranavk at collabora.co.uk>
diff --git a/loleaflet/reference.html b/loleaflet/reference.html
index dcd0a977..8f1ea583 100644
--- a/loleaflet/reference.html
+++ b/loleaflet/reference.html
@@ -2849,7 +2849,6 @@ Editor to WOPI host
export of the document.
</td>
</tr>
-
</table>
<h3 id="loleaflet-postmessage-sessions">Session Management</h3>
@@ -2914,6 +2913,18 @@ WOPI host to editor
</td>
</tr>
<tr>
+ <td><code><b>Action_SaveAs</b></code></td>
+ <td><code>
+ <nobr>Name: <String></nobr>
+ <nobr>Path: <String></nobr>
+ </code></td>
+ <td>Creates copy of the document with given Name.<br/>
+ <code>Name</code> is the requested name for the new file.<br/>
+ <code>Path</code> is the relative path in the WOPI host file system where the
+ user wants the new file to be saved.<br/>
+ </td>
+ </tr>
+ <tr>
<td><code><b>Action_Print</b></code></td>
<td><code>
</code></td>
@@ -3036,6 +3047,15 @@ Editor to WOPI host
via <code>Insert_Button</code> API above is clicked.
</td>
</tr>
+ <tr>
+ <td><code><b>UI_SaveAs</b></code></td>
+ <td></td>
+ <td>
+ Requests WOPI host to create appropriate UI, so that the user can choose
+ path and File name for creating a copy of the current file.
+ Response to this query is sent via <code>Action_SaveAs</code> message.
+ </td>
+ </tr>
</table>
<h2 id="marker">Marker</h2>
diff --git a/loleaflet/src/control/Control.Menubar.js b/loleaflet/src/control/Control.Menubar.js
index af87725d..031bbb52 100644
--- a/loleaflet/src/control/Control.Menubar.js
+++ b/loleaflet/src/control/Control.Menubar.js
@@ -9,6 +9,7 @@ L.Control.Menubar = L.Control.extend({
text: [
{name: _('File'), id: 'file', type: 'menu', menu: [
{name: _('Save'), id: 'save', type: 'action'},
+ {name: _('Save As'), id: 'saveas', type: 'action'},
{name: _('Print'), id: 'print', type: 'action'},
{name: _('See revision history'), id: 'rev-history', type: 'action'},
{name: _('Download as'), id: 'downloadas', type: 'menu', menu: [
@@ -566,6 +567,8 @@ L.Control.Menubar = L.Control.extend({
var id = $(item).data('id');
if (id === 'save') {
map.save(true, true);
+ } else if (id === 'saveas') {
+ map.fire('postMessage', {msgId: 'UI_SaveAs'});
} else if (id === 'print') {
map.print();
} else if (id.startsWith('downloadas-')) {
diff --git a/loleaflet/src/control/Toolbar.js b/loleaflet/src/control/Toolbar.js
index 832abdb0..25693170 100644
--- a/loleaflet/src/control/Toolbar.js
+++ b/loleaflet/src/control/Toolbar.js
@@ -84,20 +84,23 @@ L.Map.include({
this.downloadAs('print.pdf', 'pdf', null, 'print');
},
- saveAs: function (url, format, options) {
+ saveAs: function (newName, path, format, options) {
if (format === undefined || format === null) {
format = '';
}
if (options === undefined || options === null) {
options = '';
}
+ if (path === undefined || path === null) {
+ path = '';
+ }
- this.showBusy(_('Saving...'), false);
// TakeOwnership: we are performing a 'real' save-as, the document
// is just getting a new place, ie. it will get the
// '.uno:ModifiedStatus' upon completion.
this._socket.sendMessage('saveas ' +
- 'url=' + url + ' ' +
+ 'fileName=' + newName + ' ' +
+ 'path=' + path + ' ' +
'format=' + format + ' ' +
'options=TakeOwnership,' + options);
},
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index 99507627..68f3ea4f 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -438,6 +438,9 @@ L.Socket = L.Class.extend({
return;
}
+ else if (textMsg.startsWith('error:') && command.errorCmd === 'saveas') {
+ this._map.hideBusy();
+ }
else if (textMsg.startsWith('error:') && command.errorCmd === 'load') {
this.close();
@@ -535,6 +538,12 @@ L.Socket = L.Class.extend({
', last: ' + (command.rendercount - this._map._docLayer._debugRenderCount));
this._map._docLayer._debugRenderCount = command.rendercount;
}
+ else if (textMsg.startsWith('saveas:')) {
+ textMsg = (textMsg.substring(7)).trim();
+ // var url = textMsg.substring(0, textMsg.indexOf(' '));
+ // var fileName = textMsg.substring(textMsg.indexOf(' '));
+ /// redirect or not?
+ }
else if (textMsg.startsWith('statusindicator:')) {
//FIXME: We should get statusindicator when saving too, no?
this._map.showBusy(_('Connecting...'), false);
diff --git a/loleaflet/src/map/handler/Map.WOPI.js b/loleaflet/src/map/handler/Map.WOPI.js
index f434c433..dc6f0fa3 100644
--- a/loleaflet/src/map/handler/Map.WOPI.js
+++ b/loleaflet/src/map/handler/Map.WOPI.js
@@ -216,11 +216,19 @@ L.Map.WOPI = L.Handler.extend({
this._postMessage({msgId: 'Get_Export_Formats_Resp', args: exportFormatsResp});
}
+ else if (msg.MessageId === 'Action_SaveAs') {
+ if (msg.Values) {
+ if (msg.Values.name === null || msg.Values.name === undefined) {
+ msg.Values.name = '';
+ }
+ this.showBusy(_('Creating copy...'), false);
+ map.saveAs(msg.Values.name, msg.Values.path);
+ }
+ }
},
_postMessage: function(e) {
if (!this.enabled) { return; }
-
var msgId = e.msgId;
var values = e.args || {};
if (!!this.PostMessageOrigin && window.parent !== window.self) {
@@ -237,7 +245,6 @@ L.Map.WOPI = L.Handler.extend({
'SendTime': Date.now(),
'Values': values
};
-
window.parent.postMessage(JSON.stringify(msg), this.PostMessageOrigin);
}
}
diff --git a/tools/KitClient.cpp b/tools/KitClient.cpp
index dfdb006c..aa2c012f 100644
--- a/tools/KitClient.cpp
+++ b/tools/KitClient.cpp
@@ -78,6 +78,7 @@ extern "C"
CASE(COMMENT);
CASE(INVALIDATE_HEADER);
CASE(CELL_ADDRESS);
+ CASE(RULER_UPDATE);
#undef CASE
}
std::cout << " payload: " << payload << std::endl;
diff --git a/wsd/ClientSession.cpp b/wsd/ClientSession.cpp
index 6a9a88d5..7a4f42b0 100644
--- a/wsd/ClientSession.cpp
+++ b/wsd/ClientSession.cpp
@@ -214,6 +214,13 @@ bool ClientSession::_handleInput(const char *buffer, int length)
{
return sendCombinedTiles(buffer, length, tokens, docBroker);
}
+ else if (tokens[0] == "saveas")
+ {
+ std::string newFileName, path;
+ getTokenString(tokens[1], "fileName", newFileName);
+ getTokenString(tokens[2], "path", path);
+ docBroker->saveFileAs(getId(), newFileName, path);
+ }
else if (tokens[0] == "save")
{
int dontTerminateEdit = 1;
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 7f294336..016d251d 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -718,6 +718,29 @@ bool DocumentBroker::saveToStorageInternal(const std::string& sessionId,
return false;
}
+void DocumentBroker::saveFileAs(const std::string& sessionId, const std::string& newFileName, const std::string& path)
+{
+ const auto it = _sessions.find(sessionId);
+ if(it == _sessions.end())
+ {
+ return;
+ }
+
+ WopiStorage* wopiStorage = dynamic_cast<WopiStorage*>(_storage.get());
+ if (wopiStorage != nullptr)
+ {
+ const std::string newUrl = wopiStorage->createCopyFile(it->second->getAccessToken(), newFileName, path);
+ if (!newUrl.empty())
+ {
+ it->second->sendTextFrame("saveas: " + newUrl + " " + newFileName);
+ }
+ else
+ {
+ it->second->sendTextFrame("error: cmd=saveas kind=saveasfailed");
+ }
+ }
+}
+
void DocumentBroker::setLoaded()
{
if (!_isLoaded)
diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp
index dd0ace39..b08fd2e9 100644
--- a/wsd/DocumentBroker.hpp
+++ b/wsd/DocumentBroker.hpp
@@ -339,6 +339,9 @@ public:
/// Sends the .uno:Save command to LoKit.
bool sendUnoSave(const std::string& sessionId, bool dontTerminateEdit = true, bool dontSaveIfUnmodified = true);
+ /// Create copy of the file with a different name
+ void saveFileAs(const std::string& sessionId, const std::string& newFileName, const std::string& path);
+
/// Sends a message to all sessions
void broadcastMessage(const std::string& message);
diff --git a/wsd/Storage.cpp b/wsd/Storage.cpp
index 72bae46a..1472faa6 100644
--- a/wsd/Storage.cpp
+++ b/wsd/Storage.cpp
@@ -584,6 +584,85 @@ std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Au
return std::unique_ptr<WopiStorage::WOPIFileInfo>(new WOPIFileInfo({userId, userName, userExtraInfo, canWrite, postMessageOrigin, hidePrintOption, hideSaveOption, hideExportOption, enableOwnerTermination, disablePrint, disableExport, disableCopy, callDuration}));
}
+/// PutRelativeFile - uri format: http://server/<...>/wopi*/files/<id>/
+std::string WopiStorage::createCopyFile(const std::string& accessToken, const std::string& newFileName, const std::string& path)
+{
+ const auto size = getFileSize(_jailedFilePath);
+ std::ostringstream oss;
+ Poco::URI uriObject(_uri);
+ setQueryParameter(uriObject, "access_token", accessToken);
+
+ LOG_DBG("Wopi PutRelativeFile(save as) request for : " << uriObject.toString());
+
+ 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);
+ request.set("X-WOPI-Override", "PUT_RELATIVE");
+ request.set("X-WOPI-RelativeTarget", newFileName + "." + getFileExtension());
+ request.set("X-WOPI-Size", std::to_string(size));
+ /// custom header
+ request.set("X-WOPI-TargetPath", path);
+ request.setContentType("application/octet-stream");
+ request.setContentLength(size);
+
+ addStorageDebugCookie(request);
+ std::ostream& os = psession->sendRequest(request);
+ std::ifstream ifs(_jailedFilePath);
+ Poco::StreamCopier::copyStream(ifs, os);
+
+ Poco::Net::HTTPResponse response;
+ std::istream& rs = psession->receiveResponse(response);
+ Poco::StreamCopier::copyStream(rs, oss);
+ LOG_INF("WOPI::createCopyFile response: " << oss.str());
+ LOG_INF("WOPI::createCopyFile tried to create a copy of file at [" << uriObject.toString()
+ << "] having a size of " << size << " bytes and suggested name is " << newFileName + "." + getFileExtension() << ". Response recieved "
+ << response.getStatus() << " " << response.getReason());
+
+ auto logger = Log::trace();
+ if (logger.enabled())
+ {
+ logger << "WOPI::createCopyFile header for URI [" << uriObject.toString() << "]:\n";
+ for (const auto& pair : response)
+ {
+ logger << '\t' << pair.first << ": " << pair.second << " / ";
+ }
+
+ LOG_END(logger);
+ }
+
+ if (response.getStatus() != Poco::Net::HTTPResponse::HTTP_OK)
+ {
+ LOG_ERR("WOPI::createCopyFile failed with " << response.getStatus() << ' ' << response.getReason());
+ throw StorageConnectionException("WOPI::createCopyFile failed");
+ }
+ }
+ catch(const Poco::Exception& pexc)
+ {
+ LOG_ERR("createCopyFile cannot create a copy of file with WOPI storage uri [" << uriObject.toString() << "]. Error: " << pexc.displayText() <<
+ (pexc.nested() ? " (" + pexc.nested()->displayText() + ")" : ""));
+ return "";
+ }
+
+ std::string filename;
+ std::string url;
+ std::string hostEditUrl;
+ std::string hostViewUrl;
+
+ LOG_DBG("WOPI::createCopyFile returned: " << oss.str() );
+ Poco::JSON::Object::Ptr object;
+ if (parseJSON(oss.str(), object))
+ {
+ getWOPIValue(object, "Name", filename);
+ getWOPIValue(object, "Url", url);
+ getWOPIValue(object, "HostViewUrl", hostViewUrl);
+ getWOPIValue(object, "HostEditUrl", hostEditUrl);
+ }
+ return hostEditUrl;
+}
+
/// uri format: http://server/<...>/wopi*/files/<id>/content
std::string WopiStorage::loadStorageFileToLocal(const Authorization& auth)
{
diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp
index 6b26c066..da30b1b9 100644
--- a/wsd/Storage.hpp
+++ b/wsd/Storage.hpp
@@ -258,6 +258,9 @@ public:
/// which can then be obtained using getFileInfo()
std::unique_ptr<WOPIFileInfo> getWOPIFileInfo(const Authorization& auth);
+ /// returns
+ std::string createCopyFile(const std::string& accessToken, const std::string& newFileName, const std::string& path);
+
/// uri format: http://server/<...>/wopi*/files/<id>/content
std::string loadStorageFileToLocal(const Authorization& auth) override;
diff --git a/wsd/protocol.txt b/wsd/protocol.txt
index df96f5dc..b75afe7d 100644
--- a/wsd/protocol.txt
+++ b/wsd/protocol.txt
@@ -108,10 +108,12 @@ requestloksession
resetselection
-saveas url=<url> format=<format> options=<options>
+saveas newName=<suggested name for the new file> path=<path as per WOPI host filesystem>
+ format=<format> options=<options>
- <url> is a URL, encoded. <format> is also URL-encoded, i.e. spaces as %20 and it can be empty
- options are the whole rest of the line, not URL-encoded, and can be empty
+ Creates a copy of the current file with 'fileName' as a suggestion for the
+ name, at the given 'path' in the WOPI host fileSystem. The format and option values
+ are not being used currently, but maybe used for future extension.
selecttext type=<type> x=<x> y=<y>
@@ -306,6 +308,14 @@ pong rendercount=<num>
sent in reply to a 'ping' message, where <num> is the total number
of rendered tiles of the document.
+saveas: newUrl=<url> newFileName=<filename>
+
+ sent only if the saveas operation was succesful and the WOPI host sent the
+ HostEditUrl('newUrl') for the new created file. The 'newFileName' represents the
+ name of the newly created file, this is being sent because it is not necessary that
+ the new File will be saved with the requested file name.
+ if the operation fails a 'error' message with cmd='saveas' is sent instead.
+
status: type=<typeName> parts=<numberOfParts> current=<currentPartNumber> width=<width> height=<height> viewid=<viewId> [partNames]
<typeName> is 'text, 'spreadsheet', 'presentation', 'drawing' or 'other. Others are numbers.
More information about the Libreoffice-commits
mailing list