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 <iit2015...@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 <pran...@collabora.co.uk> Tested-by: pranavk <pran...@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. _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits