TooB ML support for large model. .zip file uploads.

This commit is contained in:
Robin Davies
2024-08-17 23:09:04 -04:00
parent 7bd4479bda
commit 1f95908d34
22 changed files with 845 additions and 530 deletions
+2 -2
View File
@@ -14,14 +14,14 @@
"socketServerAddress": "0.0.0.0:80",
/* Number of threads to use for servicing websockets */
"threads" : 10,
"threads" : 5,
"logHttpRequests": false,
/* { None=0,Error =1,Warning =2,Info = 3, Debug=4} */
"logLevel": 3,
/* 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.
(not implemented)
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -117,7 +117,7 @@ and uses model files from the GuitarML Neural Pi project (https://github.com/Gui
mod:brand "TooB";
mod:label "TooB ML";
lv2:requiredFeature urid:map, work:schedule ;
lv2:optionalFeature lv2:hardRTCapable;
lv2:optionalFeature state::freePath, state:makePath, state:mapPath ;
lv2:extensionData state:interface, work:interface;
patch:readable
+1 -1
View File
@@ -2,7 +2,7 @@
"socket_server_port": 8080,
"socket_server_address": "*",
"debug": true,
"max_upload_size": 1048576,
"max_upload_size": 536870912,
"fakeAndroid": false,
"ui_plugins": []
}
+18 -2
View File
@@ -428,6 +428,18 @@ export default withStyles(styles, { withTheme: true })(
hasSelectedFileOrFolder(): boolean {
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() {
let classes = this.props.classes;
let columnWidth = this.state.columnWidth;
@@ -573,9 +585,13 @@ export default withStyles(styles, { withTheme: true })(
this.setState({ openUploadFileDialog: false });
}
}
uploadPage={"uploadUserFile?directory=" + encodeURIComponent(
uploadPage={
"uploadUserFile?directory=" + encodeURIComponent(
pathConcat(this.props.fileProperty.directory,this.state.navDirectory)
)}
)
+ "&ext="
+ encodeURIComponent(this.getFileExtensionList(this.props.fileProperty))
}
onUploaded={() => { this.requestFiles(this.state.navDirectory); }}
fileProperty={this.props.fileProperty}
+2 -1
View File
@@ -787,7 +787,7 @@ export class PiPedalModel //implements PiPedalModel
this.androidHost = new FakeAndroidHost();
}
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 === "*") {
socket_server_address = window.location.hostname;
}
@@ -798,6 +798,7 @@ export class PiPedalModel //implements PiPedalModel
this.socketServerUrl = socket_server;
this.varServerUrl = var_server_url;
this.maxFileUploadSize = parseInt(max_upload_size);
this.webSocket = new PiPedalSocket(
this.socketServerUrl,
+5 -1
View File
@@ -208,7 +208,11 @@ export default class UploadFileDialog extends ResizeResponsiveComponent<UploadFi
{
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);
upload.status = FileUploadStatus.Uploaded;
upload.statusMessage = "Uploaded.";
+1
View File
@@ -132,6 +132,7 @@ else()
endif()
set (PIPEDAL_SOURCES
WebServerConfig.cpp WebServerConfig.hpp
Finally.hpp
ZipFile.cpp ZipFile.hpp
TemporaryFile.cpp TemporaryFile.hpp
+4
View File
@@ -2135,3 +2135,7 @@ void PiPedalModel::OnNotifyLv2RealtimeError(int64_t instanceId, const std::strin
}
delete[] t;
}
std::filesystem::path PiPedalModel::GetPluginUploadDirectory() const
{
return storage.GetPluginUploadDirectory();
}
+1
View File
@@ -196,6 +196,7 @@ namespace pipedal
virtual ~PiPedalModel();
uint16_t GetWebPort() const { return webPort; }
std::filesystem::path GetPluginUploadDirectory() const;
void Close();
void SetOnboarding(bool value);
+2 -1
View File
@@ -1441,7 +1441,8 @@ void PluginHost::CheckForResourceInitialization(const std::string &pluginUri,con
if (plugin)
{
std::filesystem::path bundlePath = plugin->bundle_path();
if (!plugin->piPedalUI())
return;
const auto& fileProperties = plugin->piPedalUI()->fileProperties();
if (fileProperties.size() != 0 && !pluginsThatHaveBeenCheckedForResources.contains(pluginUri))
{
+2 -6
View File
@@ -1680,18 +1680,14 @@ std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, co
{
if (!ensureNoDotDot(directory))
{
throw std::logic_error("Permission denied.");
throw std::logic_error(SS("Invalide filename: " << 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;
if (!this->IsValidSampleFileName(result))
{
throw std::logic_error("Permission denied.");
throw std::logic_error(SS("Invalid upload path: " << result));
}
return result;
}
+1 -1
View File
@@ -114,7 +114,7 @@ public:
std::filesystem::path GetPluginUploadDirectory() const;
std::vector<std::string> GetPedalboards();
//std::vector<std::string> GetPedalboards();
const BankIndex & GetBanks() const { return bankIndex; }
+47 -28
View File
@@ -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>
const size_t MAX_READ_SIZE = 512 * 1024 * 1024;;
const size_t MAX_READ_SIZE = 512 * 1024 * 1024;
;
using namespace boost;
class request_with_file_upload : public websocketpp::http::parser::parser
@@ -77,10 +78,10 @@ public:
{
return m_ready;
}
std::istream&get_body_input_stream();
std::istream &get_body_input_stream();
const std::filesystem::path& get_body_input_file();
size_t content_length() const { return m_content_length;}
const std::filesystem::path &get_body_input_file();
size_t content_length() const { return m_content_length; }
/// Returns the full raw request (including the body)
std::string raw() const;
@@ -148,33 +149,39 @@ public:
std::stringstream m_stringInputStream;
bool m_outputOpen = false;
};
const std::filesystem::path& request_with_file_upload::get_body_input_file()
const std::filesystem::path &request_with_file_upload::get_body_input_file()
{
if (!this->m_temporaryFile)
throw std::runtime_error("Request does not have a body.");
return this->m_temporaryFile->Path();
}
std::istream&request_with_file_upload::get_body_input_stream()
std::istream &request_with_file_upload::get_body_input_stream()
{
if (!m_outputOpen)
{
m_outputOpen = true;
if (m_uploading_to_file)
{
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;
}else {
}
else
{
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.seekg(0);
return m_stringInputStream;
}
} else {
}
else
{
if (m_uploading_to_file)
{
return m_inputStream;
} else {
}
else
{
return m_stringInputStream;
}
}
@@ -193,7 +200,7 @@ bool request_with_file_upload::prepare_body(std::error_code &ec)
try
{
this->m_temporaryFile = std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
m_outputStream.open(this->m_temporaryFile->Path(),std::ios_base::trunc | std::ios_base::out | std::ios_base::binary);
m_outputStream.open(this->m_temporaryFile->Path(), std::ios_base::trunc | std::ios_base::out | std::ios_base::binary);
if (!m_outputStream)
{
throw std::runtime_error(SS("Unable to open file " << this->m_temporaryFile->Path()));
@@ -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)
{
size_t processed = (std::min)(m_body_bytes_needed, len);
try {
m_outputStream.write(buf,processed);
} catch (const std::exception &e)
try
{
m_outputStream.write(buf, processed);
}
catch (const std::exception &e)
{
Lv2Log::error(SS("Can't write to web temporary file " << m_temporaryFile->Path() << ". " << e.what()));
ec = error::make_error_code(error::istream_bad);
@@ -507,7 +516,7 @@ public:
typedef CustomPpConfig type;
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_alog alog_type;
@@ -528,6 +537,8 @@ public:
transport_type;
};
size_t CustomPpConfig::max_http_body_size = MAX_READ_SIZE;
std::string
pipedal::last_modified(const std::filesystem::path &path)
{
@@ -637,6 +648,7 @@ namespace pipedal
int port = -1;
std::filesystem::path rootPath;
int threads = 1;
size_t maxUploadSize = 512 * 1024 * 1024;
std::thread *pBgThread = nullptr;
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 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();
}
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 &get(const std::string &key) const { return m_request.get_header(key); }
virtual bool keepAlive() const
@@ -1262,13 +1274,7 @@ namespace pipedal
this->pBgThread = new std::thread(ThreadProc, this);
}
WebServerImpl(const std::string &address, int port, const char *rootPath, int threads)
: address(address),
rootPath(rootPath),
port(port),
threads(threads)
{
}
WebServerImpl(const std::string &address, int port, const char *rootPath, int threads, size_t maxUploadSize);
};
} // namespace pipedal
@@ -1284,8 +1290,21 @@ std::shared_ptr<ISocketFactory> WebServerImpl::GetSocketFactory(const uri &reque
}
return nullptr;
}
std::shared_ptr<WebServer> pipedal::WebServer::create(const boost::asio::ip::address &address, int port, const char *rootPath, int threads)
WebServerImpl::WebServerImpl(const std::string &address, int port, const char *rootPath, int threads, size_t maxUploadSize)
: 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
View File
@@ -236,7 +236,8 @@ public:
const boost::asio::ip::address &address,
int port,
const char *rootPath,
int threads);
int threads,
size_t maxUploadsize);
};
+571
View File
@@ -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);
}
+27
View File
@@ -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);
}
+110 -26
View File
@@ -23,25 +23,25 @@
#include <map>
#include "ss.hpp"
#include "Finally.hpp"
#include <cstring>
using namespace pipedal;
ZipFile::ZipFile()
{
}
ZipFile::~ZipFile()
{
}
class ZipFileImpl: public ZipFile {
class ZipFileImpl : public ZipFile
{
public:
ZipFileImpl(const std::filesystem::path&path)
:path(path)
ZipFileImpl(const std::filesystem::path &path)
: path(path)
{
int errorOp = 0;
zipFile = zip_open(path.c_str(),ZIP_RDONLY,&errorOp);
zipFile = zip_open(path.c_str(), ZIP_RDONLY, &errorOp);
if (zipFile == nullptr)
{
throw std::runtime_error("Can't open zip file.");
@@ -49,12 +49,14 @@ public:
}
virtual ~ZipFileImpl();
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:
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.
const std::filesystem::path path;
zip_t*zipFile = nullptr;
zip_t *zipFile = nullptr;
};
ZipFile::ptr ZipFile::Create(const std::filesystem::path &path)
@@ -73,10 +75,10 @@ ZipFileImpl::~ZipFileImpl()
std::vector<std::string> ZipFileImpl::GetFiles()
{
std::vector<std::string> result;
zip_int64_t nEntries = zip_get_num_entries(zipFile,0);
zip_int64_t nEntries = zip_get_num_entries(zipFile, 0);
for (zip_int64_t i = 0; i < nEntries; ++i)
{
const char*name = zip_get_name(zipFile,i,ZIP_FL_ENC_STRICT);
const char *name = zip_get_name(zipFile, i, ZIP_FL_ENC_STRICT);
if (name)
{
result.push_back(name);
@@ -86,7 +88,8 @@ std::vector<std::string> ZipFileImpl::GetFiles()
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);
if (fi == nameMap.end())
@@ -96,7 +99,7 @@ void ZipFileImpl::ExtractTo(const std::string &zipName, const std::filesystem::p
}
zip_int64_t fileIndex = fi->second;
zip_file_t*fIn = zip_fopen_index(this->zipFile,fileIndex,0);
zip_file_t *fIn = zip_fopen_index(this->zipFile, fileIndex, 0);
if (fIn == nullptr)
{
zip_error_t *error = zip_get_error(this->zipFile);
@@ -104,30 +107,111 @@ void ZipFileImpl::ExtractTo(const std::string &zipName, const std::filesystem::p
throw std::runtime_error(SS("Failed to read zip content file. " << strError));
}
Finally t{[fIn] () mutable{
Finally t{[fIn]() mutable
{
zip_fclose(fIn);
}};
std::ofstream fo {path,std::ios_base::out |std::ios_base::trunc | std::ios_base::binary};
std::ofstream fo{path, std::ios_base::out | std::ios_base::trunc | std::ios_base::binary};
constexpr int BUFFER_SIZE = 64*1024;
constexpr int BUFFER_SIZE = 64 * 1024;
std::vector<char> vBuff(BUFFER_SIZE);
char*pBuff = (char*)&(vBuff[0]);
char *pBuff = (char *)&(vBuff[0]);
while (true)
{
zip_int64_t nRead = zip_fread(fIn,pBuff, BUFFER_SIZE);
if (nRead = 0) break;
if (nRead == -1) {
zip_error_t*error = zip_file_get_error(fIn);
zip_int64_t nRead = zip_fread(fIn, pBuff, BUFFER_SIZE);
if (nRead = 0)
break;
if (nRead == -1)
{
zip_error_t *error = zip_file_get_error(fIn);
const char *strError = zip_error_strerror(error);
throw std::runtime_error(SS("Error reading zip content file." << strError));
}
fo.write(pBuff,(std::streamsize)nRead);
if (!fo) {
fo.write(pBuff, (std::streamsize)nRead);
if (!fo)
{
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;
}
+30
View File
@@ -22,7 +22,35 @@
#include <string>
#include <vector>
#include <filesystem>
#include <iostream>
#include <zip.h>
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 {
protected:
ZipFile();
@@ -36,6 +64,8 @@ namespace pipedal {
virtual std::vector<std::string> GetFiles() = 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
View File
@@ -26,6 +26,7 @@
#include "Lv2Log.hpp"
#include "ServiceConfiguration.hpp"
#include "AvahiService.hpp"
#include "WebServerConfig.hpp"
#include "PiPedalSocket.hpp"
#include "PluginHost.hpp"
@@ -38,7 +39,6 @@
#include <sys/stat.h>
#include <boost/asio.hpp>
#include "HtmlHelper.hpp"
#include "Ipv6Helpers.hpp"
#include <thread>
#include <atomic>
@@ -47,11 +47,10 @@
#include <systemd/sd-daemon.h>
using namespace pipedal;
#define PRESET_EXTENSION ".piPreset"
#define BANK_EXTENSION ".piBank"
#define PLUGIN_PRESETS_EXTENSION ".piPluginPresets"
#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()
{
@@ -652,7 +214,7 @@ int main(int argc, char *argv[])
auto const threads = std::max<int>(1, configuration.GetThreads());
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);
@@ -787,12 +349,7 @@ int main(int argc, char *argv[])
server->AddSocketFactory(pipedalSocketFactory);
std::shared_ptr<RequestHandler> interceptConfig{new InterceptConfig(port, configuration.GetMaxUploadSize())};
server->AddRequestHandler(interceptConfig);
std::shared_ptr<DownloadIntercept> downloadIntercept = std::make_shared<DownloadIntercept>(&model);
server->AddRequestHandler(downloadIntercept);
ConfigureWebServer(*server,model,port,configuration.GetMaxUploadSize());
{
server->RunInBackground(-1);
+5 -3
View File
@@ -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.
@@ -6,9 +8,7 @@
- Shutdown/reboot midi bindings
- versioning.
- 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
-----------------
9 Upload proteus patches.
8 Migrate to Vite toolchain.
5 Re-use plugin instances when rebuilding pedalboard.