diff --git a/config/config.json b/config/config.json index 78dc0ec..4541f52 100644 --- a/config/config.json +++ b/config/config.json @@ -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) diff --git a/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.1.1.36 b/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.1.1.36 index 16de161..1a3bc6c 100644 Binary files a/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.1.1.36 and b/lv2/aarch64/ToobAmp.lv2/ToobAmp.so.1.1.36 differ diff --git a/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so b/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so index 46e3951..352958a 100644 Binary files a/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so and b/lv2/aarch64/ToobAmp.lv2/ToobAmpUI.so differ diff --git a/lv2/aarch64/ToobAmp.lv2/ToobML.ttl b/lv2/aarch64/ToobAmp.lv2/ToobML.ttl index f9b32c9..f2c77ba 100644 --- a/lv2/aarch64/ToobAmp.lv2/ToobML.ttl +++ b/lv2/aarch64/ToobAmp.lv2/ToobML.ttl @@ -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 diff --git a/react/public/var/config.json b/react/public/var/config.json index bf8d4ce..f808ca9 100644 --- a/react/public/var/config.json +++ b/react/public/var/config.json @@ -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": [] } \ No newline at end of file diff --git a/react/src/FilePropertyDialog.tsx b/react/src/FilePropertyDialog.tsx index 2be6638..edfb263 100644 --- a/react/src/FilePropertyDialog.tsx +++ b/react/src/FilePropertyDialog.tsx @@ -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( - pathConcat(this.props.fileProperty.directory,this.state.navDirectory) - )} + 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} diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index 4e08c5a..e3dd361 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -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, diff --git a/react/src/UploadFileDialog.tsx b/react/src/UploadFileDialog.tsx index 4e1cd31..368432d 100644 --- a/react/src/UploadFileDialog.tsx +++ b/react/src/UploadFileDialog.tsx @@ -208,7 +208,11 @@ export default class UploadFileDialog extends ResizeResponsiveComponentbundle_path(); - + if (!plugin->piPedalUI()) + return; const auto& fileProperties = plugin->piPedalUI()->fileProperties(); if (fileProperties.size() != 0 && !pluginsThatHaveBeenCheckedForResources.contains(pluginUri)) { diff --git a/src/Storage.cpp b/src/Storage.cpp index d57052d..24079a6 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -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; } diff --git a/src/Storage.hpp b/src/Storage.hpp index 9e7df86..deaea9c 100644 --- a/src/Storage.hpp +++ b/src/Storage.hpp @@ -114,7 +114,7 @@ public: std::filesystem::path GetPluginUploadDirectory() const; - std::vector GetPedalboards(); + //std::vector GetPedalboards(); const BankIndex & GetBanks() const { return bankIndex; } diff --git a/src/WebServer.cpp b/src/WebServer.cpp index d7b6548..781d726 100644 --- a/src/WebServer.cpp +++ b/src/WebServer.cpp @@ -56,7 +56,8 @@ const std::filesystem::path WEB_TEMP_DIR{"/var/pipedal/web_temp"}; using tcp = boost::asio::ip::tcp; // from -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(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,10 +228,12 @@ 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); return 0; @@ -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 WebServerImpl::GetSocketFactory(const uri &reque } return nullptr; } - -std::shared_ptr 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(new WebServerImpl(address.to_string(), port, rootPath, threads)); + ::CustomPpConfig::max_http_body_size = maxUploadSize; +} + +std::shared_ptr pipedal::WebServer::create( + const boost::asio::ip::address &address, + int port, + const char *rootPath, int threads, + size_t maxUploadSize) +{ + return std::shared_ptr(new WebServerImpl(address.to_string(), port, rootPath, threads, maxUploadSize)); } diff --git a/src/WebServer.hpp b/src/WebServer.hpp index a323e08..8b86ba8 100644 --- a/src/WebServer.hpp +++ b/src/WebServer.hpp @@ -236,7 +236,8 @@ public: const boost::asio::ip::address &address, int port, const char *rootPath, - int threads); + int threads, + size_t maxUploadsize); }; diff --git a/src/WebServerConfig.cpp b/src/WebServerConfig.cpp new file mode 100644 index 0000000..ed08964 --- /dev/null +++ b/src/WebServerConfig.cpp @@ -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 +#include +#include "PiPedalConfiguration.hpp" +#include "PiPedalModel.hpp" +#include "Banks.hpp" +#include "Ipv6Helpers.hpp" +#include +#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 split(const std::string& s, char delimiter) { + std::vector 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 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&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 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 interceptConfig{new InterceptConfig(port, maxUploadSize)}; + server.AddRequestHandler(interceptConfig); + + std::shared_ptr downloadIntercept = std::make_shared(&model); + server.AddRequestHandler(downloadIntercept); + + +} diff --git a/src/WebServerConfig.hpp b/src/WebServerConfig.hpp new file mode 100644 index 0000000..75b20d8 --- /dev/null +++ b/src/WebServerConfig.hpp @@ -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 + +namespace pipedal { + class WebServer; + class PiPedalModel; + void ConfigureWebServer(WebServer&server,PiPedalModel&model,int port,size_t maxUploadSize); +} \ No newline at end of file diff --git a/src/ZipFile.cpp b/src/ZipFile.cpp index f99853d..6b221f9 100644 --- a/src/ZipFile.cpp +++ b/src/ZipFile.cpp @@ -23,25 +23,25 @@ #include #include "ss.hpp" #include "Finally.hpp" +#include 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 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 nameMap; // avoid o(2) extraction operations. + std::map 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) @@ -70,13 +72,13 @@ ZipFileImpl::~ZipFileImpl() } } -std::vector ZipFileImpl::GetFiles() +std::vector ZipFileImpl::GetFiles() { std::vector 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,9 +88,10 @@ std::vector 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()) { // must call GetFiles() firest. @@ -96,38 +99,119 @@ 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); + zip_error_t *error = zip_get_error(this->zipFile); const char *strError = zip_error_strerror(error); throw std::runtime_error(SS("Failed to read zip content file. " << strError)); } - Finally t{[fIn] () mutable{ - zip_fclose(fIn); - }}; + 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 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; } diff --git a/src/ZipFile.hpp b/src/ZipFile.hpp index 6f1a2aa..61390fe 100644 --- a/src/ZipFile.hpp +++ b/src/ZipFile.hpp @@ -22,7 +22,35 @@ #include #include #include +#include +#include + + namespace pipedal { + + class zip_file_input_stream_buf : public std::streambuf { + private: + zip_file_t* file; + std::vector 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 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; }; } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 811c9eb..8355963 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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 #include #include "HtmlHelper.hpp" -#include "Ipv6Helpers.hpp" #include #include @@ -47,11 +47,10 @@ #include + 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(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 interceptConfig{new InterceptConfig(port, configuration.GetMaxUploadSize())}; - server->AddRequestHandler(interceptConfig); - - std::shared_ptr downloadIntercept = std::make_shared(&model); - server->AddRequestHandler(downloadIntercept); - + ConfigureWebServer(*server,model,port,configuration.GetMaxUploadSize()); { server->RunInBackground(-1); diff --git a/todo.txt b/todo.txt index 9236fa2..a0d6a32 100644 --- a/todo.txt +++ b/todo.txt @@ -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.