Toob File Player checkpoint.
This commit is contained in:
@@ -1809,6 +1809,9 @@ namespace pipedal
|
||||
{
|
||||
inputSysexBuffer.resize(1024);
|
||||
}
|
||||
~AlsaMidiDeviceImpl() {
|
||||
Close();
|
||||
}
|
||||
void Open(const AlsaMidiDeviceInfo &device)
|
||||
{
|
||||
runningStatus = 0;
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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 "AudioFileMetadata.hpp"
|
||||
#include <json.hpp>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <json_variant.hpp>
|
||||
#include "LRUCache.hpp"
|
||||
#include <sstream>
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
namespace chrono = std::chrono;
|
||||
|
||||
|
||||
// Safely encode a filename for use in a shell command
|
||||
static std::string shell_escape_filename(const std::string& filename) {
|
||||
std::string escaped;
|
||||
escaped.reserve(filename.size() + 2); // Reserve space for quotes and content
|
||||
|
||||
// Wrap the filename in single quotes
|
||||
escaped += '\'';
|
||||
|
||||
for (char c : filename) {
|
||||
// Escape single quotes by closing the quote, adding escaped quote, and reopening
|
||||
if (c == '\'') {
|
||||
escaped += "'\\''";
|
||||
} else {
|
||||
escaped += c;
|
||||
}
|
||||
}
|
||||
|
||||
escaped += '\'';
|
||||
return escaped;
|
||||
}
|
||||
|
||||
|
||||
static std::string readPipeToString(FILE* file) {
|
||||
std::string content;
|
||||
char buffer[4096];
|
||||
size_t bytesRead;
|
||||
while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
|
||||
content.append(buffer, bytesRead);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
static std::string GetJsonMetadata(const std::filesystem::path &path)
|
||||
{
|
||||
/* ffprobe -loglevel error -show_streams -show_format -print_format stream_tags -of json
|
||||
~/filename.flac */
|
||||
|
||||
std::stringstream ss;
|
||||
ss << "/usr/bin/ffprobe -loglevel error -show_streams -show_format -print_format stream_tags -of json "
|
||||
<< shell_escape_filename(path.string())
|
||||
<< " 2>/dev/null";
|
||||
|
||||
FILE *output = popen(ss.str().c_str(),"r");
|
||||
if(output == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Failed to process file.");
|
||||
}
|
||||
std::string result;
|
||||
try {
|
||||
result = readPipeToString(output);
|
||||
} catch (const std::exception&e)
|
||||
{
|
||||
pclose(output);
|
||||
throw std::runtime_error(e.what());
|
||||
}
|
||||
int retcode = pclose(output);
|
||||
if (retcode != 0)
|
||||
{
|
||||
throw std::runtime_error(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static float MetadataFloat(const json_variant&vt, float defaultValue = 0)
|
||||
{
|
||||
if (vt.is_string())
|
||||
{
|
||||
std::string s = vt.as_string();
|
||||
std::stringstream ss(s);
|
||||
float result = defaultValue;
|
||||
ss >> result;
|
||||
return result;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
static std::string MetadataString(const json_variant&o,const std::vector<std::string>& names) {
|
||||
for (const auto&name: names)
|
||||
{
|
||||
auto result = (*o.as_object())[name];
|
||||
if (result.is_string())
|
||||
{
|
||||
return result.as_string();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
AudioFileMetadata::AudioFileMetadata(const std::filesystem::path &file)
|
||||
{
|
||||
try {
|
||||
const std::string json = GetJsonMetadata(file);
|
||||
|
||||
json_variant vt;
|
||||
|
||||
std::stringstream ss(json);
|
||||
json_reader reader(ss);
|
||||
|
||||
reader.read(&vt);
|
||||
|
||||
auto top = vt.as_object();
|
||||
auto format = top->at("format").as_object();
|
||||
|
||||
this->duration_ = MetadataFloat(format->at("duration"),0);
|
||||
|
||||
auto tags = (*format)["tags"];
|
||||
if (tags.is_object())
|
||||
{
|
||||
this->album_ = MetadataString(tags,{"ALBUM","album"});
|
||||
this->artist_ = MetadataString(tags,{"ARTIST","artist"});
|
||||
this->albumArtist_ = MetadataString(tags,{"ALBUM ARTIST","album_artist","album artist"});
|
||||
this->title_ = MetadataString(tags,{"TITLE","title"});
|
||||
this->date_ = MetadataString(tags,{"DATE","date"});
|
||||
this->year_ = MetadataString(tags,{"YEAR","year"});
|
||||
this->track_ = MetadataString(tags,{"track","TRACK",});
|
||||
this->disc_ = MetadataString(tags,{"disc","DISC"});
|
||||
|
||||
if (title_ == "") {
|
||||
this->title_ = file.stem();
|
||||
}
|
||||
this->totalTracks_ = MetadataString(tags,{"TOTALTRACKS"});
|
||||
}
|
||||
} catch (const std::exception &e)
|
||||
{
|
||||
std::string title = file.stem();
|
||||
this->title_ = title;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
class AudioCacheKey {
|
||||
public:
|
||||
AudioCacheKey(const std::filesystem::path &path) {
|
||||
this->path = path.string();
|
||||
this->lastWrite = std::filesystem::last_write_time(path);
|
||||
|
||||
}
|
||||
// Equality operator
|
||||
bool operator==(const AudioCacheKey& other) const {
|
||||
return path == other.path && lastWrite == other.lastWrite;
|
||||
}
|
||||
|
||||
public:
|
||||
std::string path;
|
||||
fs::file_time_type lastWrite;
|
||||
};
|
||||
}
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<AudioCacheKey> {
|
||||
|
||||
|
||||
size_t operator()(const AudioCacheKey& key) const {
|
||||
size_t path_hash = hash<std::string>{}(key.path);
|
||||
size_t time_hash = hash<uintmax_t>{}(
|
||||
duration_cast<std::chrono::nanoseconds>(key.lastWrite.time_since_epoch()).count()
|
||||
);
|
||||
// Combine hashes (boost::hash_combine approach)
|
||||
return path_hash ^ (time_hash + 0x9e3779b9 + (path_hash << 6) + (path_hash >> 2));
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
static LRUCache<AudioCacheKey,std::string> metadataCache {500};
|
||||
static std::mutex metadataCacheMutex;
|
||||
|
||||
std::string pipedal::GetAudioFileMetadataString(const std::filesystem::path &path)
|
||||
{
|
||||
std::lock_guard lock { metadataCacheMutex};
|
||||
AudioCacheKey key { path};
|
||||
std::string result;
|
||||
if (metadataCache.get(key,result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
auto metadata = AudioFileMetadata(path);
|
||||
|
||||
std::stringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(metadata);
|
||||
result = ss.str();
|
||||
|
||||
metadataCache.put(key,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(AudioFileMetadata)
|
||||
JSON_MAP_REFERENCE(AudioFileMetadata, duration)
|
||||
JSON_MAP_REFERENCE(AudioFileMetadata, title)
|
||||
JSON_MAP_REFERENCE(AudioFileMetadata, track)
|
||||
JSON_MAP_REFERENCE(AudioFileMetadata, album)
|
||||
JSON_MAP_REFERENCE(AudioFileMetadata, disc)
|
||||
JSON_MAP_REFERENCE(AudioFileMetadata, artist)
|
||||
JSON_MAP_REFERENCE(AudioFileMetadata, albumArtist)
|
||||
JSON_MAP_REFERENCE(AudioFileMetadata, totalTracks)
|
||||
JSON_MAP_REFERENCE(AudioFileMetadata, date)
|
||||
JSON_MAP_REFERENCE(AudioFileMetadata, year)
|
||||
|
||||
JSON_MAP_END()
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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 <string>
|
||||
#include <filesystem>
|
||||
#include "json.hpp"
|
||||
|
||||
|
||||
namespace pipedal
|
||||
{
|
||||
|
||||
#define GETTER_SETTER_REF(name) \
|
||||
const decltype(name##_) &name() const { return name##_; } \
|
||||
void name(const decltype(name##_) &value) { name##_ = value; }
|
||||
|
||||
#define GETTER_SETTER_VEC(name) \
|
||||
decltype(name##_) &name() { return name##_; } \
|
||||
const decltype(name##_) &name() const { return name##_; }
|
||||
|
||||
// void name(decltype(const name##_) &value) { name##_ = value; }
|
||||
// void name(decltype(const name##_) &&value) { name##_ = std::move(value); }
|
||||
|
||||
#define GETTER_SETTER(name) \
|
||||
decltype(name##_) name() const { return name##_; } \
|
||||
void name(decltype(name##_) value) { name##_ = value; }
|
||||
|
||||
class AudioFileMetadata
|
||||
{
|
||||
private:
|
||||
float duration_ = 0;
|
||||
std::string title_;
|
||||
std::string track_;
|
||||
std::string album_;
|
||||
std::string disc_;
|
||||
std::string artist_;
|
||||
std::string albumArtist_;
|
||||
std::string totalTracks_;
|
||||
std::string date_;
|
||||
std::string year_;
|
||||
|
||||
|
||||
public:
|
||||
AudioFileMetadata(const std::filesystem::path &file);
|
||||
|
||||
GETTER_SETTER(duration);
|
||||
GETTER_SETTER_REF(title);
|
||||
GETTER_SETTER_REF(track);
|
||||
GETTER_SETTER_REF(album);
|
||||
GETTER_SETTER_REF(disc);
|
||||
GETTER_SETTER_REF(artist);
|
||||
GETTER_SETTER_REF(albumArtist);
|
||||
|
||||
public:
|
||||
DECLARE_JSON_MAP(AudioFileMetadata);
|
||||
|
||||
};
|
||||
|
||||
std::string GetAudioFileMetadataString(const std::filesystem::path &path);
|
||||
|
||||
#undef GETTER_SETTER
|
||||
#undef GETTER_SETTER_VEC
|
||||
#undef GETTER_SETTER_REF
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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 "AudioFiles.hpp"
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
|
||||
static constexpr size_t INVALID_INDEX = std::numeric_limits<size_t>::max();
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
class DirectoryList {
|
||||
public:
|
||||
DirectoryList(const fs::path&path);
|
||||
|
||||
const std::vector<std::filesystem::path> &files() {
|
||||
return files_;
|
||||
}
|
||||
private:
|
||||
|
||||
std::vector<std::filesystem::path> files_;
|
||||
};
|
||||
|
||||
class AudioFileInfoImpl: public AudioFileInfo {
|
||||
public:
|
||||
virtual ~AudioFileInfoImpl() { }
|
||||
protected:
|
||||
virtual std::string GetNextAudioFile(const std::string&path) override;
|
||||
virtual std::string GetPreviousAudioFile(const std::string&path) override;
|
||||
};
|
||||
/////////////////////
|
||||
|
||||
DirectoryList::DirectoryList(const fs::path&path)
|
||||
{
|
||||
for (const auto&dirEntry: fs::directory_iterator(path))
|
||||
{
|
||||
if (dirEntry.is_regular_file())
|
||||
{
|
||||
fs::path t = dirEntry.path();
|
||||
if (!t.filename().string().starts_with('.'))
|
||||
{
|
||||
files_.push_back(std::move(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string AudioFileInfoImpl::GetNextAudioFile(const std::string&path_) {
|
||||
try
|
||||
{
|
||||
fs::path path {path_};
|
||||
if (fs::exists(path) && fs::is_regular_file(path))
|
||||
{
|
||||
DirectoryList directoryList(path.parent_path());
|
||||
size_t index = INVALID_INDEX;
|
||||
auto &files = directoryList.files();
|
||||
for (size_t i= 0; i < files.size(); ++i)
|
||||
{
|
||||
if (files[i] == path)
|
||||
{
|
||||
size_t next = i;
|
||||
if (next == files.size()-1)
|
||||
{
|
||||
next = 0;
|
||||
} else {
|
||||
++next;
|
||||
}
|
||||
return files[next];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (const std::exception&e)
|
||||
{
|
||||
|
||||
}
|
||||
return "";
|
||||
|
||||
}
|
||||
std::string AudioFileInfoImpl::GetPreviousAudioFile(const std::string&path_)
|
||||
{
|
||||
try
|
||||
{
|
||||
fs::path path {path_};
|
||||
if (fs::exists(path) && fs::is_regular_file(path))
|
||||
{
|
||||
DirectoryList directoryList(path.parent_path());
|
||||
size_t index = INVALID_INDEX;
|
||||
auto &files = directoryList.files();
|
||||
for (size_t i= 0; i < files.size(); ++i)
|
||||
{
|
||||
if (files[i] == path)
|
||||
{
|
||||
size_t previous = i;
|
||||
if (previous == 0)
|
||||
{
|
||||
previous = files.size()-1;
|
||||
} else {
|
||||
--previous;
|
||||
}
|
||||
return files[previous];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (const std::exception&e)
|
||||
{
|
||||
|
||||
}
|
||||
return "";
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static AudioFileInfoImpl instance;
|
||||
|
||||
AudioFileInfo&AudioFileInfo::GetInstance()
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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 <string>
|
||||
|
||||
namespace pipedal {
|
||||
class AudioFileInfo {
|
||||
protected:
|
||||
AudioFileInfo() { }
|
||||
virtual ~AudioFileInfo() {}
|
||||
public:
|
||||
static AudioFileInfo&GetInstance();
|
||||
|
||||
virtual std::string GetNextAudioFile(const std::string&path) = 0;
|
||||
virtual std::string GetPreviousAudioFile(const std::string&path) = 0;
|
||||
};
|
||||
}
|
||||
+1
-1
@@ -1173,7 +1173,7 @@ private:
|
||||
|
||||
if (buffersValid)
|
||||
{
|
||||
pedalboard->ProcessParameterRequests(pParameterRequests);
|
||||
pedalboard->ProcessParameterRequests(pParameterRequests,nframes);
|
||||
|
||||
processed = pedalboard->Run(inputBuffers, outputBuffers, (uint32_t)nframes, &realtimeWriter);
|
||||
if (processed)
|
||||
|
||||
+10
-4
@@ -69,6 +69,7 @@ namespace pipedal
|
||||
|
||||
const char *errorMessage = nullptr;
|
||||
std::string jsonResponse;
|
||||
int64_t sampleTimeout = 0;
|
||||
|
||||
RealtimePatchPropertyRequest *pNext = nullptr;
|
||||
|
||||
@@ -102,13 +103,16 @@ namespace pipedal
|
||||
int64_t instanceId_,
|
||||
LV2_URID uridUri_,
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess_,
|
||||
std::function<void(const std::string &error)> onError_)
|
||||
std::function<void(const std::string &error)> onError_,
|
||||
size_t sampleTimeout)
|
||||
: onPatchRequestComplete(onPatchRequestcomplete_),
|
||||
clientId(clientId_),
|
||||
instanceId(instanceId_),
|
||||
uridUri(uridUri_),
|
||||
onSuccess(onSuccess_),
|
||||
onError(onError_)
|
||||
onError(onError_),
|
||||
sampleTimeout((int64_t)sampleTimeout)
|
||||
|
||||
{
|
||||
requestType = RequestType::PatchGet;
|
||||
}
|
||||
@@ -119,13 +123,15 @@ namespace pipedal
|
||||
LV2_URID uridUri_,
|
||||
LV2_Atom *atomValue,
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess_,
|
||||
std::function<void(const std::string &error)> onError_)
|
||||
std::function<void(const std::string &error)> onError_,
|
||||
size_t sampleTimeout)
|
||||
: onPatchRequestComplete(onPatchRequestcomplete_),
|
||||
clientId(clientId_),
|
||||
instanceId(instanceId_),
|
||||
uridUri(uridUri_),
|
||||
onSuccess(onSuccess_),
|
||||
onError(onError_)
|
||||
onError(onError_),
|
||||
sampleTimeout(sampleTimeout)
|
||||
{
|
||||
requestType = RequestType::PatchSet;
|
||||
size_t size = atomValue->size + sizeof(LV2_Atom);
|
||||
|
||||
+12
-3
@@ -12,6 +12,7 @@ set (USE_SANITIZE OFF) # seems to be broken on Ubuntu 24.10
|
||||
set(CXX_STANDARD 20)
|
||||
|
||||
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
# FOR FTXUI
|
||||
@@ -45,6 +46,9 @@ include(FindPkgConfig)
|
||||
|
||||
find_package(sdbus-c++ REQUIRED)
|
||||
|
||||
find_package(SQLiteCpp REQUIRED)
|
||||
|
||||
|
||||
|
||||
find_package(ICU REQUIRED COMPONENTS uc i18n )
|
||||
message(STATUS "ICU_LIBRARIES: ${ICU_LIBRARIES}")
|
||||
@@ -192,6 +196,9 @@ else()
|
||||
endif()
|
||||
|
||||
set (PIPEDAL_SOURCES
|
||||
AudioFiles.cpp AudioFiles.hpp
|
||||
AudioFileMetadata.cpp AudioFileMetadata.hpp
|
||||
LRUCache.hpp
|
||||
CpuTemperatureMonitor.cpp CpuTemperatureMonitor.hpp
|
||||
SchedulerPriority.hpp SchedulerPriority.cpp
|
||||
ModFileTypes.cpp ModFileTypes.hpp
|
||||
@@ -304,6 +311,7 @@ set (PIPEDAL_INCLUDES
|
||||
${JACK_INCLUDE_DIRS} ${LILV_0_INCLUDE_DIRS}
|
||||
${VST3_INCLUDES}
|
||||
${WEBSOCKETPP_INCLUDE_DIRS}
|
||||
${SQLite3_INCLUDE_DIRS}
|
||||
.
|
||||
)
|
||||
|
||||
@@ -314,7 +322,7 @@ set(PIPEDAL_LIBS libpipedald zip
|
||||
${VST3_LIBRARIES}
|
||||
${LILV_0_LIBRARIES}
|
||||
# ${JACK_LIBRARIES} - pending delete for JACK support.
|
||||
|
||||
${SQLite3_LIBRARIES}
|
||||
)
|
||||
|
||||
|
||||
@@ -383,8 +391,9 @@ add_executable(AuxInTest
|
||||
)
|
||||
|
||||
add_executable(pipedaltest
|
||||
testMain.cpp
|
||||
testMain.cpp
|
||||
|
||||
LRUCacheTest.cpp
|
||||
ModFileTypesTest.cpp
|
||||
jsonTest.cpp
|
||||
UpdaterTest.cpp
|
||||
@@ -822,7 +831,7 @@ add_custom_command(OUTPUT ${REACT_NOTICES_FILE}
|
||||
--output ${REACT_NOTICES_FILE}
|
||||
--projectCopyright ${DEBIAN_COPYRIGHT_FILE}
|
||||
liblilv-0-0 ${BOOST_COPYRIGHT_DIR} lv2-dev libsdbus-c++-dev librsvg2-2 libpango-1.0-0 libx11-6 libxrandr2
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
DEPENDS ${DEBIAN_COPYRIGHT_FILE} "$<TARGET_FILE:processcopyrights>"
|
||||
COMMENT "Updating copyright notices."
|
||||
)
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2025 Robin E. R. Davies
|
||||
* All rights reserved.
|
||||
|
||||
* 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 <unordered_map>
|
||||
#include <list>
|
||||
#include <stdexcept>
|
||||
|
||||
template <typename KEY, typename VALUE>
|
||||
class LRUCache {
|
||||
public:
|
||||
struct CacheNode {
|
||||
KEY key;
|
||||
VALUE value;
|
||||
CacheNode(KEY k, VALUE v) : key(k), value(v) {}
|
||||
};
|
||||
private:
|
||||
size_t capacity;
|
||||
std::list<CacheNode> cache_list; // Doubly-linked list for LRU order
|
||||
std::unordered_map<KEY, typename std::list<CacheNode>::iterator> cache_map; // Map key to list iterator
|
||||
|
||||
public:
|
||||
explicit LRUCache(size_t cap) : capacity(cap) {
|
||||
if (cap == 0) {
|
||||
throw std::invalid_argument("Cache capacity must be greater than 0");
|
||||
}
|
||||
}
|
||||
|
||||
// Get value by key, return false if not found
|
||||
bool get(const KEY& key, VALUE& value) {
|
||||
auto it = cache_map.find(key);
|
||||
if (it == cache_map.end()) {
|
||||
return false;
|
||||
}
|
||||
// Move to front (most recently used)
|
||||
cache_list.splice(cache_list.begin(), cache_list, it->second);
|
||||
value = it->second->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Put key-value pair into cache
|
||||
void put(const KEY& key, const VALUE& value) {
|
||||
auto it = cache_map.find(key);
|
||||
if (it != cache_map.end()) {
|
||||
// Update existing key
|
||||
cache_list.splice(cache_list.begin(), cache_list, it->second);
|
||||
it->second->value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Add new key-value pair
|
||||
cache_list.emplace_front(key, value);
|
||||
cache_map[key] = cache_list.begin();
|
||||
|
||||
// Evict least recently used if over capacity
|
||||
if (cache_map.size() > capacity) {
|
||||
auto lru = cache_list.back();
|
||||
cache_map.erase(lru.key);
|
||||
cache_list.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if key exists
|
||||
bool contains(const KEY& key) const {
|
||||
return cache_map.find(key) != cache_map.end();
|
||||
}
|
||||
|
||||
// Get current size
|
||||
size_t size() const {
|
||||
return cache_map.size();
|
||||
}
|
||||
|
||||
// Get capacity
|
||||
size_t get_capacity() const {
|
||||
return capacity;
|
||||
}
|
||||
const std::list<CacheNode> &cache() const {
|
||||
return cache_list;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) 2025 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 "pch.h"
|
||||
#include "catch.hpp"
|
||||
#include "LRUCache.hpp"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
TEST_CASE( "LRUCache test", "[lrucache]" ) {
|
||||
|
||||
LRUCache<int,int> lruCache(5);
|
||||
|
||||
for (int i = 0; i < 10; ++i)
|
||||
{
|
||||
lruCache.put(i,i);
|
||||
}
|
||||
REQUIRE(lruCache.cache().size() == 5);
|
||||
|
||||
REQUIRE(lruCache.cache().begin()->value == 9);
|
||||
|
||||
int value;
|
||||
REQUIRE(lruCache.get(5,value) == true);
|
||||
REQUIRE(value == 5);
|
||||
REQUIRE(lruCache.cache().begin()->value == 5);
|
||||
|
||||
cout << '[';
|
||||
for (auto it = lruCache.cache().begin();it != lruCache.cache().end(); ++it)
|
||||
{
|
||||
cout << it->value << ',';
|
||||
}
|
||||
cout << ']' << endl;
|
||||
for (auto it = ++lruCache.cache().begin();it != lruCache.cache().end(); ++it)
|
||||
{
|
||||
REQUIRE(it->value != 5);
|
||||
}
|
||||
lruCache.put(1,1);
|
||||
cout << '[';
|
||||
for (auto it = lruCache.cache().begin();it != lruCache.cache().end(); ++it)
|
||||
{
|
||||
cout << it->value << ',';
|
||||
}
|
||||
cout << ']' << endl;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1116,3 +1116,4 @@ void Lv2Effect::SetPathPatchProperty(const std::string &propertyUri, const std::
|
||||
{
|
||||
mainThreadPathProperties[propertyUri] = jsonAtom;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ namespace pipedal
|
||||
virtual void OnLogDebug(const char*message);
|
||||
|
||||
private:
|
||||
|
||||
std::unordered_map<std::string,int> controlIndex;
|
||||
|
||||
FileBrowserFilesFeature fileBrowserFilesFeature;
|
||||
|
||||
@@ -562,11 +562,13 @@ void Lv2Pedalboard::GatherPathPatchProperties(IPatchWriterCallback *cbPatchWrite
|
||||
}
|
||||
}
|
||||
|
||||
void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests)
|
||||
void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests, size_t samplesThisTime)
|
||||
{
|
||||
while (pParameterRequests != nullptr)
|
||||
{
|
||||
pParameterRequests->sampleTimeout -= samplesThisTime;
|
||||
IEffect *pEffect = this->GetEffect(pParameterRequests->instanceId);
|
||||
|
||||
if (pEffect == nullptr)
|
||||
{
|
||||
pParameterRequests->errorMessage = "No such effect.";
|
||||
@@ -574,8 +576,12 @@ void Lv2Pedalboard::ProcessParameterRequests(RealtimePatchPropertyRequest *pPara
|
||||
else if (pEffect->IsVst3())
|
||||
{
|
||||
pParameterRequests->errorMessage = "Not supported for VST3 plugins";
|
||||
} else if (pParameterRequests->sampleTimeout < 0)
|
||||
{
|
||||
pParameterRequests->sampleTimeout = 0;
|
||||
pParameterRequests->errorMessage = "Timed out.";
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
if (pEffect->IsLv2Effect())
|
||||
{
|
||||
|
||||
@@ -153,7 +153,7 @@ namespace pipedal
|
||||
|
||||
void ResetAtomBuffers();
|
||||
|
||||
void ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests);
|
||||
void ProcessParameterRequests(RealtimePatchPropertyRequest *pParameterRequests, size_t samplesThisTime);
|
||||
void GatherPatchProperties(RealtimePatchPropertyRequest *pParameterRequests);
|
||||
void GatherPathPatchProperties(IPatchWriterCallback *cbPatchWriter);
|
||||
|
||||
|
||||
+35
-18
@@ -1615,19 +1615,30 @@ void PiPedalModel::SendSetPatchProperty(
|
||||
{
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
if (!audioHost)
|
||||
{
|
||||
onError("Audio not running.");
|
||||
return;
|
||||
}
|
||||
|
||||
// save the property to the preset (currently used to reconstruct snapshots only)
|
||||
PedalboardItem *pedalboardItem = this->pedalboard.GetItem(instanceId);
|
||||
if (pedalboardItem)
|
||||
if (pedalboardItem && value.is_string())
|
||||
{
|
||||
json_variant abstractPath = pluginHost.AbstractPath(value);
|
||||
std::ostringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(abstractPath);
|
||||
std::string atomString = ss.str();
|
||||
pedalboardItem->pathProperties_[propertyUri] = atomString;
|
||||
std::shared_ptr<Lv2PluginInfo> pluginInfo = GetPluginInfo(pedalboardItem->uri_);
|
||||
auto pipedalUi = pluginInfo->piPedalUI();
|
||||
auto fileProperty = pipedalUi->GetFileProperty(propertyUri);
|
||||
if (fileProperty && value.is_string()) {
|
||||
|
||||
json_variant abstractPath = pluginHost.AbstractPath(value);
|
||||
std::ostringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(abstractPath);
|
||||
std::string atomString = ss.str();
|
||||
pedalboardItem->pathProperties_[propertyUri] = atomString;
|
||||
}
|
||||
this->SetPresetChanged(clientId, true);
|
||||
}
|
||||
this->SetPresetChanged(clientId, true);
|
||||
LV2_Atom *atomValue = atomConverter.ToAtom(value);
|
||||
|
||||
std::function<void(RealtimePatchPropertyRequest *)> onRequestComplete{
|
||||
@@ -1657,7 +1668,7 @@ void PiPedalModel::SendSetPatchProperty(
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pParameter->onSuccess)
|
||||
if (onSuccess)
|
||||
{
|
||||
onSuccess();
|
||||
}
|
||||
@@ -1668,10 +1679,12 @@ void PiPedalModel::SendSetPatchProperty(
|
||||
}};
|
||||
|
||||
LV2_URID urid = this->pluginHost.GetLv2Urid(propertyUri.c_str());
|
||||
|
||||
size_t sampleTimeout = 0.5*audioHost->GetSampleRate();
|
||||
RealtimePatchPropertyRequest *request = new RealtimePatchPropertyRequest(
|
||||
onRequestComplete,
|
||||
clientId, instanceId, urid, atomValue, nullptr, onError);
|
||||
clientId, instanceId, urid, atomValue, nullptr, onError,
|
||||
sampleTimeout
|
||||
);
|
||||
|
||||
outstandingParameterRequests.push_back(request);
|
||||
if (this->audioHost)
|
||||
@@ -1684,7 +1697,7 @@ void PiPedalModel::SendGetPatchProperty(
|
||||
int64_t clientId,
|
||||
int64_t instanceId,
|
||||
const std::string uri,
|
||||
std::function<void(const std::string &jsonResjult)> onSuccess,
|
||||
std::function<void(const std::string &jsonResult)> onSuccess,
|
||||
std::function<void(const std::string &error)> onError)
|
||||
{
|
||||
std::function<void(RealtimePatchPropertyRequest *)> onRequestComplete{
|
||||
@@ -1732,16 +1745,20 @@ void PiPedalModel::SendGetPatchProperty(
|
||||
}};
|
||||
|
||||
LV2_URID urid = this->pluginHost.GetLv2Urid(uri.c_str());
|
||||
RealtimePatchPropertyRequest *request = new RealtimePatchPropertyRequest(
|
||||
onRequestComplete,
|
||||
clientId, instanceId, urid, onSuccess, onError);
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
outstandingParameterRequests.push_back(request);
|
||||
if (this->audioHost)
|
||||
|
||||
if (!this->audioHost)
|
||||
{
|
||||
this->audioHost->sendRealtimeParameterRequest(request);
|
||||
onError("Audio stopped.");
|
||||
}
|
||||
size_t sampleTimeout = 0.3*audioHost->GetSampleRate();
|
||||
RealtimePatchPropertyRequest *request = new RealtimePatchPropertyRequest(
|
||||
onRequestComplete,
|
||||
clientId, instanceId, urid, onSuccess, onError,sampleTimeout);
|
||||
|
||||
outstandingParameterRequests.push_back(request);
|
||||
this->audioHost->sendRealtimeParameterRequest(request);
|
||||
}
|
||||
|
||||
BankIndex PiPedalModel::GetBankIndex() const
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#include "ModFileTypes.hpp"
|
||||
|
||||
|
||||
#define PIPEDAL_HOST_FEATURE "http://github.com/rerdavies/pipedal#host" // Plugin can only be hosted by PiPedal
|
||||
|
||||
#define PIPEDAL_PATCH "http://github.com/rerdavies/pipedal/patch"
|
||||
#define PIPEDAL_PATCH_PREFIX PIPEDAL_PATCH "#"
|
||||
#define PIPEDAL_PATCH__readable (PIPEDAL_PATCH_PREFIX "readable")
|
||||
|
||||
+2
-1
@@ -615,7 +615,7 @@ Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin
|
||||
// example:
|
||||
|
||||
// <http://github.com/mikeoliphant/neural-amp-modeler-lv2#model>
|
||||
// a lv2:Parameter;
|
||||
// a lv2:Parameter;
|
||||
// rdfs:label "Model";
|
||||
// rdfs:range atom:Path.
|
||||
// ...
|
||||
@@ -901,6 +901,7 @@ std::vector<std::string> supportedFeatures = {
|
||||
LV2_STATE__mapPath,
|
||||
LV2_STATE__freePath,
|
||||
LV2_CORE__inPlaceBroken,
|
||||
PIPEDAL_HOST_FEATURE,
|
||||
|
||||
// UI features that we can ignore, since we won't load their ui.
|
||||
"http://lv2plug.in/ns/extensions/ui#makeResident",
|
||||
|
||||
+100
-22
@@ -35,6 +35,9 @@
|
||||
#include "json.hpp"
|
||||
#include "HotspotManager.hpp"
|
||||
#include "MimeTypes.hpp"
|
||||
#include "AudioFileMetadata.hpp"
|
||||
#include "AudioFiles.hpp"
|
||||
|
||||
|
||||
#define OLD_PRESET_EXTENSION ".piPreset"
|
||||
#define PRESET_EXTENSION ".piPreset"
|
||||
@@ -52,20 +55,20 @@ using namespace pipedal;
|
||||
using namespace boost::system;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
|
||||
static bool HasDotDot(const std::filesystem::path &path) {
|
||||
static bool HasDotDot(const std::filesystem::path &path)
|
||||
{
|
||||
for (auto &part : path)
|
||||
{
|
||||
if (part == "..") {
|
||||
if (part == "..")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (part == ".")
|
||||
if (part == ".")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
class UserUploadResponse
|
||||
@@ -80,12 +83,12 @@ JSON_MAP_REFERENCE(UserUploadResponse, errorMessage)
|
||||
JSON_MAP_REFERENCE(UserUploadResponse, path)
|
||||
JSON_MAP_END()
|
||||
|
||||
static std::string GetMimeType(const std::filesystem::path&path) {
|
||||
static std::string GetMimeType(const std::filesystem::path &path)
|
||||
{
|
||||
std::string extension = path.extension();
|
||||
const MimeTypes&mimeTypes = MimeTypes::instance();
|
||||
const MimeTypes &mimeTypes = MimeTypes::instance();
|
||||
auto result = mimeTypes.MimeTypeFromExtension(extension);
|
||||
return result;
|
||||
|
||||
}
|
||||
static bool IsZipFile(const std::filesystem::path &path)
|
||||
{
|
||||
@@ -179,6 +182,17 @@ public:
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (segment == "AudioMetadata")
|
||||
{
|
||||
return true;
|
||||
} else if (segment == "NextAudioFile")
|
||||
{
|
||||
return true;
|
||||
} else if (segment == "PreviousAudioFile")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -246,15 +260,17 @@ public:
|
||||
try
|
||||
{
|
||||
std::string segment = request_uri.segment(1);
|
||||
if (segment == "downloadMediaFile") {
|
||||
if (segment == "downloadMediaFile")
|
||||
{
|
||||
fs::path path = request_uri.query("path");
|
||||
|
||||
|
||||
if (!fs::exists(path) || !this->model->IsInUploadsDirectory(path) || HasDotDot(path))
|
||||
{
|
||||
throw PiPedalException("File not found.");
|
||||
}
|
||||
auto mimeType = GetMimeType(path);
|
||||
if (mimeType.empty()) {
|
||||
if (mimeType.empty())
|
||||
{
|
||||
throw PiPedalException("Can't download files of this type.");
|
||||
}
|
||||
res.set(HttpField::content_type, mimeType);
|
||||
@@ -343,18 +359,20 @@ public:
|
||||
{
|
||||
std::string segment = request_uri.segment(1);
|
||||
|
||||
if (segment == "downloadMediaFile") {
|
||||
if (segment == "downloadMediaFile")
|
||||
{
|
||||
fs::path path = request_uri.query("path");
|
||||
|
||||
|
||||
bool t = this->model->IsInUploadsDirectory(path);
|
||||
std::cout << (t? "true": "false") << std::endl;
|
||||
std::cout << (t ? "true" : "false") << std::endl;
|
||||
(void)t;
|
||||
if (!fs::exists(path) || !this->model->IsInUploadsDirectory(path) || HasDotDot(path))
|
||||
{
|
||||
throw PiPedalException("File not found.");
|
||||
}
|
||||
auto mimeType = GetMimeType(path);
|
||||
if (mimeType.empty()) {
|
||||
if (mimeType.empty())
|
||||
{
|
||||
throw PiPedalException("Can't download files of this type.");
|
||||
}
|
||||
res.set(HttpField::content_type, mimeType);
|
||||
@@ -363,9 +381,10 @@ public:
|
||||
res.set(HttpField::content_disposition, disposition);
|
||||
size_t contentLength = std::filesystem::file_size(path);
|
||||
res.setContentLength(contentLength);
|
||||
res.setBodyFile(path,false);
|
||||
res.setBodyFile(path, false);
|
||||
return;
|
||||
} else if (segment == "downloadPluginPresets")
|
||||
}
|
||||
else if (segment == "downloadPluginPresets")
|
||||
{
|
||||
std::string name;
|
||||
std::string content;
|
||||
@@ -416,6 +435,65 @@ public:
|
||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
|
||||
res.setBodyFile(tmpFile);
|
||||
}
|
||||
else if (segment == "AudioMetadata")
|
||||
{
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
// res.set(HttpField::cache_control, "no-cache");
|
||||
fs::path path = request_uri.query("path");
|
||||
|
||||
if (!fs::exists(path) || !this->model->IsInUploadsDirectory(path) || HasDotDot(path))
|
||||
{
|
||||
throw PiPedalException("File not found.");
|
||||
}
|
||||
std::string metadata = GetAudioFileMetadataString(path);
|
||||
res.setBody(metadata);
|
||||
}
|
||||
else if (segment == "NextAudioFile")
|
||||
{
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
// res.set(HttpField::cache_control, "no-cache");
|
||||
fs::path path = request_uri.query("path");
|
||||
|
||||
try {
|
||||
if (!fs::exists(path) || !this->model->IsInUploadsDirectory(path) || HasDotDot(path))
|
||||
{
|
||||
throw PiPedalException("File not found.");
|
||||
}
|
||||
|
||||
std::string nextFile = AudioFileInfo::GetInstance().GetNextAudioFile(path);
|
||||
std::stringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(nextFile);
|
||||
|
||||
res.setBody(ss.str());
|
||||
} catch (const std::exception&e)
|
||||
{
|
||||
res.setBody("''");
|
||||
}
|
||||
}
|
||||
else if (segment == "PreviousAudioFile")
|
||||
{
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
// res.set(HttpField::cache_control, "no-cache");
|
||||
fs::path path = request_uri.query("path");
|
||||
|
||||
try {
|
||||
if (!fs::exists(path) || !this->model->IsInUploadsDirectory(path) || HasDotDot(path))
|
||||
{
|
||||
throw PiPedalException("File not found.");
|
||||
}
|
||||
std::string previousFile = AudioFileInfo::GetInstance().GetPreviousAudioFile(path);
|
||||
std::stringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(previousFile);
|
||||
|
||||
res.setBody(ss.str());
|
||||
} catch (const std::exception&e)
|
||||
{
|
||||
res.setBody("\"\"");
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
throw PiPedalException("Not found");
|
||||
@@ -628,7 +706,7 @@ public:
|
||||
|
||||
int64_t instanceId;
|
||||
{
|
||||
std::istringstream ss { instanceIdString};
|
||||
std::istringstream ss{instanceIdString};
|
||||
ss >> instanceId;
|
||||
if (!ss)
|
||||
{
|
||||
@@ -669,7 +747,7 @@ public:
|
||||
if (extensionChecker.IsValidExtension(extension))
|
||||
{
|
||||
auto si = zipFile->GetFileInputStream(inputFile);
|
||||
std::string path = this->model->UploadUserFile(directory, instanceId,patchProperty, inputFile, si, zipFile->GetFileSize(inputFile));
|
||||
std::string path = this->model->UploadUserFile(directory, instanceId, patchProperty, inputFile, si, zipFile->GetFileSize(inputFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -687,7 +765,7 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
outputFileName = this->model->UploadUserFile(directory, instanceId,patchProperty, filename, req.get_body_input_stream(), req.content_length());
|
||||
outputFileName = this->model->UploadUserFile(directory, instanceId, patchProperty, filename, req.get_body_input_stream(), req.content_length());
|
||||
}
|
||||
|
||||
if (outputFileName.is_relative())
|
||||
@@ -783,7 +861,7 @@ public:
|
||||
<< ", \"socket_server_address\": \"" << webSocketAddress
|
||||
<< "\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize
|
||||
<< ", \"enable_auto_update\": " << (ENABLE_AUTO_UPDATE ? " true" : "false")
|
||||
<< ", \"has_wifi_device\": " << (HotspotManager::HasWifiDevice() ? " true": "false")
|
||||
<< ", \"has_wifi_device\": " << (HotspotManager::HasWifiDevice() ? " true" : "false")
|
||||
<< " }";
|
||||
|
||||
return s.str();
|
||||
|
||||
Reference in New Issue
Block a user