loleaflet/README | 1 loleaflet/reference.html | 18 ++++++++++ loleaflet/src/control/Toolbar.js | 13 +++++++ loleaflet/src/core/Socket.js | 12 +++++++ loleaflet/src/layer/tile/TileLayer.js | 12 +++++++ loleaflet/src/map/Map.js | 1 loolwsd/LOOLSession.cpp | 57 +++++++++++++++++++++++++++++++--- loolwsd/LOOLSession.hpp | 9 ++--- loolwsd/LOOLWSD.cpp | 37 +++++++++++++++++----- loolwsd/LOOLWSD.hpp | 2 - loolwsd/protocol.txt | 9 +++++ 11 files changed, 154 insertions(+), 17 deletions(-)
New commits: commit cf089ccf2a1dd6bf9602daa374b83ed4ad9d3c7d Author: Mihai Varga <[email protected]> Date: Fri Oct 9 16:12:07 2015 +0300 tdf#94607 downloadAs command handler We use a hidden iframe to download the document. Once we have the url for it, we set iframr.src = url Also added map.downloadAs(name, format, options) method diff --git a/loleaflet/README b/loleaflet/README index 8cc2739..2011c33 100644 --- a/loleaflet/README +++ b/loleaflet/README @@ -133,6 +133,7 @@ Statusindicator (when the document is loading): Save: - API: map.saveAs(url, [format, options]) + map.downloadAs(name, [format, options]) Scroll (the following are measured in pixels): - API: diff --git a/loleaflet/reference.html b/loleaflet/reference.html index 057c2d5..9a7b3e8 100644 --- a/loleaflet/reference.html +++ b/loleaflet/reference.html @@ -1507,6 +1507,24 @@ var map = L.map('map', { <td><code>undefined</code></td> <td>Toggles the state for the given UNO command.</td> </tr> + <tr> + <td><code><b>saveAs</b>( + <nobr><String><i>url</i>,</nobr> + <nobr><String><i>format?</i>,</nobr> + <nobr><String><i>options?</i>)</nobr> + </code></td> + <td><code>undefined</code></td> + <td>Save the document as "format" at the given url by applying the filter options.</td> + </tr> + <tr> + <td><code><b>downloadAs</b>( + <nobr><String><i>name</i>,</nobr> + <nobr><String><i>format?</i>,</nobr> + <nobr><String><i>options?</i>)</nobr> + </code></td> + <td><code>undefined</code></td> + <td>Download the document as "format" with the name "name" by applying the filter options.</td> + </tr> </table> <h2 id="loleaflet-page">Page oriented</h2> diff --git a/loleaflet/src/control/Toolbar.js b/loleaflet/src/control/Toolbar.js index b8b316d..243b24a 100644 --- a/loleaflet/src/control/Toolbar.js +++ b/loleaflet/src/control/Toolbar.js @@ -31,6 +31,19 @@ L.Map.include({ return this._docLayer._toolbarCommandValues[command]; }, + downloadAs: function (name, format, options) { + if (format === undefined || format === null) { + format = ''; + } + if (options === undefined || options === null) { + options = ''; + } + L.Socket.sendMessage('downloadas ' + + 'name=' + name + ' ' + + 'format=' + format + ' ' + + 'options=' + options); + }, + saveAs: function (url, format, options) { if (format === undefined || format === null) { format = ''; diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js index 99a8c19..947adf5 100644 --- a/loleaflet/src/core/Socket.js +++ b/loleaflet/src/core/Socket.js @@ -196,6 +196,18 @@ L.Socket = { else if (tokens[i].substring(0, 5) === 'kind=') { command.errorKind = tokens[i].substring(5); } + else if (tokens[i].substring(0, 5) === 'jail=') { + command.jail = tokens[i].substring(5); + } + else if (tokens[i].substring(0, 4) === 'dir=') { + command.dir = tokens[i].substring(4); + } + else if (tokens[i].substring(0, 5) === 'name=') { + command.name = tokens[i].substring(5); + } + else if (tokens[i].substring(0, 5) === 'port=') { + command.port = tokens[i].substring(5); + } } if (command.tileWidth && command.tileHeight && this._map._docLayer) { var scale = command.tileWidth / this._map._docLayer.options.tileWidthTwips; diff --git a/loleaflet/src/layer/tile/TileLayer.js b/loleaflet/src/layer/tile/TileLayer.js index 6be61de..1054e73 100644 --- a/loleaflet/src/layer/tile/TileLayer.js +++ b/loleaflet/src/layer/tile/TileLayer.js @@ -185,6 +185,9 @@ L.TileLayer = L.GridLayer.extend({ else if (textMsg.startsWith('cursorvisible:')) { this._onCursorVisibleMsg(textMsg); } + else if (textMsg.startsWith('downloadas:')) { + this._onDownloadAsMsg(textMsg); + } else if (textMsg.startsWith('error:')) { this._onErrorMsg(textMsg); } @@ -264,6 +267,15 @@ L.TileLayer = L.GridLayer.extend({ this._onUpdateCursor(); }, + _onDownloadAsMsg: function (textMsg) { + var command = L.Socket.parseServerCmd(textMsg); + var parser = document.createElement('a'); + parser.href = this._map.options.server; + var url = 'http://' + parser.hostname + ':' + command.port + '/' + + command.jail + '/' + command.dir + '/' + command.name; + this._map._fileDownloader.src = url; + }, + _onErrorMsg: function (textMsg) { var command = L.Socket.parseServerCmd(textMsg); this._map.fire('error', {cmd: command.errorCmd, kind: command.errorKind}); diff --git a/loleaflet/src/map/Map.js b/loleaflet/src/map/Map.js index c1db931..df230b9 100644 --- a/loleaflet/src/map/Map.js +++ b/loleaflet/src/map/Map.js @@ -458,6 +458,7 @@ L.Map = L.Evented.extend({ var textAreaContainer = L.DomUtil.create('div', 'clipboard-container', container.parentElement); this._textArea = L.DomUtil.create('textarea', 'clipboard', textAreaContainer); this._resizeDetector = L.DomUtil.create('iframe', 'resize-detector', container); + this._fileDownloader = L.DomUtil.create('iframe', 'resize-detector', container); container._leaflet = true; }, commit 676c6d60d1dfd02435f2fc4440c21241116c6539 Author: Mihai Varga <[email protected]> Date: Fri Oct 9 15:55:49 2015 +0300 tdf#94607 downloadAs command that generates an URL for the doc When requested, the document is exported under /jail_path/CHILD_ID/user/thedocument/RANDOMDIR/filename and CHILD_ID, RANDOMDIR and the filename are communicated to the client. When the client requests http://server:port/CHILD_ID/RANDOMDIR/filename, the exported document is served and then RANDOMDIR is removed diff --git a/loolwsd/LOOLSession.cpp b/loolwsd/LOOLSession.cpp index d8b7dd0..0b2780c 100644 --- a/loolwsd/LOOLSession.cpp +++ b/loolwsd/LOOLSession.cpp @@ -290,11 +290,12 @@ bool MasterProcessSession::handleInput(const char *buffer, int length) } else if (tokens[0] != "canceltiles" && tokens[0] != "commandvalues" && - tokens[0] != "partpagerectangles" && + tokens[0] != "downloadas" && tokens[0] != "gettextselection" && tokens[0] != "invalidatetiles" && tokens[0] != "key" && tokens[0] != "mouse" && + tokens[0] != "partpagerectangles" && tokens[0] != "requestloksession" && tokens[0] != "resetselection" && tokens[0] != "saveas" && @@ -632,10 +633,11 @@ void MasterProcessSession::forwardToPeer(const char *buffer, int length) peer->sendBinaryFrame(buffer, length); } -ChildProcessSession::ChildProcessSession(std::shared_ptr<WebSocket> ws, LibreOfficeKit *loKit) : +ChildProcessSession::ChildProcessSession(std::shared_ptr<WebSocket> ws, LibreOfficeKit *loKit, std::string childId) : LOOLSession(ws, Kind::ToMaster), _loKitDocument(NULL), _loKit(loKit), + _childId(childId), _clientPart(0) { std::cout << Util::logPrefix() << "ChildProcessSession ctor this=" << this << " ws=" << _ws.get() << std::endl; @@ -699,7 +701,8 @@ bool ChildProcessSession::handleInput(const char *buffer, int length) // All other commands are such that they always require a LibreOfficeKitDocument session, // i.e. need to be handled in a child process. - assert(tokens[0] == "gettextselection" || + assert(tokens[0] == "downloadas" || + tokens[0] == "gettextselection" || tokens[0] == "key" || tokens[0] == "mouse" || tokens[0] == "uno" || @@ -712,7 +715,11 @@ bool ChildProcessSession::handleInput(const char *buffer, int length) { _loKitDocument->pClass->setPart(_loKitDocument, _clientPart); } - if (tokens[0] == "gettextselection") + if (tokens[0] == "downloadas") + { + return downloadAs(buffer, length, tokens); + } + else if (tokens[0] == "gettextselection") { return getTextSelection(buffer, length, tokens); } @@ -992,6 +999,48 @@ void ChildProcessSession::sendTile(const char *buffer, int length, StringTokeniz sendBinaryFrame(output.data(), output.size()); } +bool ChildProcessSession::downloadAs(const char *buffer, int length, StringTokenizer& tokens) +{ + std::string name, format, filterOptions; + + if (tokens.count() < 4 || + !getTokenString(tokens[1], "name", name)) + { + sendTextFrame("error: cmd=saveas kind=syntax"); + return false; + } + + getTokenString(tokens[2], "format", format); + + if (getTokenString(tokens[3], "options", filterOptions)) { + if (tokens.count() > 4) { + filterOptions += Poco::cat(std::string(" "), tokens.begin() + 4, tokens.end()); + } + } + + std::string tmpDir, url; + File *file = NULL; + do + { + if (file != NULL) + { + delete file; + } + tmpDir = std::to_string((((Poco::UInt64)LOOLWSD::_rng.next()) << 32) | LOOLWSD::_rng.next() | 1); + url = jailDocumentURL + "/" + tmpDir + "/" + name; + file = new File(url); + } while (file->exists()); + delete file; + + _loKitDocument->pClass->saveAs(_loKitDocument, url.c_str(), + format.size() == 0 ? NULL :format.c_str(), + filterOptions.size() == 0 ? NULL : filterOptions.c_str()); + + sendTextFrame("downloadas: jail=" + _childId + " dir=" + tmpDir + " name=" + name + + " port=" + std::to_string(LOOLWSD::portNumber)); + return true; +} + bool ChildProcessSession::getTextSelection(const char *buffer, int length, StringTokenizer& tokens) { std::string mimeType; diff --git a/loolwsd/LOOLSession.hpp b/loolwsd/LOOLSession.hpp index 13970c6..70210ae 100644 --- a/loolwsd/LOOLSession.hpp +++ b/loolwsd/LOOLSession.hpp @@ -51,12 +51,12 @@ public: virtual bool handleInput(const char *buffer, int length) = 0; + static const std::string jailDocumentURL; + protected: LOOLSession(std::shared_ptr<Poco::Net::WebSocket> ws, Kind kind); virtual ~LOOLSession(); - static const std::string jailDocumentURL; - const Kind _kind; std::string _kindString; @@ -156,7 +156,7 @@ private: class ChildProcessSession final : public LOOLSession { public: - ChildProcessSession(std::shared_ptr<Poco::Net::WebSocket> ws, LibreOfficeKit *loKit); + ChildProcessSession(std::shared_ptr<Poco::Net::WebSocket> ws, LibreOfficeKit *loKit, std::string _childId); virtual ~ChildProcessSession(); virtual bool handleInput(const char *buffer, int length) override; @@ -175,6 +175,7 @@ public: virtual void sendTile(const char *buffer, int length, Poco::StringTokenizer& tokens); + bool downloadAs(const char *buffer, int length, Poco::StringTokenizer& tokens); bool getTextSelection(const char *buffer, int length, Poco::StringTokenizer& tokens); bool keyEvent(const char *buffer, int length, Poco::StringTokenizer& tokens); bool mouseEvent(const char *buffer, int length, Poco::StringTokenizer& tokens); @@ -186,9 +187,9 @@ public: bool setClientPart(const char *buffer, int length, Poco::StringTokenizer& tokens); bool setPage(const char *buffer, int length, Poco::StringTokenizer& tokens); - std::string _jail; std::string _loSubPath; LibreOfficeKit *_loKit; + std::string _childId; private: int _clientPart; diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp index e12cbf4..867be4f 100644 --- a/loolwsd/LOOLWSD.cpp +++ b/loolwsd/LOOLWSD.cpp @@ -175,11 +175,11 @@ private: tsqueue<std::string>& _queue; }; -class WebSocketRequestHandler: public HTTPRequestHandler - /// Handle a WebSocket connection. +class RequestHandler: public HTTPRequestHandler + /// Handle a WebSocket connection or a simple HTTP request. { public: - WebSocketRequestHandler() + RequestHandler() { } @@ -198,9 +198,30 @@ public: if(!(request.find("Upgrade") != request.end() && Poco::icompare(request["Upgrade"], "websocket") == 0)) { - response.setStatusAndReason(HTTPResponse::HTTP_BAD_REQUEST); - response.setContentLength(0); - response.send(); + // The user might request a file to download + StringTokenizer tokens(request.getURI(), "/"); + if (tokens.count() != 4) + { + response.setStatus(HTTPResponse::HTTP_BAD_REQUEST); + response.setContentLength(0); + response.send(); + } + std::string dirPath = LOOLWSD::childRoot + "/" + tokens[1] + LOOLSession::jailDocumentURL + "/" + tokens[2]; + std::string filePath = dirPath + "/" + tokens[3]; + std::cout << Util::logPrefix() << "HTTP request for: " << filePath << std::endl; + File file(filePath); + if (file.exists()) + { + response.sendFile(filePath, "application/octet-stream"); + File dir(dirPath); + dir.remove(true); + } + else + { + response.setStatus(HTTPResponse::HTTP_NOT_FOUND); + response.setContentLength(0); + response.send(); + } return; } @@ -340,7 +361,7 @@ public: } Application::instance().logger().information(line); - return new WebSocketRequestHandler(); + return new RequestHandler(); } }; @@ -777,7 +798,7 @@ void LOOLWSD::componentMain() HTTPResponse response; std::shared_ptr<WebSocket> ws(new WebSocket(cs, request, response)); - std::shared_ptr<ChildProcessSession> session(new ChildProcessSession(ws, loKit)); + std::shared_ptr<ChildProcessSession> session(new ChildProcessSession(ws, loKit, std::to_string(_childId))); ws->setReceiveTimeout(0); diff --git a/loolwsd/LOOLWSD.hpp b/loolwsd/LOOLWSD.hpp index aa419c6..964115f 100644 --- a/loolwsd/LOOLWSD.hpp +++ b/loolwsd/LOOLWSD.hpp @@ -41,6 +41,7 @@ public: static std::string jail; static Poco::SharedMemory _sharedForkChild; static Poco::NamedMutex _namedMutexLOOL; + static Poco::Random _rng; static const int DEFAULT_CLIENT_PORT_NUMBER = 9980; static const int MASTER_PORT_NUMBER = 9981; @@ -70,7 +71,6 @@ private: Poco::UInt64 _childId; static int _numPreSpawnedChildren; static std::mutex _rngMutex; - static Poco::Random _rng; #if ENABLE_DEBUG public: diff --git a/loolwsd/protocol.txt b/loolwsd/protocol.txt index 88cc062..3c8369f 100644 --- a/loolwsd/protocol.txt +++ b/loolwsd/protocol.txt @@ -17,6 +17,10 @@ canceltiles dropped and will not be handled. There is no guarantee of exactly which tile: messages might still be sent back to the client. +downloadas downloadas name=<fileName> format=<document format> options=<SkipImages, etc> + + Exports the current document to the desired format and returns a download URL + gettextselection mimetype=<mimeType> Request selection's content @@ -82,6 +86,11 @@ partpagerectangles server -> client ================ +downloadas: jail=<jail directory> dir=<a tmp dir> name=<name> port=<port> + + The client should then request http://server:port/jail/dir/name in order to download + the document + error: cmd=<command> kind=<kind> <freeErrorText> _______________________________________________ Libreoffice-commits mailing list [email protected] http://lists.freedesktop.org/mailman/listinfo/libreoffice-commits
