[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