TooB ML support for large model. .zip file uploads.
This commit is contained in:
+2
-2
@@ -14,14 +14,14 @@
|
|||||||
"socketServerAddress": "0.0.0.0:80",
|
"socketServerAddress": "0.0.0.0:80",
|
||||||
|
|
||||||
/* Number of threads to use for servicing websockets */
|
/* Number of threads to use for servicing websockets */
|
||||||
"threads" : 10,
|
"threads" : 5,
|
||||||
|
|
||||||
"logHttpRequests": false,
|
"logHttpRequests": false,
|
||||||
/* { None=0,Error =1,Warning =2,Info = 3, Debug=4} */
|
/* { None=0,Error =1,Warning =2,Info = 3, Debug=4} */
|
||||||
"logLevel": 3,
|
"logLevel": 3,
|
||||||
|
|
||||||
/* Maximum filesize to allow when uploading */
|
/* Maximum filesize to allow when uploading */
|
||||||
"maxUploadSize": 1048576,
|
"maxUploadSize": 536870912,
|
||||||
|
|
||||||
/* Provide access point capture redirects on this gateway. -- provides automatic browser launching on Access Point access.
|
/* Provide access point capture redirects on this gateway. -- provides automatic browser launching on Access Point access.
|
||||||
(not implemented)
|
(not implemented)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -117,7 +117,7 @@ and uses model files from the GuitarML Neural Pi project (https://github.com/Gui
|
|||||||
mod:brand "TooB";
|
mod:brand "TooB";
|
||||||
mod:label "TooB ML";
|
mod:label "TooB ML";
|
||||||
lv2:requiredFeature urid:map, work:schedule ;
|
lv2:requiredFeature urid:map, work:schedule ;
|
||||||
lv2:optionalFeature lv2:hardRTCapable;
|
lv2:optionalFeature state::freePath, state:makePath, state:mapPath ;
|
||||||
lv2:extensionData state:interface, work:interface;
|
lv2:extensionData state:interface, work:interface;
|
||||||
|
|
||||||
patch:readable
|
patch:readable
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"socket_server_port": 8080,
|
"socket_server_port": 8080,
|
||||||
"socket_server_address": "*",
|
"socket_server_address": "*",
|
||||||
"debug": true,
|
"debug": true,
|
||||||
"max_upload_size": 1048576,
|
"max_upload_size": 536870912,
|
||||||
"fakeAndroid": false,
|
"fakeAndroid": false,
|
||||||
"ui_plugins": []
|
"ui_plugins": []
|
||||||
}
|
}
|
||||||
@@ -428,6 +428,18 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
hasSelectedFileOrFolder(): boolean {
|
hasSelectedFileOrFolder(): boolean {
|
||||||
return this.state.hasSelection && this.state.selectedFile !== "";
|
return this.state.hasSelection && this.state.selectedFile !== "";
|
||||||
}
|
}
|
||||||
|
getFileExtensionList(uiFileProperty: UiFileProperty): string {
|
||||||
|
let result = "";
|
||||||
|
for (var fileType of uiFileProperty.fileTypes)
|
||||||
|
{
|
||||||
|
if (fileType.fileExtension !== "" && fileType.fileExtension !== ".zip")
|
||||||
|
{
|
||||||
|
if (result !== "") result = result + ",";
|
||||||
|
result += fileType.fileExtension;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
render() {
|
render() {
|
||||||
let classes = this.props.classes;
|
let classes = this.props.classes;
|
||||||
let columnWidth = this.state.columnWidth;
|
let columnWidth = this.state.columnWidth;
|
||||||
@@ -573,9 +585,13 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
this.setState({ openUploadFileDialog: false });
|
this.setState({ openUploadFileDialog: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uploadPage={"uploadUserFile?directory=" + encodeURIComponent(
|
uploadPage={
|
||||||
|
"uploadUserFile?directory=" + encodeURIComponent(
|
||||||
pathConcat(this.props.fileProperty.directory,this.state.navDirectory)
|
pathConcat(this.props.fileProperty.directory,this.state.navDirectory)
|
||||||
)}
|
)
|
||||||
|
+ "&ext="
|
||||||
|
+ encodeURIComponent(this.getFileExtensionList(this.props.fileProperty))
|
||||||
|
}
|
||||||
onUploaded={() => { this.requestFiles(this.state.navDirectory); }}
|
onUploaded={() => { this.requestFiles(this.state.navDirectory); }}
|
||||||
fileProperty={this.props.fileProperty}
|
fileProperty={this.props.fileProperty}
|
||||||
|
|
||||||
|
|||||||
@@ -787,7 +787,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
this.androidHost = new FakeAndroidHost();
|
this.androidHost = new FakeAndroidHost();
|
||||||
}
|
}
|
||||||
this.debug = !!data.debug;
|
this.debug = !!data.debug;
|
||||||
let { socket_server_port, socket_server_address } = data;
|
let { socket_server_port, socket_server_address,max_upload_size } = data;
|
||||||
if ((!socket_server_address) || socket_server_address === "*") {
|
if ((!socket_server_address) || socket_server_address === "*") {
|
||||||
socket_server_address = window.location.hostname;
|
socket_server_address = window.location.hostname;
|
||||||
}
|
}
|
||||||
@@ -798,6 +798,7 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
|
|
||||||
this.socketServerUrl = socket_server;
|
this.socketServerUrl = socket_server;
|
||||||
this.varServerUrl = var_server_url;
|
this.varServerUrl = var_server_url;
|
||||||
|
this.maxFileUploadSize = parseInt(max_upload_size);
|
||||||
|
|
||||||
this.webSocket = new PiPedalSocket(
|
this.webSocket = new PiPedalSocket(
|
||||||
this.socketServerUrl,
|
this.socketServerUrl,
|
||||||
|
|||||||
@@ -208,7 +208,11 @@ export default class UploadFileDialog extends ResizeResponsiveComponent<UploadFi
|
|||||||
{
|
{
|
||||||
throw new Error("Invalid file extension.");
|
throw new Error("Invalid file extension.");
|
||||||
}
|
}
|
||||||
let filename = await this.model.uploadFile(this.props.uploadPage, this.uploadList[i].file, "application/octet-stream", upload.abortController);
|
let filename = await this.model.uploadFile(
|
||||||
|
this.props.uploadPage,
|
||||||
|
this.uploadList[i].file,
|
||||||
|
"application/octet-stream",
|
||||||
|
upload.abortController);
|
||||||
this.props.onUploaded(filename);
|
this.props.onUploaded(filename);
|
||||||
upload.status = FileUploadStatus.Uploaded;
|
upload.status = FileUploadStatus.Uploaded;
|
||||||
upload.statusMessage = "Uploaded.";
|
upload.statusMessage = "Uploaded.";
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
set (PIPEDAL_SOURCES
|
set (PIPEDAL_SOURCES
|
||||||
|
WebServerConfig.cpp WebServerConfig.hpp
|
||||||
Finally.hpp
|
Finally.hpp
|
||||||
ZipFile.cpp ZipFile.hpp
|
ZipFile.cpp ZipFile.hpp
|
||||||
TemporaryFile.cpp TemporaryFile.hpp
|
TemporaryFile.cpp TemporaryFile.hpp
|
||||||
|
|||||||
@@ -2135,3 +2135,7 @@ void PiPedalModel::OnNotifyLv2RealtimeError(int64_t instanceId, const std::strin
|
|||||||
}
|
}
|
||||||
delete[] t;
|
delete[] t;
|
||||||
}
|
}
|
||||||
|
std::filesystem::path PiPedalModel::GetPluginUploadDirectory() const
|
||||||
|
{
|
||||||
|
return storage.GetPluginUploadDirectory();
|
||||||
|
}
|
||||||
@@ -196,6 +196,7 @@ namespace pipedal
|
|||||||
virtual ~PiPedalModel();
|
virtual ~PiPedalModel();
|
||||||
|
|
||||||
uint16_t GetWebPort() const { return webPort; }
|
uint16_t GetWebPort() const { return webPort; }
|
||||||
|
std::filesystem::path GetPluginUploadDirectory() const;
|
||||||
void Close();
|
void Close();
|
||||||
|
|
||||||
void SetOnboarding(bool value);
|
void SetOnboarding(bool value);
|
||||||
|
|||||||
+2
-1
@@ -1441,7 +1441,8 @@ void PluginHost::CheckForResourceInitialization(const std::string &pluginUri,con
|
|||||||
if (plugin)
|
if (plugin)
|
||||||
{
|
{
|
||||||
std::filesystem::path bundlePath = plugin->bundle_path();
|
std::filesystem::path bundlePath = plugin->bundle_path();
|
||||||
|
if (!plugin->piPedalUI())
|
||||||
|
return;
|
||||||
const auto& fileProperties = plugin->piPedalUI()->fileProperties();
|
const auto& fileProperties = plugin->piPedalUI()->fileProperties();
|
||||||
if (fileProperties.size() != 0 && !pluginsThatHaveBeenCheckedForResources.contains(pluginUri))
|
if (fileProperties.size() != 0 && !pluginsThatHaveBeenCheckedForResources.contains(pluginUri))
|
||||||
{
|
{
|
||||||
|
|||||||
+2
-6
@@ -1680,18 +1680,14 @@ std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, co
|
|||||||
{
|
{
|
||||||
if (!ensureNoDotDot(directory))
|
if (!ensureNoDotDot(directory))
|
||||||
{
|
{
|
||||||
throw std::logic_error("Permission denied.");
|
throw std::logic_error(SS("Invalide filename: " << filename));
|
||||||
}
|
}
|
||||||
std::filesystem::path filePath{filename};
|
std::filesystem::path filePath{filename};
|
||||||
|
|
||||||
if (filePath.has_parent_path())
|
|
||||||
{
|
|
||||||
throw std::logic_error("Permission denied.");
|
|
||||||
}
|
|
||||||
std::filesystem::path result = this->GetPluginUploadDirectory() / directory / filename;
|
std::filesystem::path result = this->GetPluginUploadDirectory() / directory / filename;
|
||||||
if (!this->IsValidSampleFileName(result))
|
if (!this->IsValidSampleFileName(result))
|
||||||
{
|
{
|
||||||
throw std::logic_error("Permission denied.");
|
throw std::logic_error(SS("Invalid upload path: " << result));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -114,7 +114,7 @@ public:
|
|||||||
|
|
||||||
std::filesystem::path GetPluginUploadDirectory() const;
|
std::filesystem::path GetPluginUploadDirectory() const;
|
||||||
|
|
||||||
std::vector<std::string> GetPedalboards();
|
//std::vector<std::string> GetPedalboards();
|
||||||
|
|
||||||
const BankIndex & GetBanks() const { return bankIndex; }
|
const BankIndex & GetBanks() const { return bankIndex; }
|
||||||
|
|
||||||
|
|||||||
+38
-19
@@ -56,7 +56,8 @@ const std::filesystem::path WEB_TEMP_DIR{"/var/pipedal/web_temp"};
|
|||||||
|
|
||||||
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
||||||
|
|
||||||
const size_t MAX_READ_SIZE = 512 * 1024 * 1024;;
|
const size_t MAX_READ_SIZE = 512 * 1024 * 1024;
|
||||||
|
;
|
||||||
using namespace boost;
|
using namespace boost;
|
||||||
|
|
||||||
class request_with_file_upload : public websocketpp::http::parser::parser
|
class request_with_file_upload : public websocketpp::http::parser::parser
|
||||||
@@ -163,18 +164,24 @@ std::istream&request_with_file_upload::get_body_input_stream()
|
|||||||
{
|
{
|
||||||
m_inputStream.open(this->m_temporaryFile->Path(), std::ios_base::in | std::ios_base::binary);
|
m_inputStream.open(this->m_temporaryFile->Path(), std::ios_base::in | std::ios_base::binary);
|
||||||
return m_inputStream;
|
return m_inputStream;
|
||||||
}else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
auto body = super::get_body();
|
auto body = super::get_body();
|
||||||
m_stringInputStream.write(body.c_str(), body.length());
|
m_stringInputStream.write(body.c_str(), body.length());
|
||||||
m_stringInputStream.flush();
|
m_stringInputStream.flush();
|
||||||
m_stringInputStream.seekg(0);
|
m_stringInputStream.seekg(0);
|
||||||
return m_stringInputStream;
|
return m_stringInputStream;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (m_uploading_to_file)
|
if (m_uploading_to_file)
|
||||||
{
|
{
|
||||||
return m_inputStream;
|
return m_inputStream;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return m_stringInputStream;
|
return m_stringInputStream;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,9 +228,11 @@ inline size_t request_with_file_upload::process_body(char const *buf, size_t len
|
|||||||
if (m_body_encoding == body_encoding::plain)
|
if (m_body_encoding == body_encoding::plain)
|
||||||
{
|
{
|
||||||
size_t processed = (std::min)(m_body_bytes_needed, len);
|
size_t processed = (std::min)(m_body_bytes_needed, len);
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
m_outputStream.write(buf, processed);
|
m_outputStream.write(buf, processed);
|
||||||
} catch (const std::exception &e)
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
Lv2Log::error(SS("Can't write to web temporary file " << m_temporaryFile->Path() << ". " << e.what()));
|
Lv2Log::error(SS("Can't write to web temporary file " << m_temporaryFile->Path() << ". " << e.what()));
|
||||||
ec = error::make_error_code(error::istream_bad);
|
ec = error::make_error_code(error::istream_bad);
|
||||||
@@ -507,7 +516,7 @@ public:
|
|||||||
typedef CustomPpConfig type;
|
typedef CustomPpConfig type;
|
||||||
typedef websocketpp::config::asio base;
|
typedef websocketpp::config::asio base;
|
||||||
|
|
||||||
static const size_t max_http_body_size = MAX_READ_SIZE; // websocketpp::config::asio::max_http_body_size;
|
static size_t max_http_body_size; // websocketpp::config::asio::max_http_body_size;
|
||||||
typedef pipedal_elog elog_type;
|
typedef pipedal_elog elog_type;
|
||||||
typedef pipedal_alog alog_type;
|
typedef pipedal_alog alog_type;
|
||||||
|
|
||||||
@@ -528,6 +537,8 @@ public:
|
|||||||
transport_type;
|
transport_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
size_t CustomPpConfig::max_http_body_size = MAX_READ_SIZE;
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
pipedal::last_modified(const std::filesystem::path &path)
|
pipedal::last_modified(const std::filesystem::path &path)
|
||||||
{
|
{
|
||||||
@@ -637,6 +648,7 @@ namespace pipedal
|
|||||||
int port = -1;
|
int port = -1;
|
||||||
std::filesystem::path rootPath;
|
std::filesystem::path rootPath;
|
||||||
int threads = 1;
|
int threads = 1;
|
||||||
|
size_t maxUploadSize = 512 * 1024 * 1024;
|
||||||
|
|
||||||
std::thread *pBgThread = nullptr;
|
std::thread *pBgThread = nullptr;
|
||||||
std::recursive_mutex io_mutex;
|
std::recursive_mutex io_mutex;
|
||||||
@@ -658,13 +670,13 @@ namespace pipedal
|
|||||||
}
|
}
|
||||||
|
|
||||||
virtual std::istream &get_body_input_stream() override { return m_request.get_body_input_stream(); }
|
virtual std::istream &get_body_input_stream() override { return m_request.get_body_input_stream(); }
|
||||||
virtual const std::filesystem::path& get_body_temporary_file()override {
|
virtual const std::filesystem::path &get_body_temporary_file() override
|
||||||
|
{
|
||||||
return m_request.get_body_input_file();
|
return m_request.get_body_input_file();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual size_t content_length() const { return m_request.content_length(); }
|
virtual size_t content_length() const { return m_request.content_length(); }
|
||||||
|
|
||||||
|
|
||||||
virtual const std::string &method() const { return m_request.get_method(); }
|
virtual const std::string &method() const { return m_request.get_method(); }
|
||||||
virtual const std::string &get(const std::string &key) const { return m_request.get_header(key); }
|
virtual const std::string &get(const std::string &key) const { return m_request.get_header(key); }
|
||||||
virtual bool keepAlive() const
|
virtual bool keepAlive() const
|
||||||
@@ -1262,13 +1274,7 @@ namespace pipedal
|
|||||||
this->pBgThread = new std::thread(ThreadProc, this);
|
this->pBgThread = new std::thread(ThreadProc, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebServerImpl(const std::string &address, int port, const char *rootPath, int threads)
|
WebServerImpl(const std::string &address, int port, const char *rootPath, int threads, size_t maxUploadSize);
|
||||||
: address(address),
|
|
||||||
rootPath(rootPath),
|
|
||||||
port(port),
|
|
||||||
threads(threads)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
} // namespace pipedal
|
} // namespace pipedal
|
||||||
|
|
||||||
@@ -1284,8 +1290,21 @@ std::shared_ptr<ISocketFactory> WebServerImpl::GetSocketFactory(const uri &reque
|
|||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
WebServerImpl::WebServerImpl(const std::string &address, int port, const char *rootPath, int threads, size_t maxUploadSize)
|
||||||
std::shared_ptr<WebServer> pipedal::WebServer::create(const boost::asio::ip::address &address, int port, const char *rootPath, int threads)
|
: address(address),
|
||||||
|
rootPath(rootPath),
|
||||||
|
port(port),
|
||||||
|
threads(threads),
|
||||||
|
maxUploadSize(maxUploadSize)
|
||||||
{
|
{
|
||||||
return std::shared_ptr<WebServer>(new WebServerImpl(address.to_string(), port, rootPath, threads));
|
::CustomPpConfig::max_http_body_size = maxUploadSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<WebServer> pipedal::WebServer::create(
|
||||||
|
const boost::asio::ip::address &address,
|
||||||
|
int port,
|
||||||
|
const char *rootPath, int threads,
|
||||||
|
size_t maxUploadSize)
|
||||||
|
{
|
||||||
|
return std::shared_ptr<WebServer>(new WebServerImpl(address.to_string(), port, rootPath, threads, maxUploadSize));
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -236,7 +236,8 @@ public:
|
|||||||
const boost::asio::ip::address &address,
|
const boost::asio::ip::address &address,
|
||||||
int port,
|
int port,
|
||||||
const char *rootPath,
|
const char *rootPath,
|
||||||
int threads);
|
int threads,
|
||||||
|
size_t maxUploadsize);
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,571 @@
|
|||||||
|
// Copyright (c) 2024 Robin Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
#include "WebServerConfig.hpp"
|
||||||
|
#include "WebServer.hpp"
|
||||||
|
#include <boost/system/error_code.hpp>
|
||||||
|
#include <filesystem>
|
||||||
|
#include "PiPedalConfiguration.hpp"
|
||||||
|
#include "PiPedalModel.hpp"
|
||||||
|
#include "Banks.hpp"
|
||||||
|
#include "Ipv6Helpers.hpp"
|
||||||
|
#include <memory>
|
||||||
|
#include "ZipFile.hpp"
|
||||||
|
#include "PiPedalUI.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
#define PRESET_EXTENSION ".piPreset"
|
||||||
|
#define BANK_EXTENSION ".piBank"
|
||||||
|
#define PLUGIN_PRESETS_EXTENSION ".piPluginPresets"
|
||||||
|
|
||||||
|
|
||||||
|
using namespace pipedal;
|
||||||
|
using namespace boost::system;
|
||||||
|
|
||||||
|
static std::vector<std::string> split(const std::string& s, char delimiter) {
|
||||||
|
std::vector<std::string> tokens;
|
||||||
|
std::string token;
|
||||||
|
std::istringstream tokenStream(s);
|
||||||
|
while (std::getline(tokenStream, token, delimiter)) {
|
||||||
|
tokens.push_back(token);
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExtensionChecker {
|
||||||
|
public:
|
||||||
|
ExtensionChecker(const std::string&extensionList)
|
||||||
|
:extensions(split(extensionList,','))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
bool IsValidExtension(const std::string&extension)
|
||||||
|
{
|
||||||
|
if (extensions.size() == 0)
|
||||||
|
return true;
|
||||||
|
for (const auto&ext: extensions)
|
||||||
|
{
|
||||||
|
if (ext == extension)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
std::vector<std::string> extensions;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DownloadIntercept : public RequestHandler
|
||||||
|
{
|
||||||
|
PiPedalModel *model;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DownloadIntercept(PiPedalModel *model)
|
||||||
|
: RequestHandler("/var"),
|
||||||
|
model(model)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool wants(const std::string &method, const uri &request_uri) const
|
||||||
|
{
|
||||||
|
if (request_uri.segment_count() != 2 || request_uri.segment(0) != "var")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string segment = request_uri.segment(1);
|
||||||
|
if (segment == "uploadPluginPresets")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (segment == "downloadPluginPresets")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (segment == "downloadPreset")
|
||||||
|
{
|
||||||
|
std::string strInstanceId = request_uri.query("id");
|
||||||
|
if (strInstanceId != "")
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (segment == "uploadPreset")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (segment == "downloadBank")
|
||||||
|
{
|
||||||
|
std::string strInstanceId = request_uri.query("id");
|
||||||
|
if (strInstanceId != "")
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (segment == "uploadBank")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
} else if (segment == "uploadUserFile")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetContentDispositionHeader(const std::string &name, const std::string &extension)
|
||||||
|
{
|
||||||
|
std::string fileName = name.substr(0, 64) + extension;
|
||||||
|
std::stringstream s;
|
||||||
|
|
||||||
|
s << "attachment; filename*=" << HtmlHelper::Rfc5987EncodeFileName(fileName) << "; filename=\"" << HtmlHelper::SafeFileName(fileName) << "\"";
|
||||||
|
std::string result = s.str();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetPluginPresets(const uri &request_uri, std::string *pName, std::string *pContent)
|
||||||
|
{
|
||||||
|
std::string pluginUri = request_uri.query("id");
|
||||||
|
auto plugin = model->GetLv2Host().GetPluginInfo(pluginUri);
|
||||||
|
*pName = plugin->name();
|
||||||
|
|
||||||
|
PluginPresets pluginPresets = model->GetPluginPresets(pluginUri);
|
||||||
|
|
||||||
|
std::stringstream s;
|
||||||
|
json_writer writer(s,false);
|
||||||
|
writer.write(pluginPresets);
|
||||||
|
*pContent = s.str();
|
||||||
|
}
|
||||||
|
void GetPreset(const uri &request_uri, std::string *pName, std::string *pContent)
|
||||||
|
{
|
||||||
|
std::string strInstanceId = request_uri.query("id");
|
||||||
|
int64_t instanceId = std::stol(strInstanceId);
|
||||||
|
auto pedalboard = model->GetPreset(instanceId);
|
||||||
|
|
||||||
|
// a certain elegance to using same file format for banks and presets.
|
||||||
|
BankFile file;
|
||||||
|
file.name(pedalboard.name());
|
||||||
|
int64_t newInstanceId = file.addPreset(pedalboard);
|
||||||
|
file.selectedPreset(newInstanceId);
|
||||||
|
|
||||||
|
std::stringstream s;
|
||||||
|
json_writer writer(s,false);
|
||||||
|
writer.write(file);
|
||||||
|
*pContent = s.str();
|
||||||
|
*pName = pedalboard.name();
|
||||||
|
}
|
||||||
|
void GetBank(const uri &request_uri, std::string *pName, std::string *pContent)
|
||||||
|
{
|
||||||
|
std::string strInstanceId = request_uri.query("id");
|
||||||
|
int64_t instanceId = std::stol(strInstanceId);
|
||||||
|
BankFile bank;
|
||||||
|
model->GetBank(instanceId, &bank);
|
||||||
|
|
||||||
|
std::stringstream s;
|
||||||
|
json_writer writer(s, true); // do what we can to reduce the file size.
|
||||||
|
writer.write(bank);
|
||||||
|
*pContent = s.str();
|
||||||
|
*pName = bank.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void head_response(
|
||||||
|
const uri &request_uri,
|
||||||
|
HttpRequest &req,
|
||||||
|
HttpResponse &res,
|
||||||
|
std::error_code &ec) override
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::string segment = request_uri.segment(1);
|
||||||
|
if (segment == "downloadPluginPresets")
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string content;
|
||||||
|
GetPluginPresets(request_uri, &name, &content);
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
res.setContentLength(content.length());
|
||||||
|
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PLUGIN_PRESETS_EXTENSION));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (segment == "downloadPreset")
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string content;
|
||||||
|
GetPreset(request_uri, &name, &content);
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
res.setContentLength(content.length());
|
||||||
|
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (segment == "downloadBank")
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string content;
|
||||||
|
GetBank(request_uri, &name, &content);
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
|
||||||
|
res.setContentLength(content.length());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw PiPedalException("Not found.");
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
if (strcmp(e.what(), "Not found") == 0)
|
||||||
|
{
|
||||||
|
ec = boost::system::errc::make_error_code(boost::system::errc::no_such_file_or_directory);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ec = boost::system::errc::make_error_code(boost::system::errc::invalid_argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void get_response(
|
||||||
|
const uri &request_uri,
|
||||||
|
HttpRequest &req,
|
||||||
|
HttpResponse &res,
|
||||||
|
std::error_code &ec) override
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::string segment = request_uri.segment(1);
|
||||||
|
|
||||||
|
if (segment == "downloadPluginPresets")
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string content;
|
||||||
|
GetPluginPresets(request_uri, &name, &content);
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
res.setContentLength(content.length());
|
||||||
|
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PLUGIN_PRESETS_EXTENSION));
|
||||||
|
res.setBody(content);
|
||||||
|
}
|
||||||
|
else if (segment == "downloadPreset")
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string content;
|
||||||
|
GetPreset(request_uri, &name, &content);
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
res.setContentLength(content.length());
|
||||||
|
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION));
|
||||||
|
res.setBody(content);
|
||||||
|
}
|
||||||
|
else if (segment == "downloadBank")
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string content;
|
||||||
|
GetBank(request_uri, &name, &content);
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
res.setContentLength(content.length());
|
||||||
|
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
|
||||||
|
res.setBody(content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw PiPedalException("Not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
if (strcmp(e.what(), "Not found") == 0)
|
||||||
|
{
|
||||||
|
ec = boost::system::errc::make_error_code(boost::system::errc::no_such_file_or_directory);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ec = boost::system::errc::make_error_code(boost::system::errc::invalid_argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static std::string GetFirstFolderOrFile(const std::vector<std::string>&fileNames)
|
||||||
|
{
|
||||||
|
for (const auto &fileName: fileNames)
|
||||||
|
{
|
||||||
|
size_t nPos = fileName.find('/');
|
||||||
|
if (nPos != std::string::npos) {
|
||||||
|
return fileName.substr(0,nPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fileNames.size() == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return fileNames[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
virtual void post_response(
|
||||||
|
const uri &request_uri,
|
||||||
|
HttpRequest &req,
|
||||||
|
HttpResponse &res,
|
||||||
|
std::error_code &ec) override
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::string segment = request_uri.segment(1);
|
||||||
|
|
||||||
|
if (segment == "uploadPluginPresets")
|
||||||
|
{
|
||||||
|
json_reader reader(req.get_body_input_stream());
|
||||||
|
PluginPresets presets;
|
||||||
|
reader.read(&presets);
|
||||||
|
model->UploadPluginPresets(presets);
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
std::stringstream sResult;
|
||||||
|
sResult << -1;
|
||||||
|
std::string result = sResult.str();
|
||||||
|
res.setContentLength(result.length());
|
||||||
|
|
||||||
|
res.setBody(result);
|
||||||
|
}
|
||||||
|
else if (segment == "uploadPreset")
|
||||||
|
{
|
||||||
|
json_reader reader(req.get_body_input_stream());
|
||||||
|
|
||||||
|
uint64_t uploadAfter = -1;
|
||||||
|
std::string strUploadAfter = request_uri.query("uploadAfter");
|
||||||
|
if (strUploadAfter.length() != 0)
|
||||||
|
{
|
||||||
|
uploadAfter = std::stol(strUploadAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
BankFile bankFile;
|
||||||
|
reader.read(&bankFile);
|
||||||
|
|
||||||
|
uint64_t instanceId = model->UploadPreset(bankFile, uploadAfter);
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
std::stringstream sResult;
|
||||||
|
sResult << instanceId;
|
||||||
|
std::string result = sResult.str();
|
||||||
|
res.setContentLength(result.length());
|
||||||
|
|
||||||
|
res.setBody(result);
|
||||||
|
}
|
||||||
|
else if (segment == "uploadBank")
|
||||||
|
{
|
||||||
|
json_reader reader(req.get_body_input_stream());
|
||||||
|
|
||||||
|
uint64_t uploadAfter = -1;
|
||||||
|
std::string strUploadAfter = request_uri.query("uploadAfter");
|
||||||
|
if (strUploadAfter.length() != 0)
|
||||||
|
{
|
||||||
|
uploadAfter = std::stol(strUploadAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
BankFile bankFile;
|
||||||
|
reader.read(&bankFile);
|
||||||
|
|
||||||
|
uint64_t instanceId = model->UploadBank(bankFile, uploadAfter);
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
std::stringstream sResult;
|
||||||
|
sResult << instanceId;
|
||||||
|
std::string result = sResult.str();
|
||||||
|
res.setContentLength(result.length());
|
||||||
|
|
||||||
|
res.setBody(result);
|
||||||
|
} else if (segment == "uploadUserFile")
|
||||||
|
{
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
const std::string instanceId = request_uri.query("id");
|
||||||
|
const std::string directory = request_uri.query("directory");
|
||||||
|
const std::string filename = request_uri.query("filename");
|
||||||
|
const std::string patchProperty = request_uri.query("property");
|
||||||
|
|
||||||
|
|
||||||
|
if (patchProperty.length() == 0 && directory.length() == 0)
|
||||||
|
{
|
||||||
|
// yyy no throwing!
|
||||||
|
throw PiPedalException("Malformed request.");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
|
||||||
|
std::string outputFileName = std::filesystem::path(directory) / filename;
|
||||||
|
|
||||||
|
if (filename.ends_with(".zip"))
|
||||||
|
{
|
||||||
|
ExtensionChecker extensionChecker { request_uri.query("ext") };
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
try {
|
||||||
|
auto zipFile = ZipFile::Create(req.get_body_temporary_file());
|
||||||
|
std::vector<std::string> files = zipFile->GetFiles();
|
||||||
|
for (const auto&inputFile : files)
|
||||||
|
{
|
||||||
|
if (!inputFile.ends_with("/")) // don't process directory entries.
|
||||||
|
{
|
||||||
|
fs::path inputPath { inputFile};
|
||||||
|
std::string extension = inputPath.extension();
|
||||||
|
if (extensionChecker.IsValidExtension(extension))
|
||||||
|
{
|
||||||
|
auto si = zipFile->GetFileInputStream(inputFile);
|
||||||
|
std::string path = this->model->UploadUserFile(directory,patchProperty,inputFile, si,zipFile->GetFileSize(inputFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set outputPath to the file or folder we would like focus to go to.
|
||||||
|
// almost always a single folder in the root.
|
||||||
|
std::string returnPath = GetFirstFolderOrFile(files);
|
||||||
|
outputFileName = this->model->GetPluginUploadDirectory() / directory / returnPath;
|
||||||
|
} catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
Lv2Log::error(SS("Unzip failed. " << e.what()));
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
outputFileName = this->model->UploadUserFile(directory,patchProperty,filename,req.get_body_input_stream(), req.content_length());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::stringstream ss;
|
||||||
|
json_writer writer(ss);
|
||||||
|
writer.write(outputFileName);
|
||||||
|
std::string response = ss.str();
|
||||||
|
|
||||||
|
res.setContentLength(response.length());
|
||||||
|
res.setBody(response);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw PiPedalException("Not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
if (strcmp(e.what(), "Not found") == 0)
|
||||||
|
{
|
||||||
|
ec = boost::system::errc::make_error_code(boost::system::errc::no_such_file_or_directory);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ec = boost::system::errc::make_error_code(boost::system::errc::invalid_argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* When hosting a react app, replace /var/config.json with
|
||||||
|
data that will connect the react app with our socket server.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class InterceptConfig : public RequestHandler
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
uint64_t maxUploadSize;
|
||||||
|
int portNumber;
|
||||||
|
|
||||||
|
public:
|
||||||
|
InterceptConfig(int portNumber, uint64_t maxUploadSize)
|
||||||
|
: RequestHandler("/var/config.json"),
|
||||||
|
maxUploadSize(maxUploadSize),
|
||||||
|
portNumber(portNumber)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
std::string GetConfig(const std::string &fromAddress)
|
||||||
|
{
|
||||||
|
#define LINK_LOCAL_WEB_SOCKET 1
|
||||||
|
#if LINK_LOCAL_WEB_SOCKET
|
||||||
|
std::string webSocketAddress = GetLinkLocalAddress(fromAddress);
|
||||||
|
Lv2Log::info(SS("Web Socket Address: " << webSocketAddress << ":" << portNumber));
|
||||||
|
#else
|
||||||
|
std::string webSocketAddress = "*";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::stringstream s;
|
||||||
|
|
||||||
|
s << "{ \"socket_server_port\": " << portNumber
|
||||||
|
<< ", \"socket_server_address\": \"" << webSocketAddress <<
|
||||||
|
"\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize << " }";
|
||||||
|
|
||||||
|
return s.str();
|
||||||
|
}
|
||||||
|
virtual ~InterceptConfig() {}
|
||||||
|
|
||||||
|
virtual void head_response(
|
||||||
|
const uri &request_uri,
|
||||||
|
HttpRequest &req,
|
||||||
|
HttpResponse &res,
|
||||||
|
std::error_code &ec) override
|
||||||
|
{
|
||||||
|
// intercepted. See the other overload.
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void head_response(
|
||||||
|
const std::string &fromAddress,
|
||||||
|
const uri &request_uri,
|
||||||
|
HttpRequest &req,
|
||||||
|
HttpResponse &res,
|
||||||
|
std::error_code &ec) override
|
||||||
|
{
|
||||||
|
std::string response = GetConfig(fromAddress);
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
res.setContentLength(response.length());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void get_response(
|
||||||
|
const uri &request_uri,
|
||||||
|
HttpRequest &req,
|
||||||
|
HttpResponse &res,
|
||||||
|
std::error_code &ec) override
|
||||||
|
{
|
||||||
|
// intercepted. see the other overload.
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void get_response(
|
||||||
|
const std::string &fromAddress,
|
||||||
|
const uri &request_uri,
|
||||||
|
HttpRequest &req,
|
||||||
|
HttpResponse &res,
|
||||||
|
std::error_code &ec) override
|
||||||
|
{
|
||||||
|
std::string response = GetConfig(fromAddress);
|
||||||
|
res.set(HttpField::content_type, "application/json");
|
||||||
|
res.set(HttpField::cache_control, "no-cache");
|
||||||
|
res.setContentLength(response.length());
|
||||||
|
res.setBody(response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void pipedal::ConfigureWebServer(
|
||||||
|
WebServer&server,
|
||||||
|
PiPedalModel&model,
|
||||||
|
int port,
|
||||||
|
size_t maxUploadSize)
|
||||||
|
{
|
||||||
|
std::shared_ptr<RequestHandler> interceptConfig{new InterceptConfig(port, maxUploadSize)};
|
||||||
|
server.AddRequestHandler(interceptConfig);
|
||||||
|
|
||||||
|
std::shared_ptr<DownloadIntercept> downloadIntercept = std::make_shared<DownloadIntercept>(&model);
|
||||||
|
server.AddRequestHandler(downloadIntercept);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) 2024 Robin Davies
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace pipedal {
|
||||||
|
class WebServer;
|
||||||
|
class PiPedalModel;
|
||||||
|
void ConfigureWebServer(WebServer&server,PiPedalModel&model,int port,size_t maxUploadSize);
|
||||||
|
}
|
||||||
+95
-11
@@ -23,19 +23,19 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include "ss.hpp"
|
#include "ss.hpp"
|
||||||
#include "Finally.hpp"
|
#include "Finally.hpp"
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
|
||||||
ZipFile::ZipFile()
|
ZipFile::ZipFile()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
ZipFile::~ZipFile()
|
ZipFile::~ZipFile()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ZipFileImpl: public ZipFile {
|
class ZipFileImpl : public ZipFile
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
ZipFileImpl(const std::filesystem::path &path)
|
ZipFileImpl(const std::filesystem::path &path)
|
||||||
: path(path)
|
: path(path)
|
||||||
@@ -50,6 +50,8 @@ public:
|
|||||||
virtual ~ZipFileImpl();
|
virtual ~ZipFileImpl();
|
||||||
virtual std::vector<std::string> GetFiles() override;
|
virtual std::vector<std::string> GetFiles() override;
|
||||||
virtual void ExtractTo(const std::string &zipName, const std::filesystem::path &path) override;
|
virtual void ExtractTo(const std::string &zipName, const std::filesystem::path &path) override;
|
||||||
|
virtual zip_file_input_stream GetFileInputStream(const std::string& filename,size_t bufferSize = 16*1024) override;
|
||||||
|
virtual size_t GetFileSize(const std::string&filename) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::map<std::string, zip_int64_t> nameMap; // avoid o(2) extraction operations.
|
std::map<std::string, zip_int64_t> nameMap; // avoid o(2) extraction operations.
|
||||||
@@ -86,7 +88,8 @@ std::vector<std::string> ZipFileImpl::GetFiles()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ZipFileImpl::ExtractTo(const std::string &zipName, const std::filesystem::path& path) {
|
void ZipFileImpl::ExtractTo(const std::string &zipName, const std::filesystem::path &path)
|
||||||
|
{
|
||||||
auto fi = nameMap.find(zipName);
|
auto fi = nameMap.find(zipName);
|
||||||
|
|
||||||
if (fi == nameMap.end())
|
if (fi == nameMap.end())
|
||||||
@@ -104,7 +107,8 @@ void ZipFileImpl::ExtractTo(const std::string &zipName, const std::filesystem::p
|
|||||||
|
|
||||||
throw std::runtime_error(SS("Failed to read zip content file. " << strError));
|
throw std::runtime_error(SS("Failed to read zip content file. " << strError));
|
||||||
}
|
}
|
||||||
Finally t{[fIn] () mutable{
|
Finally t{[fIn]() mutable
|
||||||
|
{
|
||||||
zip_fclose(fIn);
|
zip_fclose(fIn);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
@@ -116,18 +120,98 @@ void ZipFileImpl::ExtractTo(const std::string &zipName, const std::filesystem::p
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
zip_int64_t nRead = zip_fread(fIn, pBuff, BUFFER_SIZE);
|
zip_int64_t nRead = zip_fread(fIn, pBuff, BUFFER_SIZE);
|
||||||
if (nRead = 0) break;
|
if (nRead = 0)
|
||||||
if (nRead == -1) {
|
break;
|
||||||
|
if (nRead == -1)
|
||||||
|
{
|
||||||
zip_error_t *error = zip_file_get_error(fIn);
|
zip_error_t *error = zip_file_get_error(fIn);
|
||||||
const char *strError = zip_error_strerror(error);
|
const char *strError = zip_error_strerror(error);
|
||||||
throw std::runtime_error(SS("Error reading zip content file." << strError));
|
throw std::runtime_error(SS("Error reading zip content file." << strError));
|
||||||
}
|
}
|
||||||
fo.write(pBuff, (std::streamsize)nRead);
|
fo.write(pBuff, (std::streamsize)nRead);
|
||||||
if (!fo) {
|
if (!fo)
|
||||||
|
{
|
||||||
throw std::runtime_error(SS("Unable to write to " << path));
|
throw std::runtime_error(SS("Unable to write to " << path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_file_input_stream_buf::zip_file_input_stream_buf(zip_file_t *file, size_t buffer_size)
|
||||||
|
: file(file), buffer(buffer_size + putback_size)
|
||||||
|
{
|
||||||
|
char *end = buffer.data() + buffer.size();
|
||||||
|
setg(end, end, end);
|
||||||
|
}
|
||||||
|
zip_file_input_stream_buf::~zip_file_input_stream_buf()
|
||||||
|
{
|
||||||
|
zip_fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::streamsize zip_file_input_stream_buf::xsgetn(char *s, std::streamsize n)
|
||||||
|
{
|
||||||
|
std::streamsize num_copied = 0;
|
||||||
|
while (n > 0)
|
||||||
|
{
|
||||||
|
if (gptr() >= egptr())
|
||||||
|
{
|
||||||
|
if (underflow() == traits_type::eof())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::streamsize chunk = std::min(n, egptr() - gptr());
|
||||||
|
std::memcpy(s, gptr(), chunk);
|
||||||
|
s += chunk;
|
||||||
|
n -= chunk;
|
||||||
|
num_copied += chunk;
|
||||||
|
gbump(chunk);
|
||||||
|
}
|
||||||
|
return num_copied;
|
||||||
|
}
|
||||||
|
zip_file_input_stream_buf::int_type zip_file_input_stream_buf::underflow()
|
||||||
|
{
|
||||||
|
if (gptr() < egptr())
|
||||||
|
{
|
||||||
|
return traits_type::to_int_type(*gptr());
|
||||||
|
}
|
||||||
|
|
||||||
|
char *base = buffer.data();
|
||||||
|
char *start = base;
|
||||||
|
|
||||||
|
if (eback() != base)
|
||||||
|
{
|
||||||
|
std::memmove(base, egptr() - putback_size, putback_size);
|
||||||
|
start += putback_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_int64_t n = zip_fread(file, start, (zip_uint64_t)(buffer.size() - (start - base)));
|
||||||
|
if (n <= 0)
|
||||||
|
{
|
||||||
|
return traits_type::eof();
|
||||||
|
}
|
||||||
|
|
||||||
|
setg(base, start, start + n);
|
||||||
|
return traits_type::to_int_type(*gptr());
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_file_input_stream ZipFileImpl::GetFileInputStream(const std::string& filename, size_t bufferSize)
|
||||||
|
{
|
||||||
|
zip_file_t *f = zip_fopen(zipFile,filename.c_str(),0);
|
||||||
|
if (!f) {
|
||||||
|
throw std::runtime_error(SS("Failed to open zip content file " << filename));
|
||||||
|
}
|
||||||
|
return zip_file_input_stream(f,bufferSize);
|
||||||
|
}
|
||||||
|
size_t ZipFileImpl::GetFileSize(const std::string&filename)
|
||||||
|
{
|
||||||
|
zip_stat_t stat;
|
||||||
|
if (zip_stat(zipFile,filename.c_str(),0,&stat) < 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("File not found.");
|
||||||
|
}
|
||||||
|
if ((stat.valid & ZIP_STAT_SIZE) == 0)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to get file size.");
|
||||||
|
}
|
||||||
|
return stat.size;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,35 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
#include <zip.h>
|
||||||
|
|
||||||
|
|
||||||
namespace pipedal {
|
namespace pipedal {
|
||||||
|
|
||||||
|
class zip_file_input_stream_buf : public std::streambuf {
|
||||||
|
private:
|
||||||
|
zip_file_t* file;
|
||||||
|
std::vector<char> buffer;
|
||||||
|
static const size_t putback_size = 8;
|
||||||
|
public:
|
||||||
|
zip_file_input_stream_buf(zip_file_t* file, size_t buffer_size = 16384);
|
||||||
|
~zip_file_input_stream_buf();
|
||||||
|
protected:
|
||||||
|
virtual int_type underflow() override;
|
||||||
|
virtual std::streamsize xsgetn(char* s, std::streamsize n) override;
|
||||||
|
};
|
||||||
|
class zip_file_input_stream : public std::istream {
|
||||||
|
private:
|
||||||
|
zip_file_input_stream_buf buf;
|
||||||
|
public:
|
||||||
|
zip_file_input_stream(zip_file_t *file, size_t buff_size = 16384)
|
||||||
|
: std::istream(nullptr), buf(file,buff_size)
|
||||||
|
{
|
||||||
|
rdbuf(&buf);
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
class ZipFile {
|
class ZipFile {
|
||||||
protected:
|
protected:
|
||||||
ZipFile();
|
ZipFile();
|
||||||
@@ -36,6 +64,8 @@ namespace pipedal {
|
|||||||
|
|
||||||
virtual std::vector<std::string> GetFiles() = 0;
|
virtual std::vector<std::string> GetFiles() = 0;
|
||||||
virtual void ExtractTo(const std::string &zipName, const std::filesystem::path& path) = 0;
|
virtual void ExtractTo(const std::string &zipName, const std::filesystem::path& path) = 0;
|
||||||
|
virtual zip_file_input_stream GetFileInputStream(const std::string& filename,size_t bufferSize = 16*1024) = 0;
|
||||||
|
virtual size_t GetFileSize(const std::string&filename) = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
+5
-448
@@ -26,6 +26,7 @@
|
|||||||
#include "Lv2Log.hpp"
|
#include "Lv2Log.hpp"
|
||||||
#include "ServiceConfiguration.hpp"
|
#include "ServiceConfiguration.hpp"
|
||||||
#include "AvahiService.hpp"
|
#include "AvahiService.hpp"
|
||||||
|
#include "WebServerConfig.hpp"
|
||||||
|
|
||||||
#include "PiPedalSocket.hpp"
|
#include "PiPedalSocket.hpp"
|
||||||
#include "PluginHost.hpp"
|
#include "PluginHost.hpp"
|
||||||
@@ -38,7 +39,6 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include "HtmlHelper.hpp"
|
#include "HtmlHelper.hpp"
|
||||||
#include "Ipv6Helpers.hpp"
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
@@ -47,11 +47,10 @@
|
|||||||
|
|
||||||
#include <systemd/sd-daemon.h>
|
#include <systemd/sd-daemon.h>
|
||||||
|
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
|
||||||
#define PRESET_EXTENSION ".piPreset"
|
|
||||||
#define BANK_EXTENSION ".piBank"
|
|
||||||
#define PLUGIN_PRESETS_EXTENSION ".piPluginPresets"
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __ARM_ARCH_ISA_A64
|
#ifdef __ARM_ARCH_ISA_A64
|
||||||
@@ -94,443 +93,6 @@ void throwSystemError(int error)
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
using namespace boost::system;
|
|
||||||
|
|
||||||
class DownloadIntercept : public RequestHandler
|
|
||||||
{
|
|
||||||
PiPedalModel *model;
|
|
||||||
|
|
||||||
public:
|
|
||||||
DownloadIntercept(PiPedalModel *model)
|
|
||||||
: RequestHandler("/var"),
|
|
||||||
model(model)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool wants(const std::string &method, const uri &request_uri) const
|
|
||||||
{
|
|
||||||
if (request_uri.segment_count() != 2 || request_uri.segment(0) != "var")
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::string segment = request_uri.segment(1);
|
|
||||||
if (segment == "uploadPluginPresets")
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (segment == "downloadPluginPresets")
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (segment == "downloadPreset")
|
|
||||||
{
|
|
||||||
std::string strInstanceId = request_uri.query("id");
|
|
||||||
if (strInstanceId != "")
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (segment == "uploadPreset")
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (segment == "downloadBank")
|
|
||||||
{
|
|
||||||
std::string strInstanceId = request_uri.query("id");
|
|
||||||
if (strInstanceId != "")
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (segment == "uploadBank")
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
} else if (segment == "uploadUserFile")
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GetContentDispositionHeader(const std::string &name, const std::string &extension)
|
|
||||||
{
|
|
||||||
std::string fileName = name.substr(0, 64) + extension;
|
|
||||||
std::stringstream s;
|
|
||||||
|
|
||||||
s << "attachment; filename*=" << HtmlHelper::Rfc5987EncodeFileName(fileName) << "; filename=\"" << HtmlHelper::SafeFileName(fileName) << "\"";
|
|
||||||
std::string result = s.str();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GetPluginPresets(const uri &request_uri, std::string *pName, std::string *pContent)
|
|
||||||
{
|
|
||||||
std::string pluginUri = request_uri.query("id");
|
|
||||||
auto plugin = model->GetLv2Host().GetPluginInfo(pluginUri);
|
|
||||||
*pName = plugin->name();
|
|
||||||
|
|
||||||
PluginPresets pluginPresets = model->GetPluginPresets(pluginUri);
|
|
||||||
|
|
||||||
std::stringstream s;
|
|
||||||
json_writer writer(s,false);
|
|
||||||
writer.write(pluginPresets);
|
|
||||||
*pContent = s.str();
|
|
||||||
}
|
|
||||||
void GetPreset(const uri &request_uri, std::string *pName, std::string *pContent)
|
|
||||||
{
|
|
||||||
std::string strInstanceId = request_uri.query("id");
|
|
||||||
int64_t instanceId = std::stol(strInstanceId);
|
|
||||||
auto pedalboard = model->GetPreset(instanceId);
|
|
||||||
|
|
||||||
// a certain elegance to using same file format for banks and presets.
|
|
||||||
BankFile file;
|
|
||||||
file.name(pedalboard.name());
|
|
||||||
int64_t newInstanceId = file.addPreset(pedalboard);
|
|
||||||
file.selectedPreset(newInstanceId);
|
|
||||||
|
|
||||||
std::stringstream s;
|
|
||||||
json_writer writer(s,false);
|
|
||||||
writer.write(file);
|
|
||||||
*pContent = s.str();
|
|
||||||
*pName = pedalboard.name();
|
|
||||||
}
|
|
||||||
void GetBank(const uri &request_uri, std::string *pName, std::string *pContent)
|
|
||||||
{
|
|
||||||
std::string strInstanceId = request_uri.query("id");
|
|
||||||
int64_t instanceId = std::stol(strInstanceId);
|
|
||||||
BankFile bank;
|
|
||||||
model->GetBank(instanceId, &bank);
|
|
||||||
|
|
||||||
std::stringstream s;
|
|
||||||
json_writer writer(s, true); // do what we can to reduce the file size.
|
|
||||||
writer.write(bank);
|
|
||||||
*pContent = s.str();
|
|
||||||
*pName = bank.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void head_response(
|
|
||||||
const uri &request_uri,
|
|
||||||
HttpRequest &req,
|
|
||||||
HttpResponse &res,
|
|
||||||
std::error_code &ec) override
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
std::string segment = request_uri.segment(1);
|
|
||||||
if (segment == "downloadPluginPresets")
|
|
||||||
{
|
|
||||||
std::string name;
|
|
||||||
std::string content;
|
|
||||||
GetPluginPresets(request_uri, &name, &content);
|
|
||||||
res.set(HttpField::content_type, "application/octet-stream");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
res.setContentLength(content.length());
|
|
||||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PLUGIN_PRESETS_EXTENSION));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (segment == "downloadPreset")
|
|
||||||
{
|
|
||||||
std::string name;
|
|
||||||
std::string content;
|
|
||||||
GetPreset(request_uri, &name, &content);
|
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/octet-stream");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
res.setContentLength(content.length());
|
|
||||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (segment == "downloadBank")
|
|
||||||
{
|
|
||||||
std::string name;
|
|
||||||
std::string content;
|
|
||||||
GetBank(request_uri, &name, &content);
|
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/octet-stream");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
|
|
||||||
res.setContentLength(content.length());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw PiPedalException("Not found.");
|
|
||||||
}
|
|
||||||
catch (const std::exception &e)
|
|
||||||
{
|
|
||||||
if (strcmp(e.what(), "Not found") == 0)
|
|
||||||
{
|
|
||||||
ec = boost::system::errc::make_error_code(boost::system::errc::no_such_file_or_directory);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ec = boost::system::errc::make_error_code(boost::system::errc::invalid_argument);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void get_response(
|
|
||||||
const uri &request_uri,
|
|
||||||
HttpRequest &req,
|
|
||||||
HttpResponse &res,
|
|
||||||
std::error_code &ec) override
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
std::string segment = request_uri.segment(1);
|
|
||||||
|
|
||||||
if (segment == "downloadPluginPresets")
|
|
||||||
{
|
|
||||||
std::string name;
|
|
||||||
std::string content;
|
|
||||||
GetPluginPresets(request_uri, &name, &content);
|
|
||||||
res.set(HttpField::content_type, "application/octet-stream");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
res.setContentLength(content.length());
|
|
||||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PLUGIN_PRESETS_EXTENSION));
|
|
||||||
res.setBody(content);
|
|
||||||
}
|
|
||||||
else if (segment == "downloadPreset")
|
|
||||||
{
|
|
||||||
std::string name;
|
|
||||||
std::string content;
|
|
||||||
GetPreset(request_uri, &name, &content);
|
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/octet-stream");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
res.setContentLength(content.length());
|
|
||||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION));
|
|
||||||
res.setBody(content);
|
|
||||||
}
|
|
||||||
else if (segment == "downloadBank")
|
|
||||||
{
|
|
||||||
std::string name;
|
|
||||||
std::string content;
|
|
||||||
GetBank(request_uri, &name, &content);
|
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/octet-stream");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
res.setContentLength(content.length());
|
|
||||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
|
|
||||||
res.setBody(content);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw PiPedalException("Not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (const std::exception &e)
|
|
||||||
{
|
|
||||||
if (strcmp(e.what(), "Not found") == 0)
|
|
||||||
{
|
|
||||||
ec = boost::system::errc::make_error_code(boost::system::errc::no_such_file_or_directory);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ec = boost::system::errc::make_error_code(boost::system::errc::invalid_argument);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
virtual void post_response(
|
|
||||||
const uri &request_uri,
|
|
||||||
HttpRequest &req,
|
|
||||||
HttpResponse &res,
|
|
||||||
std::error_code &ec) override
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
std::string segment = request_uri.segment(1);
|
|
||||||
|
|
||||||
if (segment == "uploadPluginPresets")
|
|
||||||
{
|
|
||||||
json_reader reader(req.get_body_input_stream());
|
|
||||||
PluginPresets presets;
|
|
||||||
reader.read(&presets);
|
|
||||||
model->UploadPluginPresets(presets);
|
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
std::stringstream sResult;
|
|
||||||
sResult << -1;
|
|
||||||
std::string result = sResult.str();
|
|
||||||
res.setContentLength(result.length());
|
|
||||||
|
|
||||||
res.setBody(result);
|
|
||||||
}
|
|
||||||
else if (segment == "uploadPreset")
|
|
||||||
{
|
|
||||||
json_reader reader(req.get_body_input_stream());
|
|
||||||
|
|
||||||
uint64_t uploadAfter = -1;
|
|
||||||
std::string strUploadAfter = request_uri.query("uploadAfter");
|
|
||||||
if (strUploadAfter.length() != 0)
|
|
||||||
{
|
|
||||||
uploadAfter = std::stol(strUploadAfter);
|
|
||||||
}
|
|
||||||
|
|
||||||
BankFile bankFile;
|
|
||||||
reader.read(&bankFile);
|
|
||||||
|
|
||||||
uint64_t instanceId = model->UploadPreset(bankFile, uploadAfter);
|
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
std::stringstream sResult;
|
|
||||||
sResult << instanceId;
|
|
||||||
std::string result = sResult.str();
|
|
||||||
res.setContentLength(result.length());
|
|
||||||
|
|
||||||
res.setBody(result);
|
|
||||||
}
|
|
||||||
else if (segment == "uploadBank")
|
|
||||||
{
|
|
||||||
json_reader reader(req.get_body_input_stream());
|
|
||||||
|
|
||||||
uint64_t uploadAfter = -1;
|
|
||||||
std::string strUploadAfter = request_uri.query("uploadAfter");
|
|
||||||
if (strUploadAfter.length() != 0)
|
|
||||||
{
|
|
||||||
uploadAfter = std::stol(strUploadAfter);
|
|
||||||
}
|
|
||||||
|
|
||||||
BankFile bankFile;
|
|
||||||
reader.read(&bankFile);
|
|
||||||
|
|
||||||
uint64_t instanceId = model->UploadBank(bankFile, uploadAfter);
|
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
std::stringstream sResult;
|
|
||||||
sResult << instanceId;
|
|
||||||
std::string result = sResult.str();
|
|
||||||
res.setContentLength(result.length());
|
|
||||||
|
|
||||||
res.setBody(result);
|
|
||||||
} else if (segment == "uploadUserFile")
|
|
||||||
{
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
|
|
||||||
const std::string &directory = request_uri.query("directory");
|
|
||||||
const std::string &filename = request_uri.query("filename");
|
|
||||||
const std::string &patchProperty = request_uri.query("property");
|
|
||||||
|
|
||||||
|
|
||||||
if (patchProperty.length() == 0 && directory.length() == 0)
|
|
||||||
{
|
|
||||||
throw PiPedalException("Malformed request.");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
|
|
||||||
std::string outputFileName = std::filesystem::path(directory) / filename;
|
|
||||||
|
|
||||||
std::string path = this->model->UploadUserFile(directory,patchProperty,filename,req.get_body_input_stream(), req.content_length());
|
|
||||||
|
|
||||||
std::stringstream ss;
|
|
||||||
json_writer writer(ss);
|
|
||||||
writer.write(outputFileName);
|
|
||||||
std::string response = ss.str();
|
|
||||||
|
|
||||||
res.setContentLength(response.length());
|
|
||||||
res.setBody(response);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw PiPedalException("Not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (const std::exception &e)
|
|
||||||
{
|
|
||||||
if (strcmp(e.what(), "Not found") == 0)
|
|
||||||
{
|
|
||||||
ec = boost::system::errc::make_error_code(boost::system::errc::no_such_file_or_directory);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ec = boost::system::errc::make_error_code(boost::system::errc::invalid_argument);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* When hosting a react app, replace /var/config.json with
|
|
||||||
data that will connect the react app with our socket server.
|
|
||||||
*/
|
|
||||||
|
|
||||||
class InterceptConfig : public RequestHandler
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
uint64_t maxUploadSize;
|
|
||||||
int portNumber;
|
|
||||||
|
|
||||||
public:
|
|
||||||
InterceptConfig(int portNumber, uint64_t maxUploadSize)
|
|
||||||
: RequestHandler("/var/config.json"),
|
|
||||||
maxUploadSize(maxUploadSize),
|
|
||||||
portNumber(portNumber)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
std::string GetConfig(const std::string &fromAddress)
|
|
||||||
{
|
|
||||||
#define LINK_LOCAL_WEB_SOCKET 1
|
|
||||||
#if LINK_LOCAL_WEB_SOCKET
|
|
||||||
std::string webSocketAddress = GetLinkLocalAddress(fromAddress);
|
|
||||||
Lv2Log::info(SS("Web Socket Address: " << webSocketAddress << ":" << portNumber));
|
|
||||||
#else
|
|
||||||
std::string webSocketAddress = "*";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::stringstream s;
|
|
||||||
|
|
||||||
s << "{ \"socket_server_port\": " << portNumber
|
|
||||||
<< ", \"socket_server_address\": \"" << webSocketAddress << "\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize << " }";
|
|
||||||
|
|
||||||
return s.str();
|
|
||||||
}
|
|
||||||
virtual ~InterceptConfig() {}
|
|
||||||
|
|
||||||
virtual void head_response(
|
|
||||||
const uri &request_uri,
|
|
||||||
HttpRequest &req,
|
|
||||||
HttpResponse &res,
|
|
||||||
std::error_code &ec) override
|
|
||||||
{
|
|
||||||
// intercepted. See the other overload.
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void head_response(
|
|
||||||
const std::string &fromAddress,
|
|
||||||
const uri &request_uri,
|
|
||||||
HttpRequest &req,
|
|
||||||
HttpResponse &res,
|
|
||||||
std::error_code &ec) override
|
|
||||||
{
|
|
||||||
std::string response = GetConfig(fromAddress);
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
res.setContentLength(response.length());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void get_response(
|
|
||||||
const uri &request_uri,
|
|
||||||
HttpRequest &req,
|
|
||||||
HttpResponse &res,
|
|
||||||
std::error_code &ec) override
|
|
||||||
{
|
|
||||||
// intercepted. see the other overload.
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void get_response(
|
|
||||||
const std::string &fromAddress,
|
|
||||||
const uri &request_uri,
|
|
||||||
HttpRequest &req,
|
|
||||||
HttpResponse &res,
|
|
||||||
std::error_code &ec) override
|
|
||||||
{
|
|
||||||
std::string response = GetConfig(fromAddress);
|
|
||||||
res.set(HttpField::content_type, "application/json");
|
|
||||||
res.set(HttpField::cache_control, "no-cache");
|
|
||||||
res.setContentLength(response.length());
|
|
||||||
res.setBody(response);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool isJackServiceRunning()
|
static bool isJackServiceRunning()
|
||||||
{
|
{
|
||||||
@@ -652,7 +214,7 @@ int main(int argc, char *argv[])
|
|||||||
auto const threads = std::max<int>(1, configuration.GetThreads());
|
auto const threads = std::max<int>(1, configuration.GetThreads());
|
||||||
|
|
||||||
server = WebServer::create(
|
server = WebServer::create(
|
||||||
address, port, web_root.c_str(), threads);
|
address, port, web_root.c_str(), threads,configuration.GetMaxUploadSize());
|
||||||
|
|
||||||
Lv2Log::info("Document root: %s Threads: %d", doc_root.c_str(), (int)threads);
|
Lv2Log::info("Document root: %s Threads: %d", doc_root.c_str(), (int)threads);
|
||||||
|
|
||||||
@@ -787,12 +349,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
server->AddSocketFactory(pipedalSocketFactory);
|
server->AddSocketFactory(pipedalSocketFactory);
|
||||||
|
|
||||||
std::shared_ptr<RequestHandler> interceptConfig{new InterceptConfig(port, configuration.GetMaxUploadSize())};
|
ConfigureWebServer(*server,model,port,configuration.GetMaxUploadSize());
|
||||||
server->AddRequestHandler(interceptConfig);
|
|
||||||
|
|
||||||
std::shared_ptr<DownloadIntercept> downloadIntercept = std::make_shared<DownloadIntercept>(&model);
|
|
||||||
server->AddRequestHandler(downloadIntercept);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
server->RunInBackground(-1);
|
server->RunInBackground(-1);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
- make sure lv2-dev shows up in packaging. Make sure dhcpcd shows up in packaging
|
- Zip file upload in a subdirectory.
|
||||||
|
- Turning off wifi-direct sould re-enable NetworkManager.
|
||||||
|
- ToobML save/restore state.
|
||||||
|
|
||||||
|
|
||||||
- make app use the same theme settings as the website.
|
- make app use the same theme settings as the website.
|
||||||
@@ -6,9 +8,7 @@
|
|||||||
- Shutdown/reboot midi bindings
|
- Shutdown/reboot midi bindings
|
||||||
- versioning.
|
- versioning.
|
||||||
- do we want to take a second crack at establishin multi-p2p connections with the driver_param?
|
- do we want to take a second crack at establishin multi-p2p connections with the driver_param?
|
||||||
- property subscriptions don't get refreshed after a disconnect.
|
|
||||||
|
|
||||||
sudo apt install libsdbus-c++-dev libnm-dev
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -16,4 +16,6 @@ sudo apt install libsdbus-c++-dev libnm-dev
|
|||||||
|
|
||||||
Pri Description
|
Pri Description
|
||||||
-----------------
|
-----------------
|
||||||
|
9 Upload proteus patches.
|
||||||
|
8 Migrate to Vite toolchain.
|
||||||
5 Re-use plugin instances when rebuilding pedalboard.
|
5 Re-use plugin instances when rebuilding pedalboard.
|
||||||
|
|||||||
Reference in New Issue
Block a user