Improvements to file uploads. UI improvements for future looper and audio capture plugins.
This commit is contained in:
+6
-3
@@ -190,6 +190,7 @@ set (PIPEDAL_SOURCES
|
||||
CpuTemperatureMonitor.cpp CpuTemperatureMonitor.hpp
|
||||
SchedulerPriority.hpp SchedulerPriority.cpp
|
||||
ModFileTypes.cpp ModFileTypes.hpp
|
||||
MimeTypes.cpp MimeTypes.hpp
|
||||
PatchPropertyWriter.hpp
|
||||
PresetBundle.cpp PresetBundle.hpp
|
||||
RealtimeMidiEventType.hpp
|
||||
@@ -208,9 +209,8 @@ set (PIPEDAL_SOURCES
|
||||
FilePropertyDirectoryTree.cpp FilePropertyDirectoryTree.hpp
|
||||
FileEntry.cpp FileEntry.hpp
|
||||
atom_object.hpp atom_object.cpp
|
||||
FileBrowserFiles.h
|
||||
lv2ext/pipedal.lv2/ext/fileBrowser.h
|
||||
FileBrowserFilesFeature.hpp FileBrowserFilesFeature.cpp
|
||||
MimeTypes.cpp MimeTypes.hpp
|
||||
inverting_mutex.hpp
|
||||
DbDezipper.hpp DbDezipper.cpp
|
||||
WebServerLog.hpp
|
||||
@@ -372,8 +372,10 @@ set_target_properties(hotspotManagerTest PROPERTIES EXCLUDE_FROM_ALL true)
|
||||
|
||||
|
||||
|
||||
add_executable(pipedaltest testMain.cpp
|
||||
add_executable(pipedaltest
|
||||
testMain.cpp
|
||||
|
||||
ModFileTypesTest.cpp
|
||||
jsonTest.cpp
|
||||
UpdaterTest.cpp
|
||||
|
||||
@@ -692,6 +694,7 @@ add_executable(pipedalconfig
|
||||
alsaCheck.hpp
|
||||
BootConfig.cpp BootConfig.hpp
|
||||
ModFileTypes.cpp ModFileTypes.hpp
|
||||
MimeTypes.cpp MimeTypes.hpp
|
||||
PiPedalConfiguration.hpp PiPedalConfiguration.cpp
|
||||
JackServerSettings.hpp JackServerSettings.cpp
|
||||
SystemConfigFile.hpp SystemConfigFile.cpp
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
#ifndef FILEBROWSERFILES_H
|
||||
#define FILEBROWSERFILES_H
|
||||
|
||||
#define LV2_FILEBROWSER_URI "http://two-play.com/ns/ext/fileBrowser"
|
||||
#define LV2_FILEBROWSER_PREFIX LV2_FILEBROWSER_URI "#"
|
||||
#define LV2_FILEBROWSER__files LV2_FILEBROWSER_PREFIX "files" ///< http://two-play.com/ns/ext/fileBrowser#files >
|
||||
|
||||
/**
|
||||
@defgroup filedialog LV2_FileBrowser_Files
|
||||
@ingroup lv2
|
||||
|
||||
A feature for LV2 plugins to provide sample files (or other selectable files) within the plugin's resource bundle to host file selection dialogs.
|
||||
|
||||
Browser directories may contain actual files that typically have been uploaded by users; but may also contain symlinks to files
|
||||
in other locations. This feature allows plugins to publish files within the plugin's resource bundle to browser directories. For
|
||||
each published file consists of a symlink in the browser directory that links to a corresponding file in the plugins resource bundle.
|
||||
|
||||
The LV2_FileBrowser_Files.publish_resource_files() method allows plugins to publish entire directories. publish_resource_files()
|
||||
implements a versioning system, which tracks whether sample files have been deleted by the user. If the symlink for sample files that were installed
|
||||
by a previous version of the plugin have been deleted, the sample files from the current version are not reinstalled. See LV2_FileBrowser_Files.publish_resource_files.
|
||||
not re-installed.
|
||||
|
||||
See: <http://rerdavies.github.io/pipedal/TBD XXX.html> for more details.
|
||||
|
||||
@{
|
||||
*/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
LV2_FileBrowser_Status_Success = 0,
|
||||
LV2_FileBrowser_Status_Err_Filesystem = 1,
|
||||
LV2_FileBrowser_Status_Err_InvalidParameter = 1,
|
||||
} LV2_FileBrowser_Status;
|
||||
|
||||
typedef void*
|
||||
LV2_FileBrowser_Files_Handle; ///< Opaque handle for pipedal:uploadPath feature
|
||||
|
||||
|
||||
/**
|
||||
Feature data for filedialog:files (@ref LV2_FILEBROWSER_file).
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
Opaque host data.
|
||||
*/
|
||||
LV2_FileBrowser_Files_Handle handle;
|
||||
|
||||
LV2_FileBrowser_Status (*publish_resource_files)(LV2_FileBrowser_Files_Handle handle,uint32_t version,const char*resourcePath, const char*uploadDirectory);
|
||||
|
||||
/**
|
||||
Return a path in which upload files will be stored for this plugin.
|
||||
@param handle MUST be the `handle` member of this struct.
|
||||
@param path The sub-directory name in which uploaded files will be stored.
|
||||
@return The absolute path to use for the new directory.
|
||||
|
||||
This function can be used by plugins to get the path of files or directories
|
||||
that can be browsed by file dialogs. get_filebrowser_path, unlike map_path() does
|
||||
not create or modify files in any way.
|
||||
|
||||
The caller must free memory for the returned value with LV2_FileBrowser_Files.free_path().
|
||||
|
||||
Example:
|
||||
[code]
|
||||
TBD
|
||||
[/code]
|
||||
*/
|
||||
char* (*get_upload_path)(LV2_FileBrowser_Files_Handle handle, const char* path);
|
||||
|
||||
/**
|
||||
Map resource bundle filenames to upload directories.
|
||||
@param handle MUST be the `handle` member of this struct.
|
||||
@param path File path name
|
||||
@param fileBrowserDirectory relative directory of the file browser directory in which to create a link.
|
||||
@return The absolute pathname of a file in the resource directory.
|
||||
|
||||
The primary purpose of this method is to map file references to files in the plugin's bundle
|
||||
directory to corresponding files in the a file browser directory.
|
||||
|
||||
If the path is relative, the full path is resolved using the `rootResourceDirectory` path.
|
||||
|
||||
If the full path is not a child of rootResourceDirectory or if the path is relative,
|
||||
get_upload_path returns the supplied path unmodified.
|
||||
`
|
||||
If the filename is a child of rootResourceDirectory, creates a corresponding link to the file in the file browser
|
||||
directory and returns the path of that link.
|
||||
|
||||
The caller must free memory for the returned value with `LV2_FileBrowser_Files.free_path()`.
|
||||
|
||||
Example:
|
||||
*/
|
||||
char* (*map_path)(LV2_FileBrowser_Files_Handle handle, const char* path, const char*rootResourceDirectory, const char*fileBrowserDirectory);
|
||||
|
||||
|
||||
/**
|
||||
Free a path returned by LV2_FileBrowser_Files.get_upload_path() or LV2_FileBrowser_Files.map_path()
|
||||
@param handle MUST be the `handle` member of this struct.
|
||||
@param path A path previously returned by a method of `LV2_FileBrowser_Files`.
|
||||
|
||||
*/
|
||||
void (*free_path)(LV2_FileBrowser_Files_Handle handle, char* path);
|
||||
} LV2_FileBrowser_Files;
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // FILEBROWSERFILES_H
|
||||
@@ -30,9 +30,11 @@
|
||||
#include <stdexcept>
|
||||
#include "util.hpp"
|
||||
#include "ofstream_synced.hpp"
|
||||
#include "ModFileTypes.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
using namespace pipedal::implementation;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
namespace pipedal::implementation {
|
||||
@@ -128,7 +130,9 @@ void FileBrowserFilesFeature::Initialize(
|
||||
lv2_log_logger_init(&this->lv2Logger,const_cast<LV2_URID_Map *>(lv2Map),const_cast<LV2_Log_Log *>(lv2Log));
|
||||
|
||||
this->bundleDirectory = bundleDirectory;
|
||||
this->browserDirectory = browserDirectory;
|
||||
this->browserRootDirectory = browserDirectory;
|
||||
this->privateDirectory = browserDirectory / this->bundleDirectory.filename();
|
||||
MakeDirectoryMap(browserDirectory);
|
||||
|
||||
|
||||
featureData.handle = (void *)this;
|
||||
@@ -155,9 +159,73 @@ LV2_FileBrowser_Status FileBrowserFilesFeature::FN_publish_resource_files(LV2_Fi
|
||||
return ((FileBrowserFilesFeature *)handle)->PublishResourceFiles(version, resourcePath, uploadDirectory);
|
||||
}
|
||||
|
||||
|
||||
const char*FileBrowserFilesFeature::GetWellKnownDirectory(const std::string&directory)
|
||||
{
|
||||
if (strcmp(directory.c_str(), "~") == 0) {
|
||||
return privateDirectory.c_str();
|
||||
}
|
||||
const auto &f = g_wellKnownDirectoryMap->find(directory);
|
||||
if (f != g_wellKnownDirectoryMap->end())
|
||||
{
|
||||
return f->second.c_str();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
using WellKnownDirectoryMap = std::map<std::string,std::string>;
|
||||
std::mutex FileBrowserFilesFeature::g_DirectoryMap_mutex;
|
||||
|
||||
std::unique_ptr<WellKnownDirectoryMap> FileBrowserFilesFeature::g_wellKnownDirectoryMap;
|
||||
|
||||
void FileBrowserFilesFeature::MakeDirectoryMap(const std::filesystem::path&rootBrowserDirectory)
|
||||
{
|
||||
std::lock_guard lock { g_DirectoryMap_mutex};
|
||||
|
||||
if (!g_wellKnownDirectoryMap)
|
||||
{
|
||||
g_wellKnownDirectoryMap = std::make_unique<WellKnownDirectoryMap>();
|
||||
for (const auto&modDirectory: ModFileTypes::ModDirectories())
|
||||
{
|
||||
(*g_wellKnownDirectoryMap)[modDirectory.modType] =
|
||||
(rootBrowserDirectory / modDirectory.pipedalPath).string();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
char *FileBrowserFilesFeature::GetUploadPath(const char *fileBrowserPath)
|
||||
{
|
||||
std::filesystem::path result = this->browserDirectory / fileBrowserPath;
|
||||
if (fileBrowserPath == nullptr | fileBrowserPath[0] == '\0')
|
||||
{
|
||||
return strdup("");
|
||||
}
|
||||
std::filesystem::path path {fileBrowserPath};
|
||||
|
||||
auto iter = path.begin();
|
||||
if (iter == path.end())
|
||||
{
|
||||
return strdup("");
|
||||
}
|
||||
std::string firstSegment = iter->string();
|
||||
|
||||
const auto f = (g_wellKnownDirectoryMap)->find(firstSegment);
|
||||
if (f == g_wellKnownDirectoryMap->end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
fs::path result = f->second;
|
||||
|
||||
++iter;
|
||||
while (iter != path.end())
|
||||
{
|
||||
result /= (*iter);
|
||||
++iter;
|
||||
}
|
||||
return strdup(result.c_str());
|
||||
}
|
||||
void FileBrowserFilesFeature::FreePath(char *path)
|
||||
@@ -224,7 +292,7 @@ char *FileBrowserFilesFeature::MapPath(const char *path,const char*resourcePathB
|
||||
} else {
|
||||
targetDirectory = fileBrowserPath;
|
||||
}
|
||||
targetDirectory = this->browserDirectory / targetDirectory;
|
||||
targetDirectory = this->browserRootDirectory / targetDirectory;
|
||||
std::filesystem::create_directories(targetDirectory);
|
||||
|
||||
std::filesystem::path targetPath = targetDirectory / relativePath;
|
||||
@@ -297,7 +365,7 @@ LV2_FileBrowser_Status FileBrowserFilesFeature::PublishResourceFiles(uint32_t ve
|
||||
static std::string VERSION_FILENAME_PREFIX = ".versionInfo.";
|
||||
|
||||
// generate a filename that will allow multiple plugins to install files into the same directory.
|
||||
std::filesystem::path versionFileName = this->browserDirectory / uploadDirectory /
|
||||
std::filesystem::path versionFileName = this->browserRootDirectory / uploadDirectory /
|
||||
(VERSION_FILENAME_PREFIX + GetPluginTag(this->bundleDirectory));
|
||||
|
||||
bool loaded = versionInfo.Load(versionFileName);
|
||||
@@ -325,8 +393,8 @@ LV2_FileBrowser_Status FileBrowserFilesFeature::PublishResourceFiles(uint32_t ve
|
||||
LogError("uploadDirectory must be relative.");
|
||||
return LV2_FileBrowser_Status::LV2_FileBrowser_Status_Err_InvalidParameter;
|
||||
}
|
||||
targetDirectory = this->browserDirectory / targetDirectory;
|
||||
PublishRecursive(versionInfo, sourcePath,sourcePath, this->browserDirectory / uploadDirectory);
|
||||
targetDirectory = this->browserRootDirectory / targetDirectory;
|
||||
PublishRecursive(versionInfo, sourcePath,sourcePath, this->browserRootDirectory / uploadDirectory);
|
||||
versionInfo.Save(versionFileName);
|
||||
} catch (const std::exception & e)
|
||||
{
|
||||
|
||||
@@ -24,10 +24,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FileBrowserFiles.h"
|
||||
#include "lv2ext/pipedal.lv2/ext/fileBrowser.h"
|
||||
#include <filesystem>
|
||||
#include <lv2/core/lv2.h>
|
||||
#include <lv2/log/logger.h>
|
||||
#include <mutex>
|
||||
#include <filesystem>
|
||||
|
||||
|
||||
|
||||
@@ -47,20 +49,32 @@ namespace pipedal {
|
||||
const std::string&browserDirectory);
|
||||
const LV2_Feature*GetFeature() { return &feature; }
|
||||
private:
|
||||
static void MakeDirectoryMap(const std::filesystem::path&rootBrowserDirectory);
|
||||
|
||||
using WellKnownDirectoryMap = std::map<std::string,std::string>;
|
||||
static std::mutex g_DirectoryMap_mutex;
|
||||
static std::unique_ptr<WellKnownDirectoryMap> g_wellKnownDirectoryMap;
|
||||
|
||||
|
||||
LV2_Feature feature;
|
||||
const LV2_URID_Map *lv2Map = nullptr;
|
||||
LV2_Log_Logger lv2Logger = {nullptr,0,0,0,0};
|
||||
|
||||
std::filesystem::path bundleDirectory;
|
||||
std::filesystem::path browserDirectory;
|
||||
std::filesystem::path browserRootDirectory;
|
||||
std::filesystem::path privateDirectory;
|
||||
LV2_FileBrowser_Files featureData;
|
||||
|
||||
std::map<std::string,std::string> wellKnownPaths;
|
||||
|
||||
|
||||
static char* FN_get_upload_path(LV2_FileBrowser_Files_Handle handle, const char* fileBrowserDirectory);
|
||||
static char* FN_map_path(LV2_FileBrowser_Files_Handle handle, const char* path, const char *resourcePathBase,const char*fileBrowserDirectory);
|
||||
static void FN_free_path(LV2_FileBrowser_Files_Handle handle, char* path);
|
||||
static LV2_FileBrowser_Status FN_publish_resource_files(LV2_FileBrowser_Files_Handle handle,uint32_t version,const char*resourcePath, const char*uploadDirectory);
|
||||
|
||||
const char*GetWellKnownDirectory(const std::string&directory);
|
||||
|
||||
void LogError(const char*message);
|
||||
char* GetUploadPath(const char* fileBrowserDirectory);
|
||||
char* MapPath(const char* path,const char*resourcePathBase,const char* fileBrowserDirectory);
|
||||
|
||||
+10
-10
@@ -136,7 +136,7 @@ static bool IsIpv4OnLocalSubnet(uint32_t ipv4Addres)
|
||||
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{ // TODO: Add support for AF_INET6
|
||||
uint32_t netmask = htonl(((sockaddr_in *)(p->ifa_netmask))->sin_addr.s_addr);
|
||||
uint32_t ifAddr = htonl(((sockaddr_in *)(p->ifa_addr))->sin_addr.s_addr);
|
||||
@@ -310,7 +310,7 @@ bool pipedal::IsOnLocalSubnet(const std::string &fromAddress)
|
||||
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
if (p->ifa_addr && p->ifa_addr && p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{
|
||||
|
||||
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
|
||||
@@ -353,7 +353,7 @@ std::vector<std::string> pipedal::GetEthernetIpv4Addresses()
|
||||
{
|
||||
if (isEthernetAddress(p->ifa_name))
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
uint32_t netmask = htonl(((sockaddr_in *)(p->ifa_netmask))->sin_addr.s_addr);
|
||||
uint32_t ifAddr = htonl(((sockaddr_in *)(p->ifa_addr))->sin_addr.s_addr);
|
||||
{
|
||||
@@ -378,7 +378,7 @@ static std::string GetInterfaceForIp4Address(uint32_t ipv4Address)
|
||||
std::string result;
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{ // TODO: Add support for AF_INET6
|
||||
uint32_t netmask = htonl(((sockaddr_in *)(p->ifa_netmask))->sin_addr.s_addr);
|
||||
uint32_t ifAddr = htonl(((sockaddr_in *)(p->ifa_addr))->sin_addr.s_addr);
|
||||
@@ -402,7 +402,7 @@ static std::string GetInterfaceForIp6Address(const in6_addr inetAddr6)
|
||||
std::string result;
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{ // TODO: Add support for AF_INET6
|
||||
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
|
||||
struct sockaddr_in6 *pNetMask = (struct sockaddr_in6 *)(p->ifa_netmask);
|
||||
@@ -441,7 +441,7 @@ static std::string GetNonLinkLocalAddress(const boost::asio::ip::address_v6 &ipV
|
||||
// get the nameof the interface for this scopeId.
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr)
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr)
|
||||
{ // TODO: Add support for AF_INET6
|
||||
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
|
||||
if (pAddr->sin6_scope_id == scopeId)
|
||||
@@ -459,7 +459,7 @@ static std::string GetNonLinkLocalAddress(const boost::asio::ip::address_v6 &ipV
|
||||
// an IPV4 address would be the ideal result.
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{
|
||||
if (strcmp(p->ifa_name, targetInterfaceName) == 0)
|
||||
{
|
||||
@@ -503,7 +503,7 @@ static std::string GetNonLinkLocalAddressForInterface(const std::string &name)
|
||||
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET6 && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{ // TODO: Add support for AF_INET6
|
||||
struct sockaddr_in6 *pAddr = (struct sockaddr_in6 *)(p->ifa_addr);
|
||||
if (!IN6_IS_ADDR_LINKLOCAL(&(pAddr->sin6_addr)))
|
||||
@@ -547,8 +547,8 @@ static std::string GetNonLinkLocalAddressForIp4Interface(const std::string &name
|
||||
std::string result = "";
|
||||
for (ifaddrs *p = ifap; p != nullptr; p = p->ifa_next)
|
||||
{
|
||||
if (p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{ // TODO: Add support for AF_INET6
|
||||
if (p->ifa_addr && p->ifa_addr->sa_family == AF_INET && p->ifa_addr != nullptr && p->ifa_netmask != nullptr)
|
||||
{
|
||||
struct sockaddr_in *pAddr = (struct sockaddr_in *)(p->ifa_addr);
|
||||
{
|
||||
if (name == p->ifa_name)
|
||||
|
||||
+116
-88
@@ -20,99 +20,22 @@
|
||||
|
||||
#include "MimeTypes.hpp"
|
||||
#include "ss.hpp"
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
void MimeTypes::MaybeInitialize()
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
initialized = true;
|
||||
AddMimeType("MP3", "audio/mpeg");
|
||||
AddMimeType("MPGA", "audio/mpeg");
|
||||
AddMimeType("M4A", "audio/mp4");
|
||||
AddMimeType("WAV", "audio/x-wav");
|
||||
AddMimeType("WAV", "audio/wav");
|
||||
AddMimeType("AMR", "audio/amr");
|
||||
AddMimeType("AWB", "audio/amr-wb");
|
||||
AddMimeType("WMA", "audio/x-ms-wma");
|
||||
AddMimeType("OGG", "audio/ogg");
|
||||
AddMimeType("OGG", "application/ogg");
|
||||
AddMimeType("OGA", "application/ogg");
|
||||
AddMimeType("AAC", "audio/aac");
|
||||
AddMimeType("AAC", "audio/aac-adts");
|
||||
AddMimeType("MKA", "audio/x-matroska");
|
||||
AddMimeType("MID", "audio/midi");
|
||||
AddMimeType("MIDI", "audio/midi");
|
||||
AddMimeType("XMF", "audio/midi");
|
||||
AddMimeType("RTTTL", "audio/midi");
|
||||
AddMimeType("SMF", "audio/sp-midi");
|
||||
AddMimeType("IMY", "audio/imelody");
|
||||
AddMimeType("RTX", "audio/midi");
|
||||
AddMimeType("OTA", "audio/midi");
|
||||
AddMimeType("MXMF", "audio/midi");
|
||||
|
||||
AddMimeType("MPEG", "video/mpeg");
|
||||
AddMimeType("MPG", "video/mpeg");
|
||||
AddMimeType("MP4", "video/mp4");
|
||||
AddMimeType("M4V", "video/mp4");
|
||||
AddMimeType("3GP", "video/3gpp");
|
||||
AddMimeType("3GPP", "video/3gpp");
|
||||
AddMimeType("3G2", "video/3gpp2");
|
||||
AddMimeType("3GPP2", "video/3gpp2");
|
||||
AddMimeType("MKV", "video/x-matroska");
|
||||
AddMimeType("WEBM", "video/webm");
|
||||
AddMimeType("TS", "video/mp2ts");
|
||||
AddMimeType("AVI", "video/avi");
|
||||
AddMimeType("WMV", "video/x-ms-wmv");
|
||||
AddMimeType("ASF", "video/x-ms-asf");
|
||||
AddMimeType("JPG", "image/jpeg");
|
||||
AddMimeType("JPEG", "image/jpeg");
|
||||
AddMimeType("GIF", "image/gif");
|
||||
AddMimeType("PNG", "image/png");
|
||||
AddMimeType("BMP", "image/x-ms-bmp");
|
||||
AddMimeType("WBMP", "image/vnd.wap.wbmp");
|
||||
AddMimeType("WEBP", "image/webp");
|
||||
|
||||
AddMimeType("M3U", "audio/x-mpegurl");
|
||||
AddMimeType("M3U", "application/x-mpegurl");
|
||||
AddMimeType("PLS", "audio/x-scpls");
|
||||
AddMimeType("WPL", "application/vnd.ms-wpl");
|
||||
AddMimeType("M3U8", "application/vnd.apple.mpegurl");
|
||||
AddMimeType("M3U8", "audio/mpegurl");
|
||||
AddMimeType("M3U8", "audio/x-mpegurl");
|
||||
AddMimeType("FL", "application/x-android-drm-fl");
|
||||
AddMimeType("TXT", "text/plain");
|
||||
AddMimeType("HTM", "text/html");
|
||||
AddMimeType("HTML", "text/html");
|
||||
AddMimeType("PDF", "application/pdf");
|
||||
AddMimeType("DOC", "application/msword");
|
||||
AddMimeType("XLS", "application/vnd.ms-excel");
|
||||
AddMimeType("PPT", "application/mspowerpoint");
|
||||
AddMimeType("FLAC", "audio/x-flac");
|
||||
AddMimeType("FLAC", "audio/flac");
|
||||
AddMimeType("ZIP", "application/zip");
|
||||
AddMimeType("MPG", "video/mp2p");
|
||||
AddMimeType("MPEG", "video/mp2p");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static MimeTypes staticContruct;
|
||||
|
||||
static std::string empty;
|
||||
|
||||
const std::string& MimeTypes::MimeTypeFromExtension(const std::string &extension)
|
||||
const std::string& MimeTypes::MimeTypeFromExtension(const std::string &extension) const
|
||||
{
|
||||
MaybeInitialize();
|
||||
auto iter = extensionToMimeType.find(extension);
|
||||
if (iter == extensionToMimeType.end()) return empty;
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
const std::string& MimeTypes::ExtensionFromMimeType(const std::string &mimeType)
|
||||
const std::string& MimeTypes::ExtensionFromMimeType(const std::string &mimeType) const
|
||||
{
|
||||
MaybeInitialize();
|
||||
auto iter = mimeTypeToExtension.find(mimeType);
|
||||
if (iter == mimeTypeToExtension.end()) return empty;
|
||||
return iter->second;
|
||||
@@ -135,27 +58,132 @@ static std::string toLower(const std::string&value)
|
||||
return result;
|
||||
}
|
||||
|
||||
void MimeTypes::AddMimeType(const std::string&extension_, const std::string&mimeType)
|
||||
void MimeTypes::AddMimeType(const std::string&extension_, const std::string&mimeType)
|
||||
{
|
||||
std::string extension = SS("." << toLower(extension_));
|
||||
mimeTypeToExtensions[mimeType].insert(extension);
|
||||
mimeTypeToExtension[mimeType] = extension;
|
||||
extensionToMimeType[extension] = mimeType;
|
||||
if (mimeType.starts_with("audio/"))
|
||||
if (mimeType.starts_with("audio/midi"))
|
||||
{
|
||||
midiExtensions.insert(extension);
|
||||
} else if (mimeType.starts_with("audio/"))
|
||||
{
|
||||
mimeTypeToExtensions["audio/*"].insert(extension);
|
||||
audioExtensions.insert(extension);
|
||||
}
|
||||
if (mimeType.starts_with("video/"))
|
||||
{
|
||||
mimeTypeToExtensions["video/*"].insert(extension);
|
||||
videoExtensions.insert(extension);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::map<std::string,std::string> MimeTypes::mimeTypeToExtension;
|
||||
std::map<std::string,std::string> MimeTypes::extensionToMimeType;
|
||||
std::set<std::string> MimeTypes::audioExtensions;
|
||||
std::set<std::string> MimeTypes::videoExtensions;
|
||||
bool MimeTypes::initialized;
|
||||
const std::set<std::string> &MimeTypes::AudioExtensions() const
|
||||
{
|
||||
return audioExtensions;
|
||||
|
||||
}
|
||||
const std::set<std::string> &MimeTypes::VideoExtensions() const
|
||||
{
|
||||
return videoExtensions;
|
||||
}
|
||||
const std::set<std::string> &MimeTypes::MidiExtensions() const
|
||||
{
|
||||
return midiExtensions;
|
||||
}
|
||||
|
||||
static std::mutex m_instanceMutex;
|
||||
static std::unique_ptr<MimeTypes> m_instance;
|
||||
|
||||
|
||||
MimeTypes::MimeTypes()
|
||||
{
|
||||
AddMimeType("MP3", "audio/mpeg");
|
||||
AddMimeType("MPGA", "audio/mpeg");
|
||||
AddMimeType("M4A", "audio/mp4");
|
||||
AddMimeType("WAV", "audio/x-wav");
|
||||
AddMimeType("WAV", "audio/wav");
|
||||
AddMimeType("AMR", "audio/amr");
|
||||
AddMimeType("AWB", "audio/amr-wb");
|
||||
AddMimeType("WMA", "audio/x-ms-wma");
|
||||
AddMimeType("OGG", "audio/ogg");
|
||||
AddMimeType("OGG", "application/ogg");
|
||||
AddMimeType("OGA", "application/ogg");
|
||||
AddMimeType("AAC", "audio/aac");
|
||||
AddMimeType("AAC", "audio/aac-adts");
|
||||
AddMimeType("MKA", "audio/x-matroska");
|
||||
AddMimeType("MID", "audio/midi");
|
||||
AddMimeType("MIDI", "audio/midi");
|
||||
AddMimeType("XMF", "audio/midi");
|
||||
AddMimeType("RTTTL", "audio/midi");
|
||||
AddMimeType("SMF", "audio/sp-midi");
|
||||
AddMimeType("IMY", "audio/imelody");
|
||||
AddMimeType("RTX", "audio/midi");
|
||||
AddMimeType("OTA", "audio/midi");
|
||||
AddMimeType("MXMF", "audio/midi");
|
||||
|
||||
AddMimeType("MPEG", "video/mpeg");
|
||||
AddMimeType("MPG", "video/mpeg");
|
||||
AddMimeType("MP4", "video/mp4");
|
||||
AddMimeType("M4V", "video/mp4");
|
||||
AddMimeType("3GP", "video/3gpp");
|
||||
AddMimeType("3GPP", "video/3gpp");
|
||||
AddMimeType("3G2", "video/3gpp2");
|
||||
AddMimeType("3GPP2", "video/3gpp2");
|
||||
AddMimeType("MKV", "video/x-matroska");
|
||||
AddMimeType("WEBM", "video/webm");
|
||||
AddMimeType("TS", "video/mp2ts");
|
||||
AddMimeType("AVI", "video/avi");
|
||||
AddMimeType("WMV", "video/x-ms-wmv");
|
||||
AddMimeType("ASF", "video/x-ms-asf");
|
||||
AddMimeType("JPG", "image/jpeg");
|
||||
AddMimeType("JPEG", "image/jpeg");
|
||||
AddMimeType("GIF", "image/gif");
|
||||
AddMimeType("PNG", "image/png");
|
||||
AddMimeType("BMP", "image/x-ms-bmp");
|
||||
AddMimeType("WBMP", "image/vnd.wap.wbmp");
|
||||
AddMimeType("WEBP", "image/webp");
|
||||
|
||||
AddMimeType("M3U", "audio/x-mpegurl");
|
||||
AddMimeType("M3U", "application/x-mpegurl");
|
||||
AddMimeType("PLS", "audio/x-scpls");
|
||||
AddMimeType("WPL", "application/vnd.ms-wpl");
|
||||
AddMimeType("M3U8", "application/vnd.apple.mpegurl");
|
||||
AddMimeType("M3U8", "audio/mpegurl");
|
||||
AddMimeType("M3U8", "audio/x-mpegurl");
|
||||
AddMimeType("FL", "application/x-android-drm-fl");
|
||||
AddMimeType("TXT", "text/plain");
|
||||
AddMimeType("HTM", "text/html");
|
||||
AddMimeType("HTML", "text/html");
|
||||
AddMimeType("PDF", "application/pdf");
|
||||
AddMimeType("DOC", "application/msword");
|
||||
AddMimeType("XLS", "application/vnd.ms-excel");
|
||||
AddMimeType("PPT", "application/mspowerpoint");
|
||||
AddMimeType("FLAC", "audio/x-flac");
|
||||
AddMimeType("FLAC", "audio/flac");
|
||||
AddMimeType("ZIP", "application/zip");
|
||||
AddMimeType("MPG", "video/mp2p");
|
||||
AddMimeType("MPEG", "video/mp2p");
|
||||
|
||||
}
|
||||
|
||||
const MimeTypes&MimeTypes::instance()
|
||||
{
|
||||
std::lock_guard lock { m_instanceMutex};
|
||||
if (!m_instance) {
|
||||
m_instance = std::unique_ptr<MimeTypes>(new MimeTypes());
|
||||
}
|
||||
return (*m_instance);
|
||||
}
|
||||
|
||||
const bool MimeTypes::IsValidExtension(const std::string&mimeType, const std::string&extension) const
|
||||
{
|
||||
auto iter = mimeTypeToExtensions.find(mimeType);
|
||||
if (iter == mimeTypeToExtensions.end()) return false;
|
||||
return iter->second.contains(extension);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
+20
-11
@@ -25,17 +25,26 @@
|
||||
|
||||
namespace pipedal {
|
||||
class MimeTypes {
|
||||
public:
|
||||
|
||||
static const std::string& MimeTypeFromExtension(const std::string &extension);
|
||||
static const std::string& ExtensionFromMimeType(const std::string &mimeType);
|
||||
private:
|
||||
static void AddMimeType(const std::string&extension, const std::string&mimeType);
|
||||
static std::map<std::string,std::string> mimeTypeToExtension;
|
||||
static std::map<std::string,std::string> extensionToMimeType;
|
||||
static std::set<std::string> audioExtensions;
|
||||
static std::set<std::string> videoExtensions;
|
||||
static void MaybeInitialize();
|
||||
static bool initialized;
|
||||
MimeTypes();
|
||||
public:
|
||||
static const MimeTypes&instance();
|
||||
|
||||
const std::string& MimeTypeFromExtension(const std::string &extension) const;
|
||||
// the best possible extension if we had to pick one.
|
||||
const std::string& ExtensionFromMimeType(const std::string &mimeType) const;
|
||||
// All possible extensions that other people might have picked.
|
||||
const std::set<std::string> &AudioExtensions() const;
|
||||
const std::set<std::string> &VideoExtensions() const;
|
||||
const std::set<std::string> &MidiExtensions() const;
|
||||
const bool IsValidExtension(const std::string&mimeType, const std::string&extension) const;
|
||||
private:
|
||||
void AddMimeType(const std::string&extension, const std::string&mimeType);
|
||||
std::map<std::string,std::set<std::string>> mimeTypeToExtensions;
|
||||
std::map<std::string,std::string> mimeTypeToExtension;
|
||||
std::map<std::string,std::string> extensionToMimeType;
|
||||
std::set<std::string> audioExtensions;
|
||||
std::set<std::string> midiExtensions;
|
||||
std::set<std::string> videoExtensions;
|
||||
};
|
||||
}
|
||||
+72
-49
@@ -22,38 +22,61 @@
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include "ss.hpp"
|
||||
#include "MimeTypes.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
const std::vector<ModFileTypes::ModDirectory> ModFileTypes::ModDirectories =
|
||||
|
||||
static std::mutex gModDirectoriesMutex;
|
||||
static std::unique_ptr< std::vector<ModFileTypes::ModDirectory> > gModDirectories;
|
||||
|
||||
|
||||
static std::vector<ModFileTypes::ModDirectory> *CreateModDirectories()
|
||||
{
|
||||
return new std::vector<ModFileTypes::ModDirectory>(
|
||||
{
|
||||
{"audioloop", "shared/audio/Loops", "Loops", {"audio/*"}}, // Audio Loops, meant to be used for looper-style plugins
|
||||
//"audiorecording","shared/audio/Audio Recordings", {"audio/*"}}, : Audio Recordings, triggered by plugins and stored in the unit
|
||||
{"audiorecording","shared/audio/Audio Recordings", "Audio Recordings",{"audio/*"}}, // Audio Recordings, triggered by plugins and stored in the unit
|
||||
{"audiosample", "shared/audio/Samples", "Samples", {"audio/*"}}, // One-shot Audio Samples, meant to be used for sampler-style plugins
|
||||
{"audiotrack", "shared/audio/Tracks", "Tracks", {"audio/*"}}, // Audio Tracks, meant to be used as full-performance/song or backtrack
|
||||
{"cabsim", "CabIR", "Cab IRs", {"audio/*"}}, // Speaker Cabinets, meant as small IR audio files
|
||||
|
||||
/// - h2drumkit: Hydrogen Drumkits, must use h2drumkit file extension
|
||||
{"h2drumkit", "shared/h2drumkit", "Hydrogen Drumkits",{".h2drumkit"}}, // Hydrogen Drumkits, must use h2drumkit file extension"
|
||||
{"ir", "ReverbImpulseFiles", "Impulse Responses", {"audio/*"}}, // Impulse Responses
|
||||
{"midiclip", "shared/midiClips", "MIDI Clips", {".mid", ".midi"}}, // MIDI Clips, to be used in sync with host tempo, must have mid or midi file extension
|
||||
{"midisong", "shared/midiSongs", "MIDI Songs", {".mid", ".midi"}}, // MIDI Songs, meant to be used as full-performance/song or backtrack
|
||||
{"sf2", "shared/sf2", "Sound Fonts", {"sf2", "sf3"}}, // SF2 Instruments, must have sf2 or sf3 file extension
|
||||
{"sfz", "shared/sfz", "Sfz Files", {"sfz"}}, // SFZ Instruments, must have sfz file extension
|
||||
{"sf2", "shared/sf2", "Sound Fonts", {".sf2", ".sf3"}}, // SF2 Instruments, must have sf2 or sf3 file extension
|
||||
{"sfz", "shared/sfz", "Sfz Files", {".sfz"}}, // SFZ Instruments, must have sfz file extension
|
||||
// extensions observed in the field.
|
||||
{"audio", "shared/audio", "Audio", {"audio/*"}}, // all audio files (Ratatoille)
|
||||
{"nammodel", "NeuralAmpModels", "Neural Amp Models", {".nam"}}, // Ratatoille, Mike's NAM.
|
||||
{"aidadspmodel", "shared/aidaaix", "AIDA IAX Models", {".json", ".aidaiax"}}, // Ratatoille
|
||||
{"audio", "shared/audio", "Audio", {"audio/*"}}, // all audio files (Ratatouille)
|
||||
{"nammodel", "NeuralAmpModels", "Neural Amp Models", {".nam"}}, // Ratatouille, Mike's NAM.
|
||||
{"aidadspmodel", "shared/aidaaix", "AIDA IAX Models", {".json", ".aidaiax"}}, // Ratatouille
|
||||
{"mlmodel", "ToobMlModels", "ML Models", {".json"}}, //
|
||||
|
||||
// pipedal-specific types. use pipedal_ui:filetypes instead (or in addition to) mod:fileTypes.
|
||||
{"recording", "shared/audio/Recordings", "Recordings", {"audio/*"}}, // Recordings from recordings plugins.
|
||||
|
||||
//
|
||||
};
|
||||
// For plugins that don't secify a modDirectory (allows upload of arbitrary extension)
|
||||
{"*", "shared/other", "Other", {".*"}}, //
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
const std::vector<ModFileTypes::ModDirectory>&ModFileTypes::ModDirectories()
|
||||
{
|
||||
std::lock_guard lock { gModDirectoriesMutex };
|
||||
|
||||
if (!gModDirectories)
|
||||
{
|
||||
gModDirectories = std::unique_ptr<std::vector<ModFileTypes::ModDirectory>>(CreateModDirectories());
|
||||
}
|
||||
return *gModDirectories;
|
||||
}
|
||||
|
||||
|
||||
const ModFileTypes::ModDirectory *ModFileTypes::GetModDirectory(const std::string &type)
|
||||
{
|
||||
for (const ModFileTypes::ModDirectory &modType : ModFileTypes::ModDirectories)
|
||||
for (const ModFileTypes::ModDirectory &modType : ModFileTypes::ModDirectories())
|
||||
{
|
||||
if (modType.modType == type)
|
||||
{
|
||||
@@ -78,41 +101,6 @@ ModFileTypes::ModFileTypes(const std::string &fileTypes)
|
||||
fileTypes_.push_back(type);
|
||||
}
|
||||
}
|
||||
// for Ratatoille.lv2.
|
||||
// If rootDirectories contains "nammodel" and fileTypes contains "json", add "mlmodel" too.
|
||||
// if (contains(rootDirectories_,"nammodel") && contains(fileTypes_,"json"))
|
||||
// {
|
||||
// if (!contains(rootDirectories_,"mlmodel"))
|
||||
// {
|
||||
// rootDirectories_.push_back("mlmodel");
|
||||
// }
|
||||
// }
|
||||
if (fileTypes_.empty())
|
||||
{
|
||||
for (const auto &type : types)
|
||||
{
|
||||
const ModDirectory *wellKnownType = GetModDirectory(type);
|
||||
if (wellKnownType)
|
||||
{
|
||||
for (const std::string &newType : wellKnownType->defaultFileExtensions)
|
||||
{
|
||||
bool found = false;
|
||||
for (const std::string &oldType : fileTypes_)
|
||||
{
|
||||
if (newType == oldType)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
fileTypes_.push_back(newType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::filesystem::path getModDirectoryPath(
|
||||
@@ -132,7 +120,7 @@ void ModFileTypes::CreateDefaultDirectories(const std::filesystem::path &rootDir
|
||||
using namespace std;
|
||||
try
|
||||
{
|
||||
for (const auto &modType : ModDirectories)
|
||||
for (const auto &modType : ModDirectories())
|
||||
{
|
||||
fs::path path = rootDirectory / modType.pipedalPath;
|
||||
|
||||
@@ -158,3 +146,38 @@ void ModFileTypes::CreateDefaultDirectories(const std::filesystem::path &rootDir
|
||||
}
|
||||
}
|
||||
|
||||
static std::set<std::string> makeFileExtensions(const std::vector<std::string>&fileTypes)
|
||||
{
|
||||
std::set<std::string> result;
|
||||
|
||||
for (const std::string&fileType: fileTypes)
|
||||
{
|
||||
if (fileType.starts_with('.')) {
|
||||
result.insert(fileType);
|
||||
} else {
|
||||
if (fileType == "audio/*")
|
||||
{
|
||||
const auto&audioExtensions = MimeTypes::instance().AudioExtensions();
|
||||
result.insert(audioExtensions.begin(),audioExtensions.end());
|
||||
} else if (fileType == "video/*")
|
||||
{
|
||||
const auto&videoExtensions = MimeTypes::instance().VideoExtensions();
|
||||
result.insert(videoExtensions.begin(),videoExtensions.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ModFileTypes::ModDirectory::ModDirectory(
|
||||
const std::string modType,
|
||||
const std::string pipedalPath,
|
||||
const std::string displayName,
|
||||
const std::vector<std::string> fileTypes)
|
||||
:modType(modType),
|
||||
pipedalPath(pipedalPath),
|
||||
displayName(displayName),
|
||||
fileTypesX(fileTypes),
|
||||
fileExtensions(makeFileExtensions(fileTypes))
|
||||
{
|
||||
}
|
||||
|
||||
+26
-14
@@ -21,33 +21,45 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
|
||||
namespace pipedal {
|
||||
class ModFileTypes {
|
||||
namespace pipedal
|
||||
{
|
||||
class ModFileTypes
|
||||
{
|
||||
public:
|
||||
|
||||
public:
|
||||
ModFileTypes(const std::string&fileTypes);
|
||||
const std::vector<std::string> &rootDirectories() { return rootDirectories_; }
|
||||
const std::vector<std::string> &fileTypes() { return fileTypes_;}
|
||||
ModFileTypes(const std::string &fileTypes);
|
||||
const std::vector<std::string> &rootDirectories() const { return rootDirectories_; }
|
||||
std::vector<std::string> &rootDirectories() { return rootDirectories_; }
|
||||
const std::vector<std::string> &fileTypes() const { return fileTypes_; }
|
||||
|
||||
static constexpr const char *DEFAULT_FILE_TYPES = "audio,nammodel,mlmodel,sf2,sfz,midisong,midiclip,*"; // (everything)
|
||||
|
||||
private:
|
||||
std::vector<std::string> rootDirectories_;
|
||||
std::vector<std::string> fileTypes_;
|
||||
|
||||
|
||||
public:
|
||||
class ModDirectory {
|
||||
class ModDirectory
|
||||
{
|
||||
public:
|
||||
ModDirectory(
|
||||
const std::string modType,
|
||||
const std::string pipedalPath,
|
||||
const std::string displayName,
|
||||
const std::vector<std::string> defaultFileExtensions);
|
||||
const std::string modType;
|
||||
const std::string pipedalPath;
|
||||
const std::filesystem::path pipedalPath;
|
||||
const std::string displayName;
|
||||
const std::vector<std::string> defaultFileExtensions;
|
||||
const std::vector<std::string> fileTypesX; // mixture of .ext and mime types. Use fileExtensions instead.
|
||||
const std::set<std::string> fileExtensions;
|
||||
|
||||
};
|
||||
|
||||
const static std::vector<ModDirectory> ModDirectories;
|
||||
static const ModDirectory* GetModDirectory(const std::string&modType);
|
||||
|
||||
static void CreateDefaultDirectories(const std::filesystem::path&rootDirectory);
|
||||
static const std::vector<ModDirectory> &ModDirectories();
|
||||
static const ModDirectory *GetModDirectory(const std::string &modType);
|
||||
|
||||
static void CreateDefaultDirectories(const std::filesystem::path &rootDirectory);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) 2022 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 <string>
|
||||
#include "PiPedalUI.hpp"
|
||||
#include "ModFileTypes.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
std::set<std::string> CalculateFileExtensions(const std::string &mod__fileTypes, const std::string &modType)
|
||||
{
|
||||
ModFileTypes modFileTypes(mod__fileTypes);
|
||||
|
||||
auto fileProperty =
|
||||
std::make_shared<UiFileProperty>(
|
||||
"Test", "urn:test", modFileTypes);
|
||||
|
||||
return fileProperty->GetPermittedFileExtensions(modType);
|
||||
}
|
||||
|
||||
bool checkFile(const std::string &mod__FileTypes,const std::filesystem::path&path)
|
||||
{
|
||||
ModFileTypes modFileTypes(mod__FileTypes);
|
||||
|
||||
auto fileProperty =
|
||||
std::make_shared<UiFileProperty>(
|
||||
"Test", "urn:test", modFileTypes);
|
||||
|
||||
auto fileModDirectory = fileProperty->getParentModDirectory(path);
|
||||
return fileProperty->IsValidExtension(path);
|
||||
}
|
||||
|
||||
TEST_CASE("ModFileTypes Test", "[modFileTypes]")
|
||||
{
|
||||
|
||||
REQUIRE(checkFile("audio","shared/audio/Test.wav"));
|
||||
REQUIRE(checkFile("audio","shared/audio/xxx/Test.flac"));
|
||||
REQUIRE(!checkFile("audio","shared/audio/xxx/Test.nam"));
|
||||
|
||||
REQUIRE(checkFile("audio,wav","shared/audio/Test.wav"));
|
||||
REQUIRE(!checkFile("audio,wav","shared/audio/xxx/Test.flac"));
|
||||
REQUIRE(!checkFile("audio","shared/audio/xxx/Test.nam"));
|
||||
|
||||
REQUIRE(checkFile("cabsim,wav","CabIR/Test.wav"));
|
||||
REQUIRE(!checkFile("cabsim,wav","CabIR/xxx/Test.flac"));
|
||||
REQUIRE(!checkFile("cabsim","CabIR/xxx/Test.nam"));
|
||||
|
||||
|
||||
|
||||
REQUIRE(checkFile("audio,wav","shared/audio/Test.wav"));
|
||||
REQUIRE(!checkFile("audio,wav","shared/audio/xxx/Test.flac"));
|
||||
REQUIRE(!checkFile("audio","shared/audio/xxx/Test.nam"));
|
||||
|
||||
|
||||
REQUIRE(checkFile("wav","shared/audio/Test.wav"));
|
||||
REQUIRE(!checkFile("wav","CabIR/Test.flac")); // only one audio directory for this case.
|
||||
REQUIRE(!checkFile("wav","shared/audio/xxx/Test.nam"));
|
||||
REQUIRE(!checkFile("wav","shared/midiSongs/xxx/Test.mid"));
|
||||
REQUIRE(!checkFile("wav","shared/other/xxx/Test.mid"));
|
||||
|
||||
|
||||
REQUIRE(checkFile("mid","shared/midiClips/Test.mid"));
|
||||
REQUIRE(checkFile("mid","shared/midiSongs/Test.mid"));
|
||||
REQUIRE(!checkFile("mid","CabIR/Test.flac")); // only one audio directory for this case.
|
||||
|
||||
|
||||
REQUIRE(checkFile("midisong","shared/midiSongs/Test.mid"));
|
||||
REQUIRE(checkFile("midisong","shared/midiSongs/xxx/Test.midi"));
|
||||
REQUIRE(!checkFile("midisong","shared/midiClips/xxx/Test.nam"));
|
||||
|
||||
|
||||
REQUIRE(checkFile("","shared/audio/Test.wav"));
|
||||
REQUIRE(checkFile("","shared/audio/xxx/Test.flac"));
|
||||
REQUIRE(!checkFile("","shared/audio/xxx/Test.nam"));
|
||||
REQUIRE(!checkFile("","ToobMlModels/Test.nam"));
|
||||
REQUIRE(checkFile("","ToobMlModels/Test.json"));
|
||||
REQUIRE(checkFile("","ToobMlModels/a/b/c/Test.json"));
|
||||
REQUIRE(checkFile("","shared/other/xxx/Test.xwav"));
|
||||
REQUIRE(checkFile("","shared/other/xxx/Test"));
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
std::set<std::string> result =
|
||||
CalculateFileExtensions("audio", "audio");
|
||||
|
||||
REQUIRE(result.contains(".wav"));
|
||||
REQUIRE(result.contains(".flac"));
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
std::set<std::string> result =
|
||||
CalculateFileExtensions("wav", "audio");
|
||||
|
||||
REQUIRE(result.size() == 1);
|
||||
REQUIRE(result.contains(".wav"));
|
||||
REQUIRE(!result.contains(".flac"));
|
||||
}
|
||||
{
|
||||
std::set<std::string> result =
|
||||
CalculateFileExtensions("nam", "audio");
|
||||
|
||||
REQUIRE(result.size() == 0);
|
||||
REQUIRE(!result.contains(".flac"));
|
||||
REQUIRE(!result.contains(".nam"));
|
||||
}
|
||||
{
|
||||
// extensions specified, but no modDirectory.
|
||||
ModFileTypes modFileTypes("mid,midi");
|
||||
|
||||
auto fileProperty =
|
||||
std::make_shared<UiFileProperty>(
|
||||
"Test", "urn:test", modFileTypes);
|
||||
|
||||
REQUIRE(fileProperty->modDirectories().size() == 2);
|
||||
for (const auto&modDirectory: fileProperty->modDirectories())
|
||||
{
|
||||
auto fileExtensions = fileProperty->GetPermittedFileExtensions(modDirectory);
|
||||
REQUIRE(fileExtensions.size() == 2);
|
||||
REQUIRE(fileExtensions.contains(".mid"));
|
||||
REQUIRE(fileExtensions.contains(".midi"));
|
||||
}
|
||||
}
|
||||
{
|
||||
// Custom directory, custom extensions.
|
||||
// actually simulating a PiPedalUI::UiFileProperty load from ttl.
|
||||
ModFileTypes modFileTypes("wavx");
|
||||
|
||||
modFileTypes.rootDirectories().push_back("CustomDirectory");
|
||||
|
||||
auto fileProperty =
|
||||
std::make_shared<UiFileProperty>(
|
||||
"Test", "urn:test", modFileTypes);
|
||||
|
||||
REQUIRE(fileProperty->modDirectories().size() == 1);
|
||||
auto fileExtensions = fileProperty->GetPermittedFileExtensions("CustomDirectory");
|
||||
|
||||
REQUIRE(fileProperty->modDirectories().size() == 1);
|
||||
REQUIRE(fileExtensions.size() == 1);
|
||||
REQUIRE(fileExtensions.contains(".wavx"));
|
||||
|
||||
}
|
||||
}
|
||||
+33
-16
@@ -2309,18 +2309,6 @@ void PiPedalModel::SetSystemMidiBindings(std::vector<MidiBinding> &bindings)
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> PiPedalModel::GetFileList(const UiFileProperty &fileProperty)
|
||||
{
|
||||
try
|
||||
{
|
||||
return this->storage.GetFileList(fileProperty);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::warning("GetFileList() failed: (%s)", e.what());
|
||||
return std::vector<std::string>(); // don't disclose to users what the problem is.
|
||||
}
|
||||
}
|
||||
FileRequestResult PiPedalModel::GetFileList2(const std::string &relativePath, const UiFileProperty &fileProperty)
|
||||
{
|
||||
try
|
||||
@@ -2354,15 +2342,44 @@ std::string PiPedalModel::CreateNewSampleDirectory(const std::string &relativePa
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
return storage.CreateNewSampleDirectory(relativePath, uiFileProperty);
|
||||
}
|
||||
FilePropertyDirectoryTree::ptr PiPedalModel::GetFilePropertydirectoryTree(const UiFileProperty &uiFileProperty)
|
||||
FilePropertyDirectoryTree::ptr PiPedalModel::GetFilePropertydirectoryTree(const UiFileProperty &uiFileProperty,const std::string&selectedPath)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex);
|
||||
return storage.GetFilePropertydirectoryTree(uiFileProperty);
|
||||
return storage.GetFilePropertydirectoryTree(uiFileProperty,selectedPath);
|
||||
}
|
||||
|
||||
std::string PiPedalModel::UploadUserFile(const std::string &directory, const std::string &patchProperty, const std::string &filename, std::istream &stream, size_t contentLength)
|
||||
UiFileProperty::ptr PiPedalModel::FindLoadedPatchProperty(int64_t instanceId,const std::string&patchPropertyUri)
|
||||
{
|
||||
return storage.UploadUserFile(directory, patchProperty, filename, stream, contentLength);
|
||||
|
||||
auto pedalboardItems = pedalboard.GetAllPlugins();
|
||||
|
||||
for (const auto&pedalboardItem: pedalboardItems) {
|
||||
if (pedalboardItem->instanceId() == instanceId)
|
||||
{
|
||||
Lv2PluginInfo::ptr pluginInfo = GetPluginInfo(pedalboardItem->uri());
|
||||
if (pluginInfo && pluginInfo->piPedalUI())
|
||||
{
|
||||
for (const auto&fileProperty: pluginInfo->piPedalUI()->fileProperties())
|
||||
if (fileProperty->patchProperty() == patchPropertyUri)
|
||||
{
|
||||
return fileProperty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
throw std::runtime_error("Permission denied. Plugin not currently loaded.");
|
||||
}
|
||||
|
||||
std::string PiPedalModel::UploadUserFile(const std::string &directory, int64_t instanceId,const std::string &patchProperty, const std::string &filename, std::istream &stream, size_t contentLength)
|
||||
{
|
||||
UiFileProperty::ptr fileProperty = FindLoadedPatchProperty(instanceId,patchProperty);
|
||||
if (!fileProperty)
|
||||
{
|
||||
Lv2Log::error(SS("Upload fle: Permission denied. No currently-loaded plugin provides that patch property: " << patchProperty));
|
||||
throw std::runtime_error("Permission denied.");
|
||||
}
|
||||
return storage.UploadUserFile(directory, fileProperty, filename, stream, contentLength);
|
||||
}
|
||||
|
||||
uint64_t PiPedalModel::CreateNewPreset()
|
||||
|
||||
@@ -259,6 +259,7 @@ namespace pipedal
|
||||
PiPedalConfiguration configuration;
|
||||
|
||||
void CheckForResourceInitialization(Pedalboard &pedalboard);
|
||||
UiFileProperty::ptr FindLoadedPatchProperty(int64_t instanceId,const std::string&patchPropertyUri);
|
||||
|
||||
public:
|
||||
PiPedalModel();
|
||||
@@ -447,15 +448,14 @@ namespace pipedal
|
||||
void SetFavorites(const std::map<std::string, bool> &favorites);
|
||||
void SetUpdatePolicy(UpdatePolicyT updatePolicy);
|
||||
void ForceUpdateCheck();
|
||||
std::vector<std::string> GetFileList(const UiFileProperty &fileProperty);
|
||||
FileRequestResult GetFileList2(const std::string &relativePath, const UiFileProperty &fileProperty);
|
||||
|
||||
void DeleteSampleFile(const std::filesystem::path &fileName);
|
||||
std::string CreateNewSampleDirectory(const std::string &relativePath, const UiFileProperty &uiFileProperty);
|
||||
std::string RenameFilePropertyFile(const std::string &oldRelativePath, const std::string &newRelativePath, const UiFileProperty &uiFileProperty);
|
||||
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty &uiFileProperty);
|
||||
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty &uiFileProperty,const std::string&selectedPath);
|
||||
|
||||
std::string UploadUserFile(const std::string &directory, const std::string &patchProperty, const std::string &filename, std::istream &inputStream, size_t streamLength);
|
||||
std::string UploadUserFile(const std::string &directory, int64_t instanceId, const std::string &patchProperty, const std::string &filename, std::istream &inputStream, size_t streamLength);
|
||||
uint64_t CreateNewPreset();
|
||||
|
||||
bool LoadCurrentPedalboard();
|
||||
|
||||
+24
-7
@@ -102,6 +102,23 @@ JSON_MAP_REFERENCE(RenameSampleFileArgs, newRelativePath)
|
||||
JSON_MAP_REFERENCE(RenameSampleFileArgs, uiFileProperty)
|
||||
JSON_MAP_END()
|
||||
|
||||
class GetFilePropertyDirectoryTreeArgs
|
||||
{
|
||||
public:
|
||||
UiFileProperty fileProperty_;
|
||||
std::string selectedPath_;
|
||||
|
||||
DECLARE_JSON_MAP(GetFilePropertyDirectoryTreeArgs);
|
||||
};
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(GetFilePropertyDirectoryTreeArgs)
|
||||
JSON_MAP_REFERENCE(GetFilePropertyDirectoryTreeArgs, fileProperty)
|
||||
JSON_MAP_REFERENCE(GetFilePropertyDirectoryTreeArgs, selectedPath)
|
||||
JSON_MAP_END()
|
||||
|
||||
|
||||
|
||||
class Lv2StateChangedBody
|
||||
{
|
||||
public:
|
||||
@@ -1585,10 +1602,7 @@ public:
|
||||
}
|
||||
else if (message == "requestFileList")
|
||||
{
|
||||
UiFileProperty fileProperty;
|
||||
pReader->read(&fileProperty);
|
||||
std::vector<std::string> list = this->model.GetFileList(fileProperty);
|
||||
this->Reply(replyTo, "requestFileList", list);
|
||||
throw std::runtime_error("No longer implemented.");
|
||||
}
|
||||
else if (message == "requestFileList2")
|
||||
{
|
||||
@@ -1628,9 +1642,12 @@ public:
|
||||
}
|
||||
else if (message == "getFilePropertyDirectoryTree")
|
||||
{
|
||||
UiFileProperty uiFileProperty;
|
||||
pReader->read(&uiFileProperty);
|
||||
FilePropertyDirectoryTree::ptr result = model.GetFilePropertydirectoryTree(uiFileProperty);
|
||||
GetFilePropertyDirectoryTreeArgs args;
|
||||
pReader->read(&args);
|
||||
FilePropertyDirectoryTree::ptr result =
|
||||
model.GetFilePropertydirectoryTree(
|
||||
args.fileProperty_,
|
||||
args.selectedPath_);
|
||||
this->Reply(replyTo, "GetFilePropertydirectoryTree", result);
|
||||
}
|
||||
|
||||
|
||||
+259
-16
@@ -27,6 +27,10 @@
|
||||
#include "ss.hpp"
|
||||
#include "MimeTypes.hpp"
|
||||
#include "AutoLilvNode.hpp"
|
||||
#include "ModFileTypes.hpp"
|
||||
#include <algorithm>
|
||||
#include "util.hpp"
|
||||
#include "MimeTypes.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
|
||||
@@ -122,17 +126,20 @@ UiFileType::UiFileType(PluginHost *pHost, const LilvNode *node)
|
||||
}
|
||||
if (fileExtension_ == "")
|
||||
{
|
||||
fileExtension_ = MimeTypes::ExtensionFromMimeType(mimeType_);
|
||||
fileExtension_ = MimeTypes::instance().ExtensionFromMimeType(mimeType_);
|
||||
}
|
||||
if (mimeType_ == "")
|
||||
{
|
||||
mimeType_ = MimeTypes::MimeTypeFromExtension(fileExtension_);
|
||||
mimeType_ = MimeTypes::instance().MimeTypeFromExtension(fileExtension_);
|
||||
if (mimeType_ == "")
|
||||
{
|
||||
mimeType_ = "application/octet-stream";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const std::filesystem::path &resourcePath)
|
||||
{
|
||||
auto pWorld = pHost->getWorld();
|
||||
@@ -182,6 +189,8 @@ UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const st
|
||||
throw std::logic_error("PipedalUI::fileProperty: must specify at least a directory.");
|
||||
}
|
||||
|
||||
this->modDirectories_.push_back(directory_);
|
||||
|
||||
AutoLilvNode patchProperty = lilv_world_get(
|
||||
pWorld,
|
||||
node,
|
||||
@@ -207,6 +216,8 @@ UiFileProperty::UiFileProperty(PluginHost *pHost, const LilvNode *node, const st
|
||||
this->resourceDirectory_ = resourceDirectory.AsString();
|
||||
}
|
||||
this->fileTypes_ = UiFileType::GetArray(pHost, node, pHost->lilvUris->pipedalUI__fileTypes);
|
||||
|
||||
PrecalculateFileExtensions();
|
||||
}
|
||||
|
||||
std::vector<UiFileType> UiFileType::GetArray(PluginHost *pHost, const LilvNode *node, const LilvNode *uri)
|
||||
@@ -271,21 +282,41 @@ bool UiFileProperty::IsDirectoryNameValid(const std::string &value)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UiFileProperty::IsValidExtension(const std::string &extension) const
|
||||
{
|
||||
if (fileTypes_.size() == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// bool UiFileProperty::IsValidExtension(const std::filesystem::path&relativePath,const std::string &extension) const
|
||||
// {
|
||||
// std::string modDirectory = this->getParentModDirectory(relativePath);
|
||||
// const auto&validExtensions = GetPermittedFileExtensions(modDirectory);
|
||||
// return validExtensions.contains(extension);
|
||||
// }
|
||||
|
||||
for (auto &fileType : fileTypes_)
|
||||
std::string UiFileProperty::GetFileExtension(const std::filesystem::path&path)
|
||||
{
|
||||
if (path.has_extension())
|
||||
{
|
||||
if (fileType.fileExtension() == extension)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return ToLower(path.extension());
|
||||
}
|
||||
return false;
|
||||
return "";
|
||||
}
|
||||
|
||||
bool UiFileProperty::IsValidExtension(const std::filesystem::path&relativePath) const
|
||||
{
|
||||
std::string extension = GetFileExtension(relativePath);
|
||||
if (this->fileTypes().size() != 0) {
|
||||
for (const auto&fileType: fileTypes())
|
||||
{
|
||||
UiFileType x;
|
||||
if (fileType.IsValidExtension(extension)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (this->modDirectories().size() != 0)
|
||||
{
|
||||
std::string modDirectory = this->getParentModDirectory(relativePath);
|
||||
const auto&validExtensions = GetPermittedFileExtensions(modDirectory);
|
||||
if (validExtensions.contains(extension)) return true;
|
||||
return validExtensions.contains(".*");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
UiPortNotification::UiPortNotification(PluginHost *pHost, const LilvNode *node)
|
||||
@@ -343,6 +374,52 @@ UiPortNotification::UiPortNotification(PluginHost *pHost, const LilvNode *node)
|
||||
}
|
||||
}
|
||||
|
||||
UiFileProperty::UiFileProperty(const std::string&label, const std::string&patchProperty, const ModFileTypes&modFileTypes)
|
||||
: label_(label),
|
||||
patchProperty_(patchProperty),
|
||||
directory_("")
|
||||
{
|
||||
setModFileTypes(modFileTypes);
|
||||
}
|
||||
|
||||
void UiFileProperty::setModFileTypes(const ModFileTypes&modFileTypes)
|
||||
{
|
||||
modDirectories_.resize(0);
|
||||
fileTypes_.resize(0);
|
||||
for (const std::string &type : modFileTypes.fileTypes())
|
||||
{
|
||||
fileTypes().push_back(UiFileType(SS(type << " file"), SS('.' << type)));
|
||||
}
|
||||
|
||||
if (modFileTypes.rootDirectories().size() != 0)
|
||||
{
|
||||
for (const std::string &rootDirectory : modFileTypes.rootDirectories())
|
||||
{
|
||||
modDirectories().push_back(rootDirectory);
|
||||
}
|
||||
} else {
|
||||
if (fileTypes().size() == 0)
|
||||
{
|
||||
// add everything.
|
||||
for (const auto&modDirectory: split(ModFileTypes::DEFAULT_FILE_TYPES,','))
|
||||
{
|
||||
modDirectories().push_back(modDirectory);
|
||||
}
|
||||
} else {
|
||||
// add only those mod directories that contain one or more of our fileTypes.
|
||||
for (const auto&modDirectory: split(ModFileTypes::DEFAULT_FILE_TYPES,','))
|
||||
{
|
||||
modDirectories().push_back(modDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PrecalculateFileExtensions();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
UiFileProperty::UiFileProperty(const std::string &name, const std::string &patchProperty, const std::string &directory)
|
||||
: label_(name),
|
||||
patchProperty_(patchProperty),
|
||||
@@ -362,6 +439,22 @@ PiPedalUI::PiPedalUI(
|
||||
this->fileProperties_ = std::move(fileProperties);
|
||||
}
|
||||
|
||||
bool UiFileType::IsValidExtension(const std::string&extension) const {
|
||||
if (fileExtension_.length() != 0)
|
||||
{
|
||||
if (fileExtension_ == ".*")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return fileExtension_ == extension;
|
||||
}
|
||||
if (mimeType_.length() != 0)
|
||||
{
|
||||
return MimeTypes::instance().IsValidExtension(mimeType_,extension);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UiFileType::UiFileType(const std::string&label, const std::string &fileType)
|
||||
: label_(label)
|
||||
, fileExtension_(fileType)
|
||||
@@ -369,13 +462,12 @@ UiFileType::UiFileType(const std::string&label, const std::string &fileType)
|
||||
if (fileType.starts_with('.'))
|
||||
{
|
||||
fileExtension_ = fileType;
|
||||
mimeType_ = MimeTypes::MimeTypeFromExtension(fileType);
|
||||
mimeType_ = MimeTypes::instance().MimeTypeFromExtension(fileType);
|
||||
if (mimeType_ == "")
|
||||
{
|
||||
mimeType_ = "application/octet-stream";
|
||||
}
|
||||
} else {
|
||||
fileExtension_ = MimeTypes::ExtensionFromMimeType(fileType); // (may be blank, esp. for audio/* and video/*.
|
||||
mimeType_ = fileType;
|
||||
}
|
||||
if (mimeType_ == "*")
|
||||
@@ -443,6 +535,157 @@ UiFrequencyPlot::UiFrequencyPlot(PluginHost*pHost, const LilvNode*node,
|
||||
}
|
||||
|
||||
|
||||
static std::set<std::string> emptySet;
|
||||
|
||||
const std::set<std::string>& UiFileProperty::GetPermittedFileExtensions(const std::string &modDirectory) const {
|
||||
auto result = fileExtensionsByModDirectory.find(modDirectory);
|
||||
if (result != fileExtensionsByModDirectory.end())
|
||||
{
|
||||
return result->second;
|
||||
}
|
||||
if (modDirectory == this->directory_)
|
||||
{
|
||||
std::set<std::string> result;
|
||||
for (auto &x : this->fileTypes_)
|
||||
{
|
||||
|
||||
}
|
||||
return emptySet;
|
||||
}
|
||||
return emptySet;
|
||||
}
|
||||
|
||||
void UiFileProperty::PrecalculateFileExtensions()
|
||||
{
|
||||
std::set<std::string> explicitlySpecifiedExtensions;
|
||||
bool allAudioTypes = false;
|
||||
bool allVideoTypes = false;
|
||||
for (const auto &fileType: this->fileTypes())
|
||||
{
|
||||
if (!fileType.fileExtension().empty())
|
||||
{
|
||||
explicitlySpecifiedExtensions.insert(fileType.fileExtension());
|
||||
} else if (fileType.mimeType().length() != 0)
|
||||
{
|
||||
if (fileType.mimeType() == "audio/*")
|
||||
{
|
||||
allAudioTypes = true;
|
||||
} else if (fileType.mimeType() == "video/*")
|
||||
{
|
||||
allVideoTypes = true;
|
||||
} else {
|
||||
// The principle risk being that there is more than one commonly used extension. e.g. midi.
|
||||
// If the right extension isn't picked, the client should explicitly list which extensions are wanted
|
||||
// instead of using a mime type. :-/ Not a good implementation, but not a use case that's actually going to
|
||||
// come up.
|
||||
std::string extension = MimeTypes::instance().ExtensionFromMimeType(fileType.mimeType());
|
||||
if (extension.length() == 0)
|
||||
{
|
||||
Lv2Log::warning(SS("Unknown MIME type: " << fileType.mimeType()));
|
||||
} else {
|
||||
explicitlySpecifiedExtensions.insert(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (allAudioTypes)
|
||||
{
|
||||
const auto&audioExtensions = MimeTypes::instance().AudioExtensions();
|
||||
explicitlySpecifiedExtensions.insert(
|
||||
audioExtensions.begin(),audioExtensions.end()
|
||||
);
|
||||
}
|
||||
|
||||
if (allVideoTypes)
|
||||
{
|
||||
const auto&videoExtensions = MimeTypes::instance().VideoExtensions();
|
||||
explicitlySpecifiedExtensions.insert(
|
||||
videoExtensions.begin(),videoExtensions.end()
|
||||
);
|
||||
|
||||
}
|
||||
if (this->modDirectories().size() == 0) // not specified? Add all directories, (and prune later, if we have filetypes).
|
||||
{
|
||||
this->modDirectories_ = split(ModFileTypes::DEFAULT_FILE_TYPES,',');
|
||||
}
|
||||
if (fileTypes().size() != 0)
|
||||
{
|
||||
// delete any modDirectories that do not contain one of the given filetyeps.
|
||||
auto iter = this->modDirectories_.begin();
|
||||
while(iter != this->modDirectories_.end())
|
||||
{
|
||||
const auto *pDirectory = ModFileTypes::GetModDirectory(*iter);
|
||||
if (pDirectory == nullptr)
|
||||
{
|
||||
// a custom directory! How cool is that!?
|
||||
this->fileExtensionsByModDirectory[*iter] = explicitlySpecifiedExtensions;
|
||||
++iter;
|
||||
} else {
|
||||
std::set<std::string> extensions = Intersect(pDirectory->fileExtensions,explicitlySpecifiedExtensions);
|
||||
if (extensions.size() == 0)
|
||||
{
|
||||
iter = modDirectories_.erase(iter);
|
||||
} else {
|
||||
this->fileExtensionsByModDirectory[*iter] = std::move(extensions);
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto iter = this->modDirectories_.begin();
|
||||
while(iter != this->modDirectories_.end())
|
||||
{
|
||||
const auto *pDirectory = ModFileTypes::GetModDirectory(*iter);
|
||||
if (pDirectory)
|
||||
{
|
||||
this->fileExtensionsByModDirectory[*iter] = pDirectory->fileExtensions;
|
||||
} else {
|
||||
Lv2Log::warning(SS("Unknown Mod Directory: " << *iter));
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// std::set<std::string> modDirectoryFileTypes = rootModDirectory->fileExtensions;
|
||||
// if (fileProperty.fileTypes().size() != 0)
|
||||
// {
|
||||
// std::set<std::string> specifiedFileTypes;
|
||||
|
||||
// for (const auto&fileType: fileProperty.fileTypes())
|
||||
// {
|
||||
// if (!fileType.fileExtension().empty())
|
||||
// {
|
||||
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
std::string UiFileProperty::getParentModDirectory(const std::filesystem::path&path) const
|
||||
{
|
||||
for (const auto &modDirectory: this->modDirectories_)
|
||||
{
|
||||
const auto pModDirectory = ModFileTypes::GetModDirectory(modDirectory);
|
||||
if (pModDirectory)
|
||||
{
|
||||
if (IsChildDirectory(path,pModDirectory->pipedalPath))
|
||||
{
|
||||
return modDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this->directory_.length() != 0)
|
||||
{
|
||||
if (IsChildDirectory(path,this->directory_))
|
||||
{
|
||||
return this->directory_;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
JSON_MAP_BEGIN(UiPortNotification)
|
||||
JSON_MAP_REFERENCE(UiPortNotification, portIndex)
|
||||
|
||||
+27
-6
@@ -29,6 +29,8 @@
|
||||
#include <lilv/lilv.h>
|
||||
#include "json.hpp"
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
#include "ModFileTypes.hpp"
|
||||
|
||||
|
||||
#define PIPEDAL_UI "http://github.com/rerdavies/pipedal/ui"
|
||||
@@ -83,7 +85,7 @@ namespace pipedal {
|
||||
const std::string& label() const { return label_;}
|
||||
const std::string &fileExtension() const { return fileExtension_; }
|
||||
const std::string &mimeType() const { return mimeType_; }
|
||||
|
||||
bool IsValidExtension(const std::string&extension) const;
|
||||
public:
|
||||
DECLARE_JSON_MAP(UiFileType);
|
||||
|
||||
@@ -118,13 +120,20 @@ namespace pipedal {
|
||||
std::string resourceDirectory_;
|
||||
std::vector<std::string> modDirectories_;
|
||||
bool useLegacyModDirectory_= false;
|
||||
std::map<std::string,std::set<std::string>> fileExtensionsByModDirectory; // non-serialized.
|
||||
void PrecalculateFileExtensions();
|
||||
public:
|
||||
using ptr = std::shared_ptr<UiFileProperty>;
|
||||
UiFileProperty() { }
|
||||
UiFileProperty(PluginHost*pHost, const LilvNode*node, const std::filesystem::path&resourcePath);
|
||||
UiFileProperty(const std::string&name, const std::string&patchProperty,const std::string &directory);
|
||||
UiFileProperty(const std::string&label, const std::string&patchProperty,const std::string &directory);
|
||||
UiFileProperty(
|
||||
const std::string&label,
|
||||
const std::string&patchProperty,
|
||||
const ModFileTypes&modFileType);
|
||||
|
||||
void setModFileTypes(const ModFileTypes&modFileType);
|
||||
|
||||
|
||||
std::vector<std::string>& modDirectories() { return modDirectories_; }
|
||||
const std::vector<std::string>& modDirectories() const { return modDirectories_; }
|
||||
|
||||
@@ -144,11 +153,19 @@ namespace pipedal {
|
||||
std::vector<UiFileType> &fileTypes() { return fileTypes_; }
|
||||
|
||||
const std::string &patchProperty() const { return patchProperty_; }
|
||||
bool IsValidExtension(const std::string&extension) const;
|
||||
|
||||
bool IsValidExtension(const std::filesystem::path&relativePath) const;
|
||||
|
||||
static bool IsDirectoryNameValid(const std::string&value);
|
||||
|
||||
static std::string GetFileExtension(const std::filesystem::path&path);
|
||||
|
||||
const std::string&resourceDirectory() const { return resourceDirectory_; }
|
||||
|
||||
|
||||
const std::set<std::string>& GetPermittedFileExtensions(const std::string &modDirectory) const;
|
||||
|
||||
std::string getParentModDirectory(const std::filesystem::path&path) const;
|
||||
public:
|
||||
DECLARE_JSON_MAP(UiFileProperty);
|
||||
};
|
||||
@@ -202,6 +219,8 @@ namespace pipedal {
|
||||
return frequencyPlots_;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const std::vector<UiPortNotification::ptr> &portNotifications() const { return portNotifications_; }
|
||||
|
||||
const UiFileProperty*GetFileProperty(const std::string &propertyUri) const
|
||||
@@ -215,14 +234,16 @@ namespace pipedal {
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool unsupportedPatchProperty() const { return unsupportedPatchProperty_; }
|
||||
void unsupportedPatchProperty(bool value) { unsupportedPatchProperty_ = value; }
|
||||
private:
|
||||
bool unsupportedPatchProperty_ = false; // not serialized.
|
||||
std::vector<UiFileProperty::ptr> fileProperties_;
|
||||
std::vector<UiFrequencyPlot::ptr> frequencyPlots_;
|
||||
std::vector<UiPortNotification::ptr> portNotifications_;
|
||||
};
|
||||
|
||||
// Utiltities for validating file paths received via PiPedalFileProperty-related APIs.
|
||||
// utilities for validating file paths received via PiPedalFileProperty-related APIs.
|
||||
bool IsAlphaNumeric(const std::string&value);
|
||||
|
||||
|
||||
|
||||
+95
-94
@@ -195,7 +195,6 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
||||
dc__format = lilv_new_uri(pWorld, "http://purl.org/dc/terms/format");
|
||||
|
||||
mod__fileTypes = lilv_new_uri(pWorld, "http://moddevices.com/ns/mod#fileTypes");
|
||||
pipedalui__fileTypes =lilv_new_uri(pWorld, PIPEDAL_UI__fileTypes);
|
||||
}
|
||||
|
||||
void PluginHost::LilvUris::Free()
|
||||
@@ -367,7 +366,7 @@ void PluginHost::LoadPluginClassesFromLilv()
|
||||
LILV_FOREACH(plugin_classes, iPluginClass, classes)
|
||||
{
|
||||
const LilvPluginClass *pClass = lilv_plugin_classes_get(classes, iPluginClass);
|
||||
// Get it to prepopulate the map.
|
||||
// Get it to pre-populate the map.
|
||||
MakePluginClass(pClass);
|
||||
}
|
||||
for (auto value : classesMap)
|
||||
@@ -432,6 +431,9 @@ void PluginHost::Load(const char *lv2Path)
|
||||
if (pluginInfo->hasCvPorts())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. (Has CV ports).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
} else if (pluginInfo->hasUnsupportedPatchProperties())
|
||||
{
|
||||
Lv2Log::debug("Plugin %s (%s) skipped. (Has unsupported patch parameters).", pluginInfo->name().c_str(), pluginInfo->uri().c_str());
|
||||
}
|
||||
#if !SUPPORT_MIDI
|
||||
else if (pluginInfo->plugin_class() == LV2_MIDI_PLUGIN)
|
||||
@@ -481,10 +483,15 @@ void PluginHost::Load(const char *lv2Path)
|
||||
|
||||
for (auto plugin : this->plugins_)
|
||||
{
|
||||
pluginsByUri[plugin->uri()] = plugin;
|
||||
|
||||
Lv2PluginUiInfo info(this, plugin.get());
|
||||
if (plugin->is_valid())
|
||||
{
|
||||
#if 1
|
||||
// no plugins with more than 2 inputs or outputs.
|
||||
// no zero-input or zero-output plugins (temporarily disables midi plugins)
|
||||
// no zero output devices (permanent, I think)
|
||||
if (info.audio_inputs() > 2 || info.audio_outputs() > 2) {
|
||||
Lv2Log::debug(
|
||||
"Plugin %s (%s) skipped. %d inputs, %d outputs.", plugin->name().c_str(), plugin->uri().c_str(),
|
||||
@@ -598,7 +605,8 @@ bool Lv2PluginInfo::HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plu
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<PiPedalUI> Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin *pPlugin)
|
||||
Lv2PluginInfo::FindWritablePathPropertiesResult
|
||||
Lv2PluginInfo::FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin *pPlugin)
|
||||
{
|
||||
// example:
|
||||
|
||||
@@ -615,6 +623,7 @@ std::shared_ptr<PiPedalUI> Lv2PluginInfo::FindWritablePathProperties(PluginHost
|
||||
|
||||
std::vector<UiFileProperty::ptr> fileProperties;
|
||||
|
||||
bool unsupportedPatchProperty = false;
|
||||
LILV_FOREACH(nodes, iNode, patchWritables)
|
||||
{
|
||||
AutoLilvNode propertyUri = lilv_nodes_get(patchWritables, iNode);
|
||||
@@ -631,99 +640,91 @@ std::shared_ptr<PiPedalUI> Lv2PluginInfo::FindWritablePathProperties(PluginHost
|
||||
AutoLilvNode label = lilv_world_get(pWorld, propertyUri, lv2Host->lilvUris->rdfs__label, nullptr);
|
||||
std::string strLabel = label.AsString();
|
||||
|
||||
if (strLabel.length() != 0)
|
||||
if (strLabel.length() == 0)
|
||||
{
|
||||
std::filesystem::path path = this->bundle_path();
|
||||
path = path.parent_path();
|
||||
std::string lv2DirectoryName = path.filename().string();
|
||||
// we have a valid path property!
|
||||
|
||||
auto fileProperty =
|
||||
std::make_shared<UiFileProperty>(
|
||||
strLabel, propertyUri.AsUri(), lv2DirectoryName);
|
||||
|
||||
|
||||
AutoLilvNode indexNode = lilv_world_get(pWorld, propertyUri, lv2Host->lilvUris->lv2core__index, nullptr);
|
||||
int32_t index = indexNode.AsInt(-1);
|
||||
fileProperty->index(index);
|
||||
|
||||
|
||||
// if there's a pipedalui_fileTypes node, use that instead.
|
||||
|
||||
AutoLilvNodes mod__fileTypes = lilv_world_find_nodes(pWorld, propertyUri, lv2Host->lilvUris->pipedalui__fileTypes, nullptr);
|
||||
if (!mod__fileTypes)
|
||||
{
|
||||
mod__fileTypes = lilv_world_find_nodes(pWorld, propertyUri, lv2Host->lilvUris->mod__fileTypes, nullptr);
|
||||
}
|
||||
|
||||
LILV_FOREACH(nodes, i, mod__fileTypes)
|
||||
{
|
||||
// "nam,nammodel"
|
||||
AutoLilvNode lilvfileType{lilv_nodes_get(mod__fileTypes, i)};
|
||||
std::string fileTypes = lilvfileType.AsString();
|
||||
ModFileTypes modFileTypes(fileTypes);
|
||||
|
||||
for (const std::string &rootDirectory : modFileTypes.rootDirectories())
|
||||
{
|
||||
fileProperty->modDirectories().push_back(rootDirectory);
|
||||
}
|
||||
for (const std::string &type : modFileTypes.fileTypes())
|
||||
{
|
||||
fileProperty->fileTypes().push_back(UiFileType(SS(type << " file"), SS('.' << type)));
|
||||
}
|
||||
// Legacy case: audio_uploads/<plugin_directory> exists.
|
||||
|
||||
std::filesystem::path bundleDirectoryName = std::filesystem::path(bundle_path()).parent_path().filename();
|
||||
std::filesystem::path legacyUploadPath = lv2Host->MapPath(bundleDirectoryName.string());
|
||||
|
||||
if (std::filesystem::exists(legacyUploadPath))
|
||||
{
|
||||
if (!std::filesystem::exists(legacyUploadPath / ".migrated"))
|
||||
{
|
||||
fileProperty->useLegacyModDirectory(true);
|
||||
fileProperty->directory(bundleDirectoryName);
|
||||
}
|
||||
}
|
||||
if (fileProperty->modDirectories().size() == 0)
|
||||
{
|
||||
fileProperty->directory(bundleDirectoryName);
|
||||
}
|
||||
else if (fileProperty->modDirectories().size() == 1 && !fileProperty->useLegacyModDirectory()) // no synthetic root.
|
||||
{
|
||||
auto modType = ModFileTypes::GetModDirectory(fileProperty->modDirectories()[0]);
|
||||
if (modType)
|
||||
{
|
||||
fileProperty->directory(modType->pipedalPath);
|
||||
}
|
||||
} else {
|
||||
// handled at request time.
|
||||
}
|
||||
}
|
||||
if (!mod__fileTypes)
|
||||
{
|
||||
AutoLilvNodes dc_types = lilv_world_find_nodes(pWorld, propertyUri, lv2Host->lilvUris->dc__format, nullptr);
|
||||
LILV_FOREACH(nodes, i, dc_types)
|
||||
{
|
||||
AutoLilvNode dc_type = lilv_nodes_get(dc_types, i);
|
||||
std::string fileType = dc_type.AsString();
|
||||
std::string label = "";
|
||||
fileProperty->fileTypes().push_back(UiFileType(label, fileType));
|
||||
}
|
||||
}
|
||||
|
||||
fileProperties.push_back(
|
||||
fileProperty);
|
||||
strLabel = "File";
|
||||
}
|
||||
|
||||
std::filesystem::path path = this->bundle_path();
|
||||
path = path.parent_path();
|
||||
std::string lv2DirectoryName = path.filename().string();
|
||||
// we have a valid path property!
|
||||
|
||||
auto fileProperty =
|
||||
std::make_shared<UiFileProperty>(
|
||||
strLabel, propertyUri.AsUri(), lv2DirectoryName);
|
||||
|
||||
|
||||
AutoLilvNode indexNode = lilv_world_get(pWorld, propertyUri, lv2Host->lilvUris->lv2core__index, nullptr);
|
||||
int32_t index = indexNode.AsInt(-1);
|
||||
fileProperty->index(index);
|
||||
|
||||
|
||||
// if there's a pipedalui_fileTypes node, use that instead.
|
||||
|
||||
|
||||
AutoLilvNode mod__fileTypes = lilv_world_get(pWorld, propertyUri, lv2Host->lilvUris->mod__fileTypes, nullptr);
|
||||
|
||||
// default: everything.
|
||||
std::string fileTypes = ModFileTypes::DEFAULT_FILE_TYPES;
|
||||
if (mod__fileTypes)
|
||||
{
|
||||
// "nam,nammodel"
|
||||
fileTypes = mod__fileTypes.AsString();
|
||||
}
|
||||
ModFileTypes modFileTypes(fileTypes);
|
||||
|
||||
|
||||
|
||||
// Legacy case: audio_uploads/<plugin_directory> exists.
|
||||
|
||||
std::filesystem::path bundleDirectoryName = std::filesystem::path(bundle_path()).parent_path().filename();
|
||||
std::filesystem::path legacyUploadPath = lv2Host->MapPath(bundleDirectoryName.string());
|
||||
|
||||
if (std::filesystem::exists(legacyUploadPath))
|
||||
{
|
||||
if (!std::filesystem::exists(legacyUploadPath / ".migrated"))
|
||||
{
|
||||
fileProperty->useLegacyModDirectory(true);
|
||||
fileProperty->directory(bundleDirectoryName);
|
||||
}
|
||||
modFileTypes.rootDirectories().push_back(bundleDirectoryName.filename()); // push the private directory!
|
||||
}
|
||||
fileProperty->setModFileTypes(modFileTypes);
|
||||
|
||||
fileProperty->directory(bundleDirectoryName);
|
||||
|
||||
fileProperties.push_back(fileProperty);
|
||||
} else {
|
||||
unsupportedPatchProperty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FindWritablePathPropertiesResult result;
|
||||
|
||||
|
||||
|
||||
if (fileProperties.size() != 0)
|
||||
{
|
||||
return std::make_shared<PiPedalUI>(std::move(fileProperties));
|
||||
}
|
||||
std::sort(fileProperties.begin(),fileProperties.end(),[](const UiFileProperty::ptr& left,const UiFileProperty::ptr&right) {
|
||||
// properies with indexes first.
|
||||
int32_t indexL = left->index();
|
||||
if (indexL < 0) indexL = std::numeric_limits<int32_t>::max();
|
||||
int32_t indexR = right->index();
|
||||
if (indexR < 0) indexR = std::numeric_limits<int32_t>::max();
|
||||
if (indexL < indexR) return true;
|
||||
if (indexL > indexR) return false;
|
||||
|
||||
return std::shared_ptr<PiPedalUI>();
|
||||
// there is no natural order. TTL indexing means that the order we see them in is random.
|
||||
// We can't order them sensibly. Let's at least order them consistently.
|
||||
return left->label() < right->label();
|
||||
|
||||
});
|
||||
result.pipedalUi = std::make_shared<PiPedalUI>(std::move(fileProperties));
|
||||
}
|
||||
result.hasUnsupportedPatchProperties = unsupportedPatchProperty;
|
||||
return result;
|
||||
}
|
||||
|
||||
Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvPlugin *pPlugin)
|
||||
@@ -837,7 +838,9 @@ Lv2PluginInfo::Lv2PluginInfo(PluginHost *lv2Host, LilvWorld *pWorld, const LilvP
|
||||
else
|
||||
{
|
||||
// look for
|
||||
this->piPedalUI_ = FindWritablePathProperties(lv2Host, pPlugin);
|
||||
auto result = FindWritablePathProperties(lv2Host, pPlugin);
|
||||
this->piPedalUI_ = result.pipedalUi;
|
||||
this->hasUnsupportedPatchProperties_ = result.hasUnsupportedPatchProperties;
|
||||
}
|
||||
|
||||
int nInputs = 0;
|
||||
@@ -1220,14 +1223,12 @@ std::shared_ptr<Lv2PluginClass> PluginHost::GetLv2PluginClass() const
|
||||
|
||||
std::shared_ptr<Lv2PluginInfo> PluginHost::GetPluginInfo(const std::string &uri) const
|
||||
{
|
||||
for (auto i = this->plugins_.begin(); i != this->plugins_.end(); ++i)
|
||||
auto ff = pluginsByUri.find(uri);
|
||||
if (ff == pluginsByUri.end())
|
||||
{
|
||||
if ((*i)->uri() == uri)
|
||||
{
|
||||
return (*i);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return nullptr;
|
||||
return ff->second;
|
||||
}
|
||||
|
||||
Lv2Pedalboard *PluginHost::CreateLv2Pedalboard(Pedalboard &pedalboard, Lv2PedalboardErrorList &errorMessages)
|
||||
|
||||
+8
-1
@@ -346,7 +346,11 @@ namespace pipedal
|
||||
bool isSplit() const ;
|
||||
|
||||
private:
|
||||
std::shared_ptr<PiPedalUI> FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin *pPlugin);
|
||||
struct FindWritablePathPropertiesResult {
|
||||
std::shared_ptr<PiPedalUI> pipedalUi;
|
||||
bool hasUnsupportedPatchProperties = false;
|
||||
};
|
||||
FindWritablePathPropertiesResult FindWritablePathProperties(PluginHost *lv2Host, const LilvPlugin *pPlugin);
|
||||
|
||||
bool HasFactoryPresets(PluginHost *lv2Host, const LilvPlugin *plugin);
|
||||
std::string bundle_path_;
|
||||
@@ -369,6 +373,7 @@ namespace pipedal
|
||||
std::vector<std::shared_ptr<Lv2PortGroup>> port_groups_;
|
||||
bool is_valid_ = false;
|
||||
PiPedalUI::ptr piPedalUI_;
|
||||
bool hasUnsupportedPatchProperties_ = false;
|
||||
|
||||
bool IsSupportedFeature(const std::string &feature) const;
|
||||
|
||||
@@ -391,6 +396,7 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET(port_groups)
|
||||
LV2_PROPERTY_GETSET(has_factory_presets)
|
||||
LV2_PROPERTY_GETSET(piPedalUI)
|
||||
LV2_PROPERTY_GETSET(hasUnsupportedPatchProperties)
|
||||
|
||||
const Lv2PortInfo &getPort(const std::string &symbol)
|
||||
{
|
||||
@@ -779,6 +785,7 @@ namespace pipedal
|
||||
void free_world();
|
||||
|
||||
std::vector<std::shared_ptr<Lv2PluginInfo>> plugins_;
|
||||
std::map<std::string,std::shared_ptr<Lv2PluginInfo>> pluginsByUri;
|
||||
std::vector<Lv2PluginUiInfo> ui_plugins_;
|
||||
|
||||
std::map<std::string, std::shared_ptr<Lv2PluginClass>> classesMap;
|
||||
|
||||
+118
-65
@@ -34,6 +34,9 @@
|
||||
#include "ss.hpp"
|
||||
#include "ofstream_synced.hpp"
|
||||
#include "ModFileTypes.hpp"
|
||||
#include <set>
|
||||
#include <MimeTypes.hpp>
|
||||
#include "util.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
@@ -1388,7 +1391,7 @@ uint64_t Storage::SavePluginPreset(
|
||||
void Storage::UpdatePluginPresets(const PluginUiPresets &pluginPresets)
|
||||
{
|
||||
// handles deletions, renaming, and reordering only.
|
||||
// If you need to add a preset, you neet to call SavePluginPreset or DulicatePluginPreset instead.
|
||||
// If you need to add a preset, you need to call SavePluginPreset or DuplicatePluginPreset instead.
|
||||
|
||||
PluginPresets presets = this->GetPluginPresets(pluginPresets.pluginUri_);
|
||||
PluginPresets newPresets;
|
||||
@@ -1623,51 +1626,6 @@ static void ThrowPermissionDeniedError()
|
||||
throw std::logic_error("Permission denied.");
|
||||
}
|
||||
|
||||
std::vector<std::string> Storage::GetFileList(const UiFileProperty &fileProperty)
|
||||
{
|
||||
if (!UiFileProperty::IsDirectoryNameValid(fileProperty.directory()))
|
||||
{
|
||||
ThrowPermissionDeniedError();
|
||||
}
|
||||
|
||||
std::vector<std::string> result;
|
||||
|
||||
// if fileProperty has a user-accessible directory, push the entire file path.
|
||||
if (fileProperty.directory().size() != 0)
|
||||
{
|
||||
std::filesystem::path audioFileDirectory = this->GetPluginUploadDirectory() / fileProperty.directory();
|
||||
try
|
||||
{
|
||||
for (auto const &dir_entry : std::filesystem::directory_iterator(audioFileDirectory))
|
||||
{
|
||||
if (dir_entry.is_regular_file())
|
||||
{
|
||||
auto &path = dir_entry.path();
|
||||
auto name = path.filename().string();
|
||||
if (name.length() > 0 && name[0] != '.') // don't show hidden files.
|
||||
{
|
||||
if (fileProperty.IsValidExtension(path.extension().string()))
|
||||
{
|
||||
// a relative path!
|
||||
result.push_back(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const std::exception &error)
|
||||
{
|
||||
throw std::logic_error("GetFileList failed. Directory not found: " + audioFileDirectory.string());
|
||||
}
|
||||
}
|
||||
|
||||
// sort lexicographically
|
||||
auto collator = Locale::GetInstance()->GetCollator();
|
||||
|
||||
std::sort(result.begin(), result.end(), [&collator](const std::string &left, const std::string &right)
|
||||
{ return collator->Compare(left, right) < 0; });
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool ensureNoDotDot(const std::filesystem::path &path)
|
||||
{
|
||||
@@ -1695,8 +1653,10 @@ static bool ensureNoDotDot(const std::filesystem::path &path)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void AddFilesToResult(
|
||||
FileRequestResult &result,
|
||||
const ModFileTypes::ModDirectory *modDirectoryInfo, //yyx
|
||||
const UiFileProperty &fileProperty,
|
||||
const fs::path &rootPath)
|
||||
{
|
||||
@@ -1705,7 +1665,12 @@ static void AddFilesToResult(
|
||||
return; // silently without error.
|
||||
}
|
||||
auto &resultFiles = result.files_;
|
||||
try
|
||||
|
||||
std::set<std::string> validExtensions = fileProperty.GetPermittedFileExtensions(
|
||||
modDirectoryInfo? modDirectoryInfo->modType: "");
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
for (auto const &dir_entry : std::filesystem::directory_iterator(rootPath))
|
||||
{
|
||||
@@ -1713,9 +1678,15 @@ static void AddFilesToResult(
|
||||
auto name = path.filename().string();
|
||||
if (dir_entry.is_regular_file())
|
||||
{
|
||||
std::string extension = UiFileProperty::GetFileExtension(path);
|
||||
|
||||
bool match = false;
|
||||
if (name.length() > 0 && name[0] != '.') // don't show hidden files.
|
||||
{
|
||||
if (fileProperty.IsValidExtension(path.extension().string()))
|
||||
bool match = validExtensions.size() == 0 || validExtensions.contains(extension)
|
||||
|| validExtensions.contains(".*");
|
||||
|
||||
if (match)
|
||||
{
|
||||
resultFiles.push_back(
|
||||
FileEntry(path, name, false, false));
|
||||
@@ -1778,6 +1749,7 @@ FileRequestResult Storage::GetModFileList2(const std::string &relativePath, cons
|
||||
result.breadcrumbs_.push_back({"", "Home"});
|
||||
fs::path fsRelativePath{relativePath};
|
||||
|
||||
const ModFileTypes::ModDirectory *rootModDirectory = nullptr;
|
||||
for (const auto &modDirectory : fileProperty.modDirectories())
|
||||
{
|
||||
const ModFileTypes::ModDirectory *modDirectoryInfo = ModFileTypes::GetModDirectory(modDirectory);
|
||||
@@ -1785,6 +1757,7 @@ FileRequestResult Storage::GetModFileList2(const std::string &relativePath, cons
|
||||
{
|
||||
if (isSubdirectory(fsRelativePath, uploadsDirectory / modDirectoryInfo->pipedalPath))
|
||||
{
|
||||
rootModDirectory = modDirectoryInfo;
|
||||
modDirectoryPath = uploadsDirectory / modDirectoryInfo->pipedalPath;
|
||||
result.breadcrumbs_.push_back({modDirectoryPath.string(), modDirectoryInfo->displayName});
|
||||
break;
|
||||
@@ -1804,12 +1777,12 @@ FileRequestResult Storage::GetModFileList2(const std::string &relativePath, cons
|
||||
}
|
||||
}
|
||||
|
||||
// add remaing path segements as breadcrumbs.
|
||||
// add reaming path segments as breadcrumbs.
|
||||
{
|
||||
fs::path modPath{modDirectoryPath};
|
||||
fs::path rp{relativePath};
|
||||
auto iRp = rp.begin();
|
||||
// skip past one or more segments in the modDiretoryPath.
|
||||
// skip past one or more segments in the modDirectoryPath.
|
||||
for (auto iModPath = modPath.begin(); iModPath != modPath.end(); ++iModPath)
|
||||
{
|
||||
if (iRp != rp.end())
|
||||
@@ -1824,7 +1797,8 @@ FileRequestResult Storage::GetModFileList2(const std::string &relativePath, cons
|
||||
}
|
||||
}
|
||||
|
||||
AddFilesToResult(result, fileProperty, relativePath);
|
||||
|
||||
AddFilesToResult(result,rootModDirectory, fileProperty, relativePath);
|
||||
result.currentDirectory_ = relativePath;
|
||||
return result;
|
||||
}
|
||||
@@ -1911,7 +1885,14 @@ FileRequestResult Storage::GetFileList2(const std::string &relativePath_, const
|
||||
{
|
||||
throw std::runtime_error(SS("Improper location. " << absolutePath));
|
||||
}
|
||||
AddFilesToResult(result, fileProperty, absolutePath);
|
||||
|
||||
const ModFileTypes::ModDirectory*pModDirectory = nullptr;
|
||||
if (fileProperty.modDirectories().size() > 0)
|
||||
{
|
||||
pModDirectory = ModFileTypes::GetModDirectory(fileProperty.modDirectories()[0]);
|
||||
}
|
||||
|
||||
AddFilesToResult(result,pModDirectory, fileProperty, absolutePath);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2005,7 +1986,10 @@ std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, co
|
||||
}
|
||||
return result;
|
||||
}
|
||||
std::string Storage::UploadUserFile(const std::string &directory, const std::string &patchProperty, const std::string &filename, std::istream &stream, size_t contentLength)
|
||||
std::string Storage::UploadUserFile(const std::string &directory,
|
||||
UiFileProperty::ptr uiFileProperty,
|
||||
const std::string &filename,
|
||||
std::istream &stream, size_t contentLength)
|
||||
{
|
||||
std::filesystem::path path;
|
||||
if (directory.length() != 0)
|
||||
@@ -2013,14 +1997,23 @@ std::string Storage::UploadUserFile(const std::string &directory, const std::str
|
||||
path = this->MakeUserFilePath(directory, filename);
|
||||
}
|
||||
else
|
||||
|
||||
{
|
||||
if (patchProperty.length() == 0)
|
||||
{
|
||||
throw std::logic_error("Malformed request.");
|
||||
}
|
||||
throw std::logic_error("patchProperty directory not implemented.");
|
||||
throw std::logic_error("Directory argument not supplied.");
|
||||
}
|
||||
fs::path relativePath;
|
||||
try {
|
||||
relativePath = MakeRelativePath(path,this->GetPluginUploadDirectory());
|
||||
} catch (const std::exception& e)
|
||||
{
|
||||
throw std::logic_error("Permission denied. Path is outside the upload storage directory.");
|
||||
}
|
||||
|
||||
if (!uiFileProperty->IsValidExtension(relativePath))
|
||||
{
|
||||
throw std::logic_error("Permission denied. Invalid file extension for this directory.");
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -2138,24 +2131,84 @@ void Storage::FillSampleDirectoryTree(FilePropertyDirectoryTree *node, const std
|
||||
return collator->Compare(left->directoryName_, right->directoryName_) < 0;
|
||||
});
|
||||
}
|
||||
FilePropertyDirectoryTree::ptr Storage::GetFilePropertydirectoryTree(const UiFileProperty &uiFileProperty)
|
||||
|
||||
|
||||
|
||||
static void GetAllExtensions(std::set<std::string>&result, const std::filesystem::path &path)
|
||||
{
|
||||
assert(fs::is_directory(path));
|
||||
|
||||
for (const auto&dirEnt : fs::directory_iterator(path))
|
||||
{
|
||||
if (dirEnt.is_directory())
|
||||
{
|
||||
GetAllExtensions(result,dirEnt.path());
|
||||
} else {
|
||||
std::string filename = dirEnt.path().filename();
|
||||
if (!filename.starts_with('.'))
|
||||
{
|
||||
if (dirEnt.path().has_extension())
|
||||
{
|
||||
std::string extension = dirEnt.path().extension();
|
||||
result.insert(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::set<std::string> GetAllExtensions(const std::filesystem::path &path)
|
||||
{
|
||||
std::set<std::string> result;
|
||||
if (fs::is_regular_file(path)) {
|
||||
result.insert(UiFileProperty::GetFileExtension(path));
|
||||
} else {
|
||||
GetAllExtensions(result,path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
FilePropertyDirectoryTree::ptr Storage::GetFilePropertydirectoryTree(const UiFileProperty &uiFileProperty, const std::filesystem::path&selectedPath)
|
||||
{
|
||||
fs::path uploadDirectory = this->GetPluginUploadDirectory();
|
||||
|
||||
fs::path relativePath;
|
||||
try {
|
||||
relativePath = MakeRelativePath(selectedPath,uploadDirectory);
|
||||
} catch (const std::exception&e) {
|
||||
// not an upload directory.
|
||||
throw std::runtime_error(SS("Permission denied: " << selectedPath));
|
||||
}
|
||||
|
||||
std::set<std::string> fileExtensions = GetAllExtensions(selectedPath);
|
||||
|
||||
|
||||
if (hasSyntheticModRoot(uiFileProperty))
|
||||
{
|
||||
|
||||
if (relativePath.empty())
|
||||
{
|
||||
throw std::runtime_error(SS("Permission denied: " << selectedPath));
|
||||
}
|
||||
FilePropertyDirectoryTree::ptr result = std::make_unique<FilePropertyDirectoryTree>("", "Home");
|
||||
result->isProtected_ = true;
|
||||
std::string parentModDirectory = uiFileProperty.getParentModDirectory(relativePath);
|
||||
|
||||
for (const auto &modDirectory : uiFileProperty.modDirectories())
|
||||
{
|
||||
auto modDirectoryInfo = ModFileTypes::GetModDirectory(modDirectory);
|
||||
if (modDirectoryInfo)
|
||||
{
|
||||
auto childPath = uploadDirectory / modDirectoryInfo->pipedalPath;
|
||||
FilePropertyDirectoryTree::ptr child = std::make_unique<FilePropertyDirectoryTree>(
|
||||
childPath, modDirectoryInfo->displayName);
|
||||
FillSampleDirectoryTree(child.get(), childPath);
|
||||
result->children_.push_back(std::move(child));
|
||||
if (IsSubset(fileExtensions,modDirectoryInfo->fileExtensions) ||
|
||||
modDirectoryInfo->fileExtensions.contains(".*") ||
|
||||
modDirectoryInfo->fileExtensions.empty() // Private directory that accepts files of any type.
|
||||
)
|
||||
{
|
||||
auto childPath = uploadDirectory / modDirectoryInfo->pipedalPath;
|
||||
FilePropertyDirectoryTree::ptr child = std::make_unique<FilePropertyDirectoryTree>(
|
||||
childPath, modDirectoryInfo->displayName);
|
||||
FillSampleDirectoryTree(child.get(), childPath);
|
||||
result->children_.push_back(std::move(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (uiFileProperty.useLegacyModDirectory())
|
||||
|
||||
+4
-3
@@ -37,6 +37,7 @@ namespace pipedal {
|
||||
|
||||
class UiFileProperty;
|
||||
class Lv2PluginInfo;
|
||||
class UiFileProperty;
|
||||
|
||||
class CurrentPreset {
|
||||
public:
|
||||
@@ -153,7 +154,6 @@ public:
|
||||
void MoveBank(int from, int to);
|
||||
int64_t DeleteBank(int64_t bankId);
|
||||
|
||||
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
|
||||
FileRequestResult GetFileList2(const std::string&relativePath,const UiFileProperty&fileProperty);
|
||||
|
||||
FileRequestResult GetModFileList2(const std::string &relativePath,const UiFileProperty &fileProperty);
|
||||
@@ -222,13 +222,14 @@ public:
|
||||
void SetSystemMidiBindings(const std::vector<MidiBinding>&bindings);
|
||||
std::vector<MidiBinding> GetSystemMidiBindings();
|
||||
void DeleteSampleFile(const std::filesystem::path &fileName);
|
||||
std::string UploadUserFile(const std::string &directory, const std::string &patchProperty,const std::string&filename,std::istream&stream, size_t contentLength);
|
||||
std::string UploadUserFile(const std::string &directory,
|
||||
std::shared_ptr<UiFileProperty> uiFileProperty ,const std::string&filename,std::istream&stream, size_t contentLength);
|
||||
std::string CreateNewSampleDirectory(const std::string&relativePath, const UiFileProperty&uiFileProperty);
|
||||
std::string RenameFilePropertyFile(
|
||||
const std::string&oldRelativePath,
|
||||
const std::string&newRelativePath,
|
||||
const UiFileProperty&uiFileProperty);
|
||||
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty);
|
||||
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty,const std::filesystem::path&selectedPath);
|
||||
};
|
||||
|
||||
|
||||
|
||||
+13
-3
@@ -550,11 +550,21 @@ public:
|
||||
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
std::string instanceId = request_uri.query("id");
|
||||
std::string instanceIdString = request_uri.query("id");
|
||||
std::string directory = request_uri.query("directory");
|
||||
std::string filename = request_uri.query("filename");
|
||||
std::string patchProperty = request_uri.query("property");
|
||||
|
||||
int64_t instanceId;
|
||||
{
|
||||
std::istringstream ss { instanceIdString};
|
||||
ss >> instanceId;
|
||||
if (!ss)
|
||||
{
|
||||
throw PiPedalException("Invalid instanceID");
|
||||
}
|
||||
}
|
||||
|
||||
if (patchProperty.length() == 0 && directory.length() == 0)
|
||||
{
|
||||
throw PiPedalException("Malformed request.");
|
||||
@@ -588,7 +598,7 @@ public:
|
||||
if (extensionChecker.IsValidExtension(extension))
|
||||
{
|
||||
auto si = zipFile->GetFileInputStream(inputFile);
|
||||
std::string path = this->model->UploadUserFile(directory, patchProperty, inputFile, si, zipFile->GetFileSize(inputFile));
|
||||
std::string path = this->model->UploadUserFile(directory, instanceId,patchProperty, inputFile, si, zipFile->GetFileSize(inputFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -606,7 +616,7 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
outputFileName = this->model->UploadUserFile(directory, 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())
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* Copyright (c) 2023 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.
|
||||
*/
|
||||
|
||||
#ifndef FILEBROWSERFILES_H
|
||||
#define FILEBROWSERFILES_H
|
||||
|
||||
#define LV2_FILEBROWSER_URI "http://two-play.com/ns/ext/fileBrowser"
|
||||
#define LV2_FILEBROWSER_PREFIX LV2_FILEBROWSER_URI "#"
|
||||
#define LV2_FILEBROWSER__files LV2_FILEBROWSER_PREFIX "files" ///< http://two-play.com/ns/ext/fileBrowser#files >
|
||||
|
||||
/**
|
||||
@defgroup filedialog LV2_FileBrowser_Files
|
||||
@ingroup lv2
|
||||
|
||||
Because PiPedal is accessed remotely through a web interface, audio and model files
|
||||
have to be uploaded into standardized directories using the Upload File button, found in
|
||||
the File Property Dialog. Plugins that run locally can place sample and model files
|
||||
more casually, since users can browse anywhere on their computer using the system
|
||||
file dialog.
|
||||
|
||||
This LV2 Extension allows plugin developers to determine the location of well-known directory.
|
||||
|
||||
The extension addresses three primary uses-cases:
|
||||
|
||||
- Allow plugins to published factory-installed resource files that can be
|
||||
selected using the Patch Property file dialog. Typically. Source files would be
|
||||
stored in the bundle directory of the plugin at install time. On first use, the
|
||||
plugin would create symlinks in the well-known directory that reference files in the
|
||||
bundle directory.
|
||||
|
||||
- Allow plugins to reference factory-installed resource files that can be selected using
|
||||
the Patch Property file dialog.
|
||||
|
||||
- Allow recording plugins to locate the directory in which new files should be
|
||||
placed.
|
||||
|
||||
The plugin recognizes the following well-known directory locations, which are based on MOD fileType
|
||||
conventions.
|
||||
|
||||
"audioloop"
|
||||
Audio Loops, meant to be used for looper-style plugins.
|
||||
"audiorecording",
|
||||
Audio Recordings, triggered by plugins and stored in the unit
|
||||
"audiosample",
|
||||
One-shot Audio Samples, meant to be used for sampler-style plugins
|
||||
"audiotrack"
|
||||
Audio Tracks, meant to be used as full-performance/song or backtrack
|
||||
"cabsim"
|
||||
Impulse response files for convolution-based Cabinet response simulation.
|
||||
"h2drumkit"
|
||||
Hydrogen Drumkits, must use h2drumkit file extension. Not currently supported by PiPedal.
|
||||
"ir"
|
||||
Impulse response files of arbitrary size. May be either CAB IR files, or Reverbe IR files.
|
||||
"midiclip"
|
||||
MIDI Clips", {".mid", ".midi"}}, // MIDI Clips, to be used in sync with
|
||||
host tempo, must have mid or midi file extension
|
||||
"midisong"
|
||||
MIDI Songs", {".mid", ".midi"}}, meant to be used as
|
||||
full-performance/song or backtrack.
|
||||
"sf2"
|
||||
SF2 Instruments, must have sf2 or sf3 file extension
|
||||
"sfz"
|
||||
SFZ Instruments, must have sfz file extension
|
||||
"audio"
|
||||
Parent directory of all audio file directories. Not officially documented
|
||||
by MOD, but has been observed in the field.
|
||||
"nammodel"
|
||||
.nam files: Nueral amp model's for Steven Atkin's Neural Amp Modeler library.
|
||||
"aidadspmodel"
|
||||
AIDA IAX model files. (file extensions of ".json")
|
||||
"mlmodel"
|
||||
Model files for plugins based on the ML neural net library. (file
|
||||
extensions of ".json"). TooB ML, Proteus, probably others.
|
||||
"~"
|
||||
References a directory that is for private use by only the current plugin.
|
||||
Users can upload and select files in the private directory when using
|
||||
the current plugin, but no other plugins can access the files. (Not
|
||||
supported by MOD).
|
||||
|
||||
|
||||
Methods are are safe on the realtime thread, as they perform file i/o and allocate memory. This feature should be used
|
||||
at initialization time, in the call to Activate, or else on a scheduler thread.
|
||||
|
||||
@{
|
||||
*/
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
LV2_FileBrowser_Status_Success = 0,
|
||||
LV2_FileBrowser_Status_Err_InvalidParameter = 1,
|
||||
LV2_FileBrowser_Status_Err_Filesystem = 2,
|
||||
} LV2_FileBrowser_Status;
|
||||
|
||||
typedef void*
|
||||
LV2_FileBrowser_Files_Handle; ///< Opaque handle for pipedal:uploadPath feature
|
||||
|
||||
|
||||
/**
|
||||
Feature data for filedialog:files (@ref LV2_FILEBROWSER_file).
|
||||
*/
|
||||
typedef struct {
|
||||
/**
|
||||
Opaque host data.
|
||||
*/
|
||||
LV2_FileBrowser_Files_Handle handle;
|
||||
|
||||
/**
|
||||
Publish a resource file provided by the plugin to a well-known directory location.
|
||||
|
||||
@param handle MUST be the `handle` member of this struct.
|
||||
|
||||
@param version The version number of the resource file.
|
||||
|
||||
@param resourcePath The source filename./
|
||||
|
||||
@param uploadDirectory The target filename.
|
||||
|
||||
@return LV2_FileBrowser_Status Zero if successful.
|
||||
|
||||
The version number is the version number of the resource file(s). The host keeps track of which
|
||||
resource files have previously been installed, and the version thereof. Files will only be
|
||||
published once. This allows users to delete published files if they want to; and allows
|
||||
upgraded plugins to write upgraded resource files.
|
||||
|
||||
`resourcePath` is specified relative to the plugin's bundle directory. You can supply an
|
||||
absolute path if you want, but it's generally better to place resource files in the plugin's
|
||||
bundle, and specify the location with a relative path. If the resourcePath specifies a file,
|
||||
only the file will be copied; if the the path specifies a directory, all files and subdirectories
|
||||
will be published.
|
||||
|
||||
`uploadDirectory` most be a relative path of a directory, and the first segment of the path must be one of
|
||||
the well-known directory locations.
|
||||
|
||||
A return code of LV2_FileBrowser_Status_Err_InvalidParameter indicates
|
||||
either that the well-known directory was not found, or that an absolute
|
||||
path was provided where a relative path was expected.
|
||||
LV2_FileBrowser_Status_Err_Filesystem will be return if some kind of
|
||||
catastrophic filesystem error occurred (out of disk space, perhaps, or
|
||||
permission denied).
|
||||
|
||||
Example:
|
||||
[code]
|
||||
// becomes /usr/lib/lv2/ToobAmp.lv2/namFactoryModels/FenderBlackface.nam (for example)
|
||||
const char*sourceFile = "namFactoryModels/Fender Blackface.nam"
|
||||
|
||||
// /becomes /var/pipedal/audio_uploads/NeuralAmpModels/TooB NAM (for example)
|
||||
const char(*targetDirectory = "namModel/TooB NAM")
|
||||
|
||||
Lv2_FileBrowserStatus ec =
|
||||
feature.publish_resource_files(feature.handle,1,sourceFile,targetDirectory);
|
||||
|
||||
// on success creates a symlink at
|
||||
// /var/pipedal/audio_uploads/NeuralAmpModels/TooB NAM/Fender Blackface.nam
|
||||
[/code]
|
||||
*/
|
||||
|
||||
LV2_FileBrowser_Status (*publish_resource_files)(
|
||||
LV2_FileBrowser_Files_Handle handle,
|
||||
uint32_t version,
|
||||
const char*resourcePath,
|
||||
const char*uploadDirectory);
|
||||
|
||||
/**
|
||||
ccc
|
||||
|
||||
@param handle MUST be the `handle` member of this struct.
|
||||
|
||||
@param path A relative path, starting with a well-known directory name.
|
||||
|
||||
@return The absolute path to use for the new directory.
|
||||
|
||||
This function can be used by plugins to get the path of files or directories
|
||||
that can be browsed by file dialogs. get_filebrowser_path, unlike
|
||||
map_path() does not create or modify files or directories in any way.
|
||||
|
||||
The path can have multiple segments, but the first segment of the path must be
|
||||
the name of a well-known directory.
|
||||
|
||||
The caller must free memory for the returned value with
|
||||
LV2_FileBrowser_Files.free_path().
|
||||
|
||||
Example:
|
||||
[code]
|
||||
const char *filePath = feature.get_upload_path(feature.handle,"audio/Tracks/Xmas/Frosty.wav");
|
||||
// returns (for example)
|
||||
// /var/pipedal/audio_uploads/shared/audio/Tracks/Xmas/Frosty.wav
|
||||
[/code]
|
||||
*/
|
||||
char* (*get_upload_path)(LV2_FileBrowser_Files_Handle handle, const char* path);
|
||||
|
||||
/**
|
||||
Map resource bundle filenames to upload directories.
|
||||
@param handle MUST be the `handle` member of this struct.
|
||||
@param path File path name
|
||||
@param fileBrowserDirectory relative directory of the file browser directory in which to create a link.
|
||||
@return The absolute pathname of a file in the resource directory.
|
||||
|
||||
The primary purpose of this method is to map file references to files in the plugin's bundle
|
||||
directory to corresponding files in the a file browser directory.
|
||||
|
||||
If the path is relative, the full path is resolved using the `rootResourceDirectory` path.
|
||||
|
||||
If the full path is not a child of rootResourceDirectory or if the path is relative,
|
||||
get_upload_path returns the supplied path unmodified.
|
||||
`
|
||||
If the filename is a child of rootResourceDirectory, creates a corresponding link to the file in the file browser
|
||||
directory and returns the path of that link.
|
||||
|
||||
The caller must free memory for the returned value with `LV2_FileBrowser_Files.free_path()`.
|
||||
|
||||
Example:
|
||||
*/
|
||||
char* (*map_path)(LV2_FileBrowser_Files_Handle handle, const char* path, const char*rootResourceDirectory, const char*fileBrowserDirectory);
|
||||
|
||||
|
||||
/**
|
||||
Free a path returned by LV2_FileBrowser_Files.get_upload_path() or LV2_FileBrowser_Files.map_path()
|
||||
@param handle MUST be the `handle` member of this struct.
|
||||
@param path A path previously returned by a method of `LV2_FileBrowser_Files`.
|
||||
|
||||
*/
|
||||
void (*free_path)(LV2_FileBrowser_Files_Handle handle, char* path);
|
||||
} LV2_FileBrowser_Files;
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // FILEBROWSERFILES_H
|
||||
Reference in New Issue
Block a user