Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/wasm remote 2 #1288

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
*.zip
build-*
AppRun*
*.AppImage
*.txz
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@
[submodule "3rdparty/hap"]
path = 3rdparty/hap
url = https://github.com/Vidvox/hap
[submodule "3rdparty/qml-remote"]
path = 3rdparty/qml-remote
url = https://github.com/ossia/qml-remote.git
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remove that part, we don't want this as a submodule

1 change: 1 addition & 0 deletions 3rdparty/qml-remote
Submodule qml-remote added at e4427b
7 changes: 6 additions & 1 deletion src/plugins/score-plugin-remotecontrol/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ set(HDRS
"RemoteControl/DocumentPlugin.hpp"
"i-score-remote/RemoteApplication.hpp"
"score_plugin_remotecontrol.hpp"
"RemoteControl/Http_server.hpp"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you name that file HttpServer in line with the rest of score ?

)
set(SRCS
"${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/Settings/Model.cpp"
Expand All @@ -41,12 +42,16 @@ set(SRCS
"${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/ApplicationPlugin.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/DocumentPlugin.cpp"

"${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/Http_server.cpp"

"${CMAKE_CURRENT_SOURCE_DIR}/score_plugin_remotecontrol.cpp"
)

file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/build-wasm/ DESTINATION ${CMAKE_BINARY_DIR}/src/plugins/score-plugin-remotecontrol/CMakeFiles/score_plugin_remotecontrol.dir/RemoteControl/build-wasm/)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we won't need that: the remote's .wasm file is not supposed to be built as part of score but in a different repo which will be in the package manager


add_library(${PROJECT_NAME} ${SRCS} ${HDRS} ${QRCS})

target_link_libraries(${PROJECT_NAME} PUBLIC score_plugin_scenario score_plugin_js ${QT_PREFIX}::WebSockets)
target_link_libraries(${PROJECT_NAME} PUBLIC score_plugin_scenario score_plugin_js ${QT_PREFIX}::WebSockets ossia)

setup_score_plugin(${PROJECT_NAME})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <RemoteControl/ApplicationPlugin.hpp>
#include <RemoteControl/DocumentPlugin.hpp>


namespace RemoteControl
{
ApplicationPlugin::ApplicationPlugin(const score::GUIApplicationContext& app)
Expand All @@ -16,6 +18,8 @@ void ApplicationPlugin::on_createdDocument(score::Document& doc)
{
doc.model().addPluginModel(new DocumentPlugin{
doc.context(), &doc.model()});

m_server.start_thread();
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once
#include <score/plugins/application/GUIApplicationPlugin.hpp>

#include <RemoteControl/Http_server.hpp>
namespace RemoteControl
{
class ApplicationPlugin final : public score::GUIApplicationPlugin
Expand All @@ -10,5 +11,6 @@ class ApplicationPlugin final : public score::GUIApplicationPlugin

protected:
void on_createdDocument(score::Document& doc) override;
Http_server m_server;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HttpServer

};
}
325 changes: 325 additions & 0 deletions src/plugins/score-plugin-remotecontrol/RemoteControl/Http_server.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
//
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//

#include <RemoteControl/Http_server.hpp>

//------------------------------------------------------------------------------

namespace RemoteControl
{

Http_server::Http_server()
{
// m_docRoot = "/tmp";
}

Http_server::~Http_server()
{
shutdown(m_listenSocket, SHUT_RDWR);
ioc.stop();
m_serverThread.join();
}

// Return a reasonable mime type based on the extension of a file.
beast::string_view
Http_server::mime_type(beast::string_view path)
{
using beast::iequals;
auto const ext = [&path]
{
auto const pos = path.rfind(".");
if(pos == beast::string_view::npos)
return beast::string_view{};
return path.substr(pos);
}();
if(iequals(ext, ".htm")) return "text/html";
if(iequals(ext, ".html")) return "text/html";
if(iequals(ext, ".php")) return "text/html";
if(iequals(ext, ".css")) return "text/css";
if(iequals(ext, ".txt")) return "text/plain";
if(iequals(ext, ".js")) return "application/javascript";
if(iequals(ext, ".json")) return "application/json";
if(iequals(ext, ".xml")) return "application/xml";
if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
if(iequals(ext, ".flv")) return "video/x-flv";
if(iequals(ext, ".png")) return "image/png";
if(iequals(ext, ".jpe")) return "image/jpeg";
if(iequals(ext, ".jpeg")) return "image/jpeg";
if(iequals(ext, ".jpg")) return "image/jpeg";
if(iequals(ext, ".gif")) return "image/gif";
if(iequals(ext, ".bmp")) return "image/bmp";
if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
if(iequals(ext, ".tiff")) return "image/tiff";
if(iequals(ext, ".tif")) return "image/tiff";
if(iequals(ext, ".svg")) return "image/svg+xml";
if(iequals(ext, ".svgz")) return "image/svg+xml";
return "application/text";
}

// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string
Http_server::path_cat(
beast::string_view base,
beast::string_view path)
{
if (base.empty())
return std::string(path);
std::string result(base);
#ifdef BOOST_MSVC
char constexpr path_separator = '\\';
if(result.back() == path_separator)
result.resize(result.size() - 1);
result.append(path.data(), path.size());
for(auto& c : result)
if(c == '/')
c = path_separator;
#else
char constexpr path_separator = '/';
if(result.back() == path_separator)
result.resize(result.size() - 1);
result.append(path.data(), path.size());
#endif
return result;
}

// This function produces an HTTP response for the given
// request. The type of the response object depends on the
// contents of the request, so the interface requires the
// caller to pass a generic lambda for receiving the response.
template<
class Body, class Allocator,
class Send>
void
Http_server::handle_request(
beast::string_view doc_root,
http::request<Body, http::basic_fields<Allocator>>&& req,
Send&& send)
{
// Returns a bad request response
auto const bad_request =
[&req](beast::string_view why)
{
http::response<http::string_body> res{http::status::bad_request, req.version()};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = std::string(why);
res.prepare_payload();
return res;
};

// Returns a not found response
auto const not_found =
[&req](beast::string_view target)
{
http::response<http::string_body> res{http::status::not_found, req.version()};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = "The resource '" + std::string(target) + "' was not found.y<br> Go to the following address : http://ip_address:port/remote.html.";
res.prepare_payload();
return res;
};

// Returns a server error response
auto const server_error =
[&req](beast::string_view what)
{
http::response<http::string_body> res{http::status::internal_server_error, req.version()};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, "text/html");
res.keep_alive(req.keep_alive());
res.body() = "An error occurred: '" + std::string(what) + "'";
res.prepare_payload();
return res;
};

// Make sure we can handle the method
if( req.method() != http::verb::get &&
req.method() != http::verb::head)
return send(bad_request("Unknown HTTP-method"));

// Request path must be absolute and not contain "..".
if( req.target().empty() ||
req.target()[0] != '/' ||
req.target().find("..") != beast::string_view::npos)
return send(bad_request("Illegal request-target"));

// Build the path to the requested file
std::string path = path_cat(doc_root, req.target());
if(req.target().back() == '/')
path.append("index.html");

// Attempt to open the file
beast::error_code ec;
http::file_body::value_type body;
body.open(path.c_str(), beast::file_mode::scan, ec);

// Handle the case where the file doesn't exist
if(ec == beast::errc::no_such_file_or_directory)
return send(not_found(req.target()));

// Handle an unknown error
if(ec)
return send(server_error(ec.message()));

// Cache the size since we need it after the move
auto const size = body.size();

// Respond to HEAD request
if(req.method() == http::verb::head)
{
http::response<http::empty_body> res{http::status::ok, req.version()};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, mime_type(path));
res.content_length(size);
res.keep_alive(false);
return send(std::move(res));
}

// Respond to GET request
http::response<http::file_body> res{
std::piecewise_construct,
std::make_tuple(std::move(body)),
std::make_tuple(http::status::ok, req.version())};
res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
res.set(http::field::content_type, mime_type(path));
res.content_length(size);
res.keep_alive(false);
return send(std::move(res));
}

//------------------------------------------------------------------------------

// Report a failure
void
Http_server::fail(beast::error_code ec, char const* what)
{
std::cerr << what << ": " << ec.message() << "\n";
}

// Handles an HTTP server connection
void
Http_server::do_session(
tcp::socket& socket,
std::shared_ptr<std::string const> const& doc_root)
{
bool close = false;
beast::error_code ec;

// This buffer is required to persist across reads
beast::flat_buffer buffer;

// This lambda is used to send messages
send_lambda<tcp::socket> lambda{socket, close, ec};

for(;;)
{
// Read a request
http::request<http::string_body> req;
http::read(socket, buffer, req, ec);
if(ec == http::error::end_of_stream)
break;
if(ec)
return Http_server::fail(ec, "read");

// Send the response
Http_server::handle_request(*doc_root, std::move(req), lambda);
if(ec)
return Http_server::fail(ec, "write");
if(close)
{
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
break;
}
}

// Send a TCP shutdown
socket.shutdown(tcp::socket::shutdown_send, ec);

// At this point the connection is closed gracefully
}

//------------------------------------------------------------------------------

// Set the IP address in the remote.html file
void
Http_server::set_ip_address(std::string address)
{
std::rename("./src/plugins/score-plugin-remotecontrol/CMakeFiles/score_plugin_remotecontrol.dir/RemoteControl/build-wasm/remote.html",
"./src/plugins/score-plugin-remotecontrol/CMakeFiles/score_plugin_remotecontrol.dir/RemoteControl/build-wasm/remote.html~");

std::ifstream old_file("./src/plugins/score-plugin-remotecontrol/CMakeFiles/score_plugin_remotecontrol.dir/RemoteControl/build-wasm/remote.html~");
std::ofstream new_file("./src/plugins/score-plugin-remotecontrol/CMakeFiles/score_plugin_remotecontrol.dir/RemoteControl/build-wasm/remote.html");

std::string addr = "\"" + address + "\"";

for( std::string contents_of_file; std::getline(old_file, contents_of_file); ) {
std::string::size_type position = contents_of_file.find("%SCORE_IP_ADDRESS%");
if( position != std::string::npos )
contents_of_file = contents_of_file.replace(position, 18, addr);
new_file << contents_of_file << '\n';
}
}

//------------------------------------------------------------------------------

// Launch the open_server function in a thread
void
Http_server::start_thread()
{
m_serverThread = std::thread{[this] { open_server(); }};
}

//------------------------------------------------------------------------------

// Open a server using sockets
int
Http_server::open_server()
{
try
{
auto const address2 = net::ip::make_address("0.0.0.0");
auto const port = static_cast<unsigned short>(std::atoi("8080"));
auto const m_docRoot = std::make_shared<std::string>("./src/plugins/score-plugin-remotecontrol/CMakeFiles/score_plugin_remotecontrol.dir/RemoteControl/build-wasm/");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line only works on the build computer.
instead we have to look for it in the user library which is where it will be installed on the user's computer:

#include <Library/LibrarySettings.hpp>
#include <score/application/ApplicationContext.hpp>

QString chemin_vers_dossier_packages = score::AppContext().settings<Library::Settings::Model>().getPackagesPath();


bool is_ip_address_set = false;

// The acceptor receives incoming connections
tcp::acceptor acceptor{ioc, {address2, port}};
m_listenSocket = acceptor.native_handle();
for(;;)
{
// This will receive the new connection
tcp::socket socket{ioc};

// Block until we get a connection
acceptor.accept(socket);

// Set ip address
if(!is_ip_address_set)
{
set_ip_address(socket.local_endpoint().address().to_string());
is_ip_address_set = true;
}

// Launch the session, transferring ownership of the socket
do_session(socket, m_docRoot);
}
}
catch (const std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}

}
Loading