|
|
Line 184: |
Line 184: |
|
| |
|
| == Patches == | | == Patches == |
| === FlightGear ===
| |
| <syntaxhighlight lang="diff">
| |
| diff --git a/src/Canvas/canvas_mgr.cxx b/src/Canvas/canvas_mgr.cxx
| |
| index 6646b77..5316543 100644
| |
| --- a/src/Canvas/canvas_mgr.cxx
| |
| +++ b/src/Canvas/canvas_mgr.cxx
| |
| @@ -64,7 +64,7 @@ CanvasMgr::CanvasMgr():
| |
| fgGetNode("/sim/signals/model-reinit", true)
| |
| )
| |
| {
| |
| -
| |
| + SG_LOG(SG_GENERAL, SG_ALERT, "CanvasMgr() constructor invoked");
| |
| }
| |
|
| |
| //----------------------------------------------------------------------------
| |
| diff --git a/src/Network/http/CMakeLists.txt b/src/Network/http/CMakeLists.txt
| |
| index 9e913d0..1a099cb 100644
| |
| --- a/src/Network/http/CMakeLists.txt
| |
| +++ b/src/Network/http/CMakeLists.txt
| |
| @@ -3,6 +3,7 @@ include(FlightGearComponent)
| |
| set(SOURCES
| |
| httpd.cxx
| |
| ScreenshotUriHandler.cxx
| |
| + CanvasImageUriHandler.cxx
| |
| PropertyUriHandler.cxx
| |
| JsonUriHandler.cxx
| |
| FlightHistoryUriHandler.cxx
| |
| @@ -19,6 +20,7 @@ set(HEADERS
| |
| urihandler.hxx
| |
| httpd.hxx
| |
| ScreenshotUriHandler.hxx
| |
| + CanvasImageUriHandler.hxx
| |
| PropertyUriHandler.hxx
| |
| JsonUriHandler.hxx
| |
| FlightHistoryUriHandler.hxx
| |
| diff --git a/src/Network/http/CanvasImageUriHandler.cxx b/src/Network/http/CanvasImageUriHandler.cxx
| |
| new file mode 100644
| |
| index 0000000..7cd5a0e
| |
| --- /dev/null
| |
| +++ b/src/Network/http/CanvasImageUriHandler.cxx
| |
| @@ -0,0 +1,335 @@
| |
| +// CanvasImageUriHandler.cxx -- Provide canvasimages via http
| |
| +//
| |
| +// Started by Curtis Olson, started June 2001.
| |
| +// osg support written by James Turner
| |
| +// Ported to new httpd infrastructure by Torsten Dreyer
| |
| +// Derived from ScreenshotUrihandler originally by Torsten Dreyer)
| |
| +//
| |
| +// This program is free software; you can redistribute it and/or
| |
| +// modify it under the terms of the GNU General Public License as
| |
| +// published by the Free Software Foundation; either version 2 of the
| |
| +// License, or (at your option) any later version.
| |
| +//
| |
| +// This program is distributed in the hope that it will be useful, but
| |
| +// WITHOUT ANY WARRANTY; without even the implied warranty of
| |
| +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
| |
| +// General Public License for more details.
| |
| +//
| |
| +// You should have received a copy of the GNU General Public License
| |
| +// along with this program; if not, write to the Free Software
| |
| +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| |
| +
| |
| +#include "CanvasImageUriHandler.hxx"
| |
| +
| |
| +#ifdef _WIN32
| |
| +#include <windows.h>
| |
| +#endif
| |
| +#include <osgDB/Registry>
| |
| +#include <osgDB/ReaderWriter>
| |
| +#include <osgUtil/SceneView>
| |
| +#include <osgViewer/Viewer>
| |
| +
| |
| +#include <Canvas/canvas_mgr.hxx>
| |
| +#include <simgear/canvas/Canvas.hxx>
| |
| +
| |
| +#include <simgear/threads/SGQueue.hxx>
| |
| +#include <simgear/structure/Singleton.hxx>
| |
| +#include <Main/globals.hxx>
| |
| +#include <Viewer/renderer.hxx>
| |
| +
| |
| +#include <queue>
| |
| +#include <boost/lexical_cast.hpp>
| |
| +
| |
| +using std::string;
| |
| +using std::vector;
| |
| +using std::list;
| |
| +
| |
| +namespace sc = simgear::canvas;
| |
| +
| |
| +namespace flightgear {
| |
| + namespace http {
| |
| +
| |
| + ///////////////////////////////////////////////////////////////////////////
| |
| +
| |
| + class StringReadyListener {
| |
| + public:
| |
| + virtual void stringReady(const std::string &) = 0;
| |
| +
| |
| + virtual ~StringReadyListener() {
| |
| + }
| |
| + };
| |
| +
| |
| + struct ImageCompressionTask {
| |
| + StringReadyListener * stringReadyListener;
| |
| + string format;
| |
| + osg::ref_ptr<osg::Image> image;
| |
| +
| |
| + ImageCompressionTask() {
| |
| + stringReadyListener = NULL;
| |
| + }
| |
| +
| |
| + ImageCompressionTask(const ImageCompressionTask & other) {
| |
| + stringReadyListener = other.stringReadyListener;
| |
| + format = other.format;
| |
| + image = other.image;
| |
| + }
| |
| +
| |
| + ImageCompressionTask & operator=(const ImageCompressionTask & other) {
| |
| + stringReadyListener = other.stringReadyListener;
| |
| + format = other.format;
| |
| + image = other.image;
| |
| + return *this;
| |
| + }
| |
| +
| |
| + };
| |
| + //TODO reuse from screenshoturihandler
| |
| +
| |
| + class ImageCompressorCI : public OpenThreads::Thread {
| |
| + public:
| |
| +
| |
| + ImageCompressorCI() {
| |
| + }
| |
| + virtual void run();
| |
| + void addTask(ImageCompressionTask & task);
| |
| + private:
| |
| + typedef SGBlockingQueue<ImageCompressionTask> TaskList;
| |
| + TaskList _tasks;
| |
| + };
| |
| +
| |
| + typedef simgear::Singleton<ImageCompressorCI> ImageCompressorCISingleton;
| |
| +
| |
| + void ImageCompressorCI::run() {
| |
| + osg::ref_ptr<osgDB::ReaderWriter::Options> options = new osgDB::ReaderWriter::Options("JPEG_QUALITY 80 PNG_COMPRESSION 9");
| |
| +
| |
| + SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI is running");
| |
| + for (;;) {
| |
| + ImageCompressionTask task = _tasks.pop();
| |
| + SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI has an image");
| |
| +
| |
| + if (NULL != task.stringReadyListener) {
| |
| + SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI checking for writer for " << task.format);
| |
| + osgDB::ReaderWriter* writer = osgDB::Registry::instance()->getReaderWriterForExtension(task.format);
| |
| + if (!writer)
| |
| + continue;
| |
| +
| |
| + SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI compressing to " << task.format);
| |
| + std::stringstream outputStream;
| |
| + osgDB::ReaderWriter::WriteResult wr;
| |
| + wr = writer->writeImage(*task.image, outputStream, options);
| |
| +
| |
| + if (wr.success()) {
| |
| + SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI compressed to " << task.format);
| |
| + task.stringReadyListener->stringReady(outputStream.str());
| |
| + }
| |
| + SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI done for this image" << task.format);
| |
| +
| |
| + }
| |
| + }
| |
| + SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressorCI exiting");
| |
| + }
| |
| +
| |
| + void ImageCompressorCI::addTask(ImageCompressionTask & task) {
| |
| + _tasks.push(task);
| |
| + }
| |
| +
| |
| +
| |
| + ///////////////////////////////////////////////////////////////////////////
| |
| +
| |
| + class CanvasImageRequest : public ConnectionData, public simgear::canvas::CanvasImageReadyListener, StringReadyListener {
| |
| + public:
| |
| + ImageCompressionTask *currenttask=NULL;
| |
| + sc::CanvasPtr canvas;
| |
| +
| |
| + CanvasImageRequest(const string & window, const string & type, int canvasindex, bool stream)
| |
| + : _type(type), _stream(stream) {
| |
| + SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImageRequest: \n");
| |
| +
| |
| + if (NULL == osgDB::Registry::instance()->getReaderWriterForExtension(_type))
| |
| + throw sg_format_exception("Unsupported image type: " + type, type);
| |
| +
| |
| + CanvasMgr* canvas_mgr = static_cast<CanvasMgr*> (globals->get_subsystem("Canvas"));
| |
| + if (!canvas_mgr) {
| |
| + SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImage:CanvasMgr not found\n");
| |
| + } else {
| |
| + canvas = canvas_mgr->getCanvas(canvasindex);
| |
| + if (!canvas) {
| |
| + SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImage:Canvas not found\n");
| |
| + } else {
| |
| + SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImage:Canvas found\n");
| |
| + //SG_LOG(SG_NETWORK, SG_DEBUG, "CanvasImageRequest: found camera %d. width=%d, height=%d\n", camera, canvas->getSizeX(), canvas->getSizeY());
| |
| +
| |
| + SGConstPropertyNode_ptr canvasnode = canvas->getProps();
| |
| + if (canvasnode) {
| |
| + const char *canvasname = canvasnode->getStringValue("name");
| |
| + if (canvasname) {
| |
| + SG_LOG(SG_NETWORK, SG_INFO, "CanvasImageRequest: node=%s(%s)\n");//, canvasnode->getDisplayName().c_str(), canvasname);
| |
| + }
| |
| + }
| |
| +
| |
| + canvas->subscribe(this);
| |
| + }
| |
| + }
| |
| + }
| |
| +
| |
| + // Assumption: when unsubscribe returns,there might just be a compressor thread running,
| |
| + // causing a crash when the deconstructor finishes. Rare, but might happen. Just wait to be sure.
| |
| + virtual ~CanvasImageRequest() {
| |
| + if (currenttask){
| |
| + SG_LOG(SG_NETWORK, SG_INFO, "canvasimage task running");
| |
| +#ifdef _WIN32
| |
| + Sleep(15000);
| |
| +#else
| |
| + sleep(15);
| |
| +#endif
| |
| + }
| |
| +
| |
| + if (canvas){
| |
| + canvas->unsubscribe(this);
| |
| + }
| |
| + //_canvasimageCallback->unsubscribe(this);
| |
| + }
| |
| +
| |
| + virtual void imageReady(osg::ref_ptr<osg::Image> rawImage) {
| |
| + SG_LOG(SG_NETWORK, SG_INFO, "CanvasImage:imageReady");
| |
| + // called from a rendering thread, not from the main loop
| |
| + ImageCompressionTask task;
| |
| + currenttask = &task;
| |
| + task.image = rawImage;
| |
| + task.format = _type;
| |
| + task.stringReadyListener = this;
| |
| + ImageCompressorCISingleton::instance()->addTask(task);
| |
| + }
| |
| +
| |
| + void requestCanvasImage() {
| |
| + // _canvasimageCallback->subscribe(this);
| |
| + }
| |
| +
| |
| + mutable OpenThreads::Mutex _lock;
| |
| +
| |
| + virtual void stringReady(const string & s) {
| |
| + SG_LOG(SG_NETWORK, SG_INFO, "CanvasImage:stringReady");
| |
| +
| |
| + // called from the compressor thread
| |
| + OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
| |
| + _compressedData = s;
| |
| + // allow destructor
| |
| + currenttask = NULL;
| |
| + }
| |
| +
| |
| + string getCanvasImage() {
| |
| + string reply;
| |
| + {
| |
| + // called from the main loop
| |
| + OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
| |
| + reply = _compressedData;
| |
| + _compressedData.clear();
| |
| + }
| |
| + return reply;
| |
| + }
| |
| +
| |
| + bool isStream() const {
| |
| + return _stream;
| |
| + }
| |
| +
| |
| + const string & getType() const {
| |
| + return _type;
| |
| + }
| |
| +
| |
| + private:
| |
| + string _type;
| |
| + bool _stream;
| |
| + string _compressedData;
| |
| + //CanvasImageCallback * _canvasimageCallback;
| |
| + };
| |
| +
| |
| + CanvasImageUriHandler::CanvasImageUriHandler(const char * uri)
| |
| + : URIHandler(uri) {
| |
| + }
| |
| +
| |
| + CanvasImageUriHandler::~CanvasImageUriHandler() {
| |
| + ImageCompressorCISingleton::instance()->cancel();
| |
| + //ImageCompressorSingleton::instance()->join();
| |
| + }
| |
| +
| |
| + const static string KEY("CanvasImageUriHandler::CanvasImageRequest");
| |
| +#define BOUNDARY "--fgfs-canvasimage-boundary"
| |
| +
| |
| + bool CanvasImageUriHandler::handleGetRequest(const HTTPRequest & request, HTTPResponse & response, Connection * connection) {
| |
| + if (!ImageCompressorCISingleton::instance()->isRunning())
| |
| + ImageCompressorCISingleton::instance()->start();
| |
| +
| |
| + string type = request.RequestVariables.get("type");
| |
| + if (type.empty()) type = "jpg";
| |
| +
| |
| + string canvasindex = request.RequestVariables.get("canvasindex");
| |
| + if (canvasindex.empty()) canvasindex = "0";
| |
| +
| |
| + // string camera = request.RequestVariables.get("camera");
| |
| + string window = request.RequestVariables.get("window");
| |
| +
| |
| + bool stream = (false == request.RequestVariables.get("stream").empty());
| |
| +
| |
| + SGSharedPtr<CanvasImageRequest> canvasimageRequest;
| |
| + try {
| |
| + SG_LOG(SG_NETWORK, SG_INFO, "new CanvasImageRequest(" << window << "," << type << "," << stream << ")");
| |
| + canvasimageRequest = new CanvasImageRequest(window, type, atoi(canvasindex.c_str()),stream);
| |
| + } catch (sg_format_exception & ex) {
| |
| + SG_LOG(SG_NETWORK, SG_INFO, ex.getFormattedMessage());
| |
| + response.Header["Content-Type"] = "text/plain";
| |
| + response.StatusCode = 410;
| |
| + response.Content = ex.getFormattedMessage();
| |
| + return true;
| |
| + } catch (sg_error & ex) {
| |
| + SG_LOG(SG_NETWORK, SG_INFO, ex.getFormattedMessage());
| |
| + response.Header["Content-Type"] = "text/plain";
| |
| + response.StatusCode = 500;
| |
| + response.Content = ex.getFormattedMessage();
| |
| + return true;
| |
| + }
| |
| +
| |
| + if (false == stream) {
| |
| + response.Header["Content-Type"] = string("image/").append(type);
| |
| + response.Header["Content-Disposition"] = string("inline; filename=\"fgfs-canvasimage.").append(type).append("\"");
| |
| + } else {
| |
| + response.Header["Content-Type"] = string("multipart/x-mixed-replace; boundary=" BOUNDARY);
| |
| +
| |
| + }
| |
| +
| |
| + connection->put(KEY, canvasimageRequest);
| |
| + return false; // call me again thru poll
| |
| + }
| |
| +
| |
| + bool CanvasImageUriHandler::poll(Connection * connection) {
| |
| +
| |
| + SGSharedPtr<ConnectionData> data = connection->get(KEY);
| |
| + CanvasImageRequest * canvasimageRequest = dynamic_cast<CanvasImageRequest*> (data.get());
| |
| + if (NULL == canvasimageRequest) return true; // Should not happen, kill the connection
| |
| +
| |
| + const string & canvasimage = canvasimageRequest->getCanvasImage();
| |
| + if (canvasimage.empty()) {
| |
| + SG_LOG(SG_NETWORK, SG_INFO, "No canvasimage available.");
| |
| + return false; // not ready yet, call again.
| |
| + }
| |
| +
| |
| + SG_LOG(SG_NETWORK, SG_INFO, "CanvasImage is ready, size=" << canvasimage.size());
| |
| +
| |
| + if (canvasimageRequest->isStream()) {
| |
| + string s(BOUNDARY "\r\nContent-Type: image/");
| |
| + s.append(canvasimageRequest->getType()).append("\r\nContent-Length:");
| |
| + s += boost::lexical_cast<string>(canvasimage.size());
| |
| + s += "\r\n\r\n";
| |
| + connection->write(s.c_str(), s.length());
| |
| + }
| |
| +
| |
| + connection->write(canvasimage.data(), canvasimage.size());
| |
| +
| |
| + /* unknown purpose
| |
| + if (canvasimageRequest->isStream()) {
| |
| + canvasimageRequest->requestCanvasImage();
| |
| + // continue until user closes connection
| |
| + return false;
| |
| + }
| |
| + */
| |
| +
| |
| + // single canvasimage, send terminating chunk
| |
| + connection->remove(KEY);
| |
| + connection->write("", 0);
| |
| + return true; // done.
| |
| + }
| |
| +
| |
| + } // namespace http
| |
| +} // namespace flightgear
| |
| +
| |
| diff --git a/src/Network/http/CanvasImageUriHandler.hxx b/src/Network/http/CanvasImageUriHandler.hxx
| |
| new file mode 100644
| |
| index 0000000..f50be37
| |
| --- /dev/null
| |
| +++ b/src/Network/http/CanvasImageUriHandler.hxx
| |
| @@ -0,0 +1,40 @@
| |
| +// CanvasImageUriHandler.hxx -- Provide canvasimages via http
| |
| +//
| |
| +// Written by Torsten Dreyer, started April 2014.
| |
| +//
| |
| +// Copyright (C) 2014 Torsten Dreyer
| |
| +// derived from ScreenshotUrihandler by ThomasS
| |
| +// This program is free software; you can redistribute it and/or
| |
| +// modify it under the terms of the GNU General Public License as
| |
| +// published by the Free Software Foundation; either version 2 of the
| |
| +// License, or (at your option) any later version.
| |
| +//
| |
| +// This program is distributed in the hope that it will be useful, but
| |
| +// WITHOUT ANY WARRANTY; without even the implied warranty of
| |
| +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
| |
| +// General Public License for more details.
| |
| +//
| |
| +// You should have received a copy of the GNU General Public License
| |
| +// along with this program; if not, write to the Free Software
| |
| +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| |
| +
| |
| +#ifndef __FG_CANVASIMAGE_URI_HANDLER_HXX
| |
| +#define __FG_CANVASIMAGE_URI_HANDLER_HXX
| |
| +
| |
| +#include "urihandler.hxx"
| |
| +
| |
| +namespace flightgear {
| |
| +namespace http {
| |
| +
| |
| +class CanvasImageUriHandler : public URIHandler {
| |
| +public:
| |
| + CanvasImageUriHandler( const char * uri = "/canvasimage/" );
| |
| + ~CanvasImageUriHandler();
| |
| + virtual bool handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
| |
| + virtual bool poll( Connection * connection );
| |
| +};
| |
| +
| |
| +} // namespace http
| |
| +} // namespace flightgear
| |
| +
| |
| +#endif //#define __FG_CANVASIMAGE_URI_HANDLER_HXX
| |
| diff --git a/src/Network/http/httpd.cxx b/src/Network/http/httpd.cxx
| |
| index 358b247..3bfff3a 100644
| |
| --- a/src/Network/http/httpd.cxx
| |
| +++ b/src/Network/http/httpd.cxx
| |
| @@ -22,6 +22,7 @@
| |
| #include "HTTPRequest.hxx"
| |
| #include "PropertyChangeWebsocket.hxx"
| |
| #include "ScreenshotUriHandler.hxx"
| |
| +#include "CanvasImageUriHandler.hxx"
| |
| #include "PropertyUriHandler.hxx"
| |
| #include "JsonUriHandler.hxx"
| |
| #include "FlightHistoryUriHandler.hxx"
| |
| @@ -443,6 +444,11 @@ void MongooseHttpd::init()
| |
| _uriHandler.push_back(new flightgear::http::ScreenshotUriHandler(uri));
| |
| }
| |
|
| |
| +if ((uri = n->getStringValue("canvasimage"))[0] != 0) {
| |
| + SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding canvasimage uri handler at " << uri);
| |
| + _uriHandler.push_back(new flightgear::http::CanvasImageUriHandler(uri));
| |
| + }
| |
| +
| |
| if ((uri = n->getStringValue("property"))[0] != 0) {
| |
| SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding property uri handler at " << uri);
| |
| _uriHandler.push_back(new flightgear::http::PropertyUriHandler(uri));
| |
|
| |
| </syntaxhighlight>
| |
|
| |
| === Base Package === | | === Base Package === |
| CanvasView.html | | CanvasView.html |