Shared upload directories

This commit is contained in:
Robin Davies
2024-10-27 19:47:05 -04:00
parent ae7e6f5abe
commit 6bff83604d
19 changed files with 1367 additions and 766 deletions
+2
View File
@@ -157,6 +157,7 @@ else()
endif()
set (PIPEDAL_SOURCES
ModFileTypes.cpp ModFileTypes.hpp
PatchPropertyWriter.hpp
PresetBundle.cpp PresetBundle.hpp
RealtimeMidiEventType.hpp
@@ -637,6 +638,7 @@ add_executable(pipedalconfig
CommandLineParser.hpp
PiPedalException.hpp
ConfigMain.cpp
ModFileTypes.cpp ModFileTypes.hpp
PiPedalConfiguration.hpp PiPedalConfiguration.cpp
JackServerSettings.hpp JackServerSettings.cpp
SystemConfigFile.hpp SystemConfigFile.cpp
+4
View File
@@ -23,6 +23,7 @@
#include <unistd.h>
#include "CommandLineParser.hpp"
#include "SystemConfigFile.hpp"
#include "ModFileTypes.hpp"
#include <filesystem>
#include <stdlib.h>
@@ -951,6 +952,7 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
}
try
{
// apply policy changes we dropped into the polkit configuration files (allows pipedal_d to use NetworkManager dbus apis).
silentSysExec(SYSTEMCTL_BIN " restart polkit.service");
@@ -1158,6 +1160,8 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
sysExec(SYSTEMCTL_BIN " daemon-reload");
FixPermissions();
ModFileTypes::CreateDefaultDirectories("/var/pipedal/audio_uploads");
StopService(false);
AvahiInstall();
+15 -2
View File
@@ -22,7 +22,20 @@
using namespace pipedal;
JSON_MAP_BEGIN(FileEntry)
JSON_MAP_REFERENCE(FileEntry,filename)
JSON_MAP_REFERENCE(FileEntry,pathname)
JSON_MAP_REFERENCE(FileEntry,displayName)
JSON_MAP_REFERENCE(FileEntry,isProtected)
JSON_MAP_REFERENCE(FileEntry,isDirectory)
JSON_MAP_END()
JSON_MAP_BEGIN(BreadcrumbEntry)
JSON_MAP_REFERENCE(BreadcrumbEntry,pathname)
JSON_MAP_REFERENCE(BreadcrumbEntry,displayName)
JSON_MAP_END()
JSON_MAP_BEGIN(FileRequestResult)
JSON_MAP_REFERENCE(FileRequestResult,files)
JSON_MAP_REFERENCE(FileRequestResult,isProtected)
JSON_MAP_REFERENCE(FileRequestResult,breadcrumbs)
JSON_MAP_END()
+21 -3
View File
@@ -26,15 +26,33 @@ namespace pipedal {
class FileEntry {
public:
FileEntry() { }
FileEntry(std::string filename,bool isDirectory)
:filename_(filename), isDirectory_(isDirectory)
FileEntry(const std::string&pathname,const std::string &displayName,bool isDirectory, bool isProtected = false)
:pathname_(pathname), displayName_(displayName), isDirectory_(isDirectory),isProtected_(isProtected)
{
}
std::string filename_;
std::string pathname_;
std::string displayName_;
bool isDirectory_ = false;
bool isProtected_ = false;
DECLARE_JSON_MAP(FileEntry);
};
class BreadcrumbEntry {
public:
std::string pathname_;
std::string displayName_;
DECLARE_JSON_MAP(BreadcrumbEntry);
};
class FileRequestResult {
public:
std::vector<FileEntry> files_;
bool isProtected_ = false;
std::vector<BreadcrumbEntry> breadcrumbs_;
DECLARE_JSON_MAP(FileRequestResult);
};
}
+156
View File
@@ -0,0 +1,156 @@
// Copyright (c) 2024 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "ModFileTypes.hpp"
#include "util.hpp"
#include <stdexcept>
#include <iostream>
#include "ss.hpp"
using namespace pipedal;
const std::vector<ModFileTypes::ModDirectory> ModFileTypes::ModDirectories =
{
{"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
{"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
{"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
// 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
{"mlmodel", "ToobMlModels", "ML Models", {".json"}}, //
//
};
const ModFileTypes::ModDirectory *ModFileTypes::GetModDirectory(const std::string &type)
{
for (const ModFileTypes::ModDirectory &modType : ModFileTypes::ModDirectories)
{
if (modType.modType == type)
{
return &modType;
}
}
return nullptr;
}
ModFileTypes::ModFileTypes(const std::string &fileTypes)
{
std::vector<std::string> types = split(fileTypes, ',');
for (const auto &type : types)
{
const ModDirectory *wellKnownType = GetModDirectory(type);
if (wellKnownType)
{
rootDirectories_.push_back(type);
}
else
{
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(
const std::filesystem::path &rootDirectory,
const std::string &modType)
{
auto wellKnownType = ModFileTypes::GetModDirectory(modType);
if (!wellKnownType)
{
throw std::runtime_error(SS("Can't find modFileType " << modType));
}
return rootDirectory / wellKnownType->pipedalPath;
}
void ModFileTypes::CreateDefaultDirectories(const std::filesystem::path &rootDirectory)
{
namespace fs = std::filesystem;
using namespace std;
try
{
for (const auto &modType : ModDirectories)
{
fs::path path = rootDirectory / modType.pipedalPath;
fs::create_directories(path);
}
if (!fs::exists(rootDirectory / "shared" / "audio" / "Cab IR Files"))
{
fs::create_symlink(
getModDirectoryPath(rootDirectory, "cabsim"),
rootDirectory / "shared" / "audio" / "Cab IR Files");
}
if (!fs::exists(rootDirectory / "shared" / "audio" / "Impulse Responses"))
{
fs::create_symlink(
getModDirectoryPath(rootDirectory, "ir"),
rootDirectory / "shared" / "audio" / "Impulse Responses");
}
}
catch (const std::exception &e)
{
cout << "Can't create default MOD directories. " << e.what() << endl;
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright (c) 2024 Robin Davies
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#pragma once
#include <string>
#include <vector>
#include <filesystem>
namespace pipedal {
class ModFileTypes {
public:
ModFileTypes(const std::string&fileTypes);
const std::vector<std::string> &rootDirectories() { return rootDirectories_; }
const std::vector<std::string> &fileTypes() { return fileTypes_;}
private:
std::vector<std::string> rootDirectories_;
std::vector<std::string> fileTypes_;
public:
class ModDirectory {
public:
const std::string modType;
const std::string pipedalPath;
const std::string displayName;
const std::vector<std::string> defaultFileExtensions;
};
const static std::vector<ModDirectory> ModDirectories;
static const ModDirectory* GetModDirectory(const std::string&modType);
static void CreateDefaultDirectories(const std::filesystem::path&rootDirectory);
};
}
+2 -2
View File
@@ -2296,7 +2296,7 @@ std::vector<std::string> PiPedalModel::GetFileList(const UiFileProperty &filePro
return std::vector<std::string>(); // don't disclose to users what the problem is.
}
}
std::vector<FileEntry> PiPedalModel::GetFileList2(const std::string &relativePath, const UiFileProperty &fileProperty)
FileRequestResult PiPedalModel::GetFileList2(const std::string &relativePath, const UiFileProperty &fileProperty)
{
try
{
@@ -2305,7 +2305,7 @@ std::vector<FileEntry> PiPedalModel::GetFileList2(const std::string &relativePat
catch (const std::exception &e)
{
Lv2Log::warning("GetFileList() failed: (%s)", e.what());
return std::vector<FileEntry>(); // don't disclose to users what the problem is.
throw;
}
}
+1 -1
View File
@@ -432,7 +432,7 @@ namespace pipedal
void SetUpdatePolicy(UpdatePolicyT updatePolicy);
void ForceUpdateCheck();
std::vector<std::string> GetFileList(const UiFileProperty &fileProperty);
std::vector<FileEntry> GetFileList2(const std::string &relativePath, 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);
+2 -2
View File
@@ -1589,8 +1589,8 @@ public:
{
FileRequestArgs requestArgs;
pReader->read(&requestArgs);
std::vector<FileEntry> list = this->model.GetFileList2(requestArgs.relativePath_, requestArgs.fileProperty_);
this->Reply(replyTo, "requestFileList2", list);
FileRequestResult result = this->model.GetFileList2(requestArgs.relativePath_, requestArgs.fileProperty_);
this->Reply(replyTo, "requestFileList2", result);
}
else if (message == "newPreset")
{
+2
View File
@@ -465,6 +465,8 @@ JSON_MAP_REFERENCE(UiFileProperty, patchProperty)
JSON_MAP_REFERENCE(UiFileProperty, fileTypes)
JSON_MAP_REFERENCE(UiFileProperty, portGroup)
JSON_MAP_REFERENCE(UiFileProperty, resourceDirectory)
JSON_MAP_REFERENCE(UiFileProperty, modDirectories)
JSON_MAP_REFERENCE(UiFileProperty, useLegacyModDirectory)
JSON_MAP_END()
JSON_MAP_BEGIN(UiFrequencyPlot)
+10
View File
@@ -114,6 +114,8 @@ namespace pipedal {
std::string patchProperty_;
std::string portGroup_;
std::string resourceDirectory_;
std::vector<std::string> modDirectories_;
bool useLegacyModDirectory_= false;
public:
using ptr = std::shared_ptr<UiFileProperty>;
UiFileProperty() { }
@@ -121,9 +123,17 @@ namespace pipedal {
UiFileProperty(const std::string&name, const std::string&patchProperty,const std::string &directory);
std::vector<std::string>& modDirectories() { return modDirectories_; }
const std::vector<std::string>& modDirectories() const { return modDirectories_; }
bool useLegacyModDirectory() const { return useLegacyModDirectory_; }
void useLegacyModDirectory(bool value) { useLegacyModDirectory_ = value; }
const std::string &label() const { return label_; }
int32_t index() const { return index_; }
const std::string &directory() const { return directory_; }
void directory(const std::string &path) { directory_ = path; }
const std::string&portGroup() const { return portGroup_; }
const std::vector<UiFileType> &fileTypes() const { return fileTypes_; }
+90 -55
View File
@@ -46,6 +46,7 @@
#include "PiPedalException.hpp"
#include "StdErrorCapture.hpp"
#include "util.hpp"
#include "ModFileTypes.hpp"
#include "Locale.hpp"
@@ -118,7 +119,7 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
core__toggled = lilv_new_uri(pWorld, LV2_CORE__toggled);
core__connectionOptional = lilv_new_uri(pWorld, LV2_CORE__connectionOptional);
portprops__not_on_gui_property_uri = lilv_new_uri(pWorld, LV2_PORT_PROPS__notOnGUI);
portprops__trigger = lilv_new_uri(pWorld,LV2_PORT_PROPS__trigger);
portprops__trigger = lilv_new_uri(pWorld, LV2_PORT_PROPS__trigger);
midi__event = lilv_new_uri(pWorld, LV2_MIDI__MidiEvent);
core__designation = lilv_new_uri(pWorld, LV2_CORE__designation);
portgroups__group = lilv_new_uri(pWorld, LV2_PORT_GROUPS__group);
@@ -192,8 +193,7 @@ 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");
mod__fileTypes = lilv_new_uri(pWorld, "http://moddevices.com/ns/mod#fileTypes");
}
void PluginHost::LilvUris::Free()
@@ -478,8 +478,7 @@ void PluginHost::Load(const char *lv2Path)
const char *pb1 = left->name().c_str();
const char *pb2 = right->name().c_str();
return collator->Compare(
left->name(),right->name())
< 0;
left->name(), right->name()) < 0;
};
std::sort(this->plugins_.begin(), this->plugins_.end(), compare);
@@ -625,15 +624,47 @@ std::shared_ptr<PiPedalUI> Lv2PluginInfo::FindWritablePathProperties(PluginHost
strLabel, propertyUri.AsUri(), lv2DirectoryName);
AutoLilvNodes mod__fileTypes = lilv_world_find_nodes(pWorld, propertyUri, lv2Host->lilvUris->mod__fileTypes, nullptr);
LILV_FOREACH(nodes,i,mod__fileTypes)
LILV_FOREACH(nodes, i, mod__fileTypes)
{
// "nam,nammodel"
AutoLilvNode lilvfileType {lilv_nodes_get(mod__fileTypes, i)};
// "nam,nammodel"
AutoLilvNode lilvfileType{lilv_nodes_get(mod__fileTypes, i)};
std::string fileTypes = lilvfileType.AsString();
ModFileTypes modFileTypes(fileTypes);
for (std::string&type: split(fileTypes,','))
for (const std::string &rootDirectory : modFileTypes.rootDirectories())
{
fileProperty->fileTypes().push_back(UiFileType(SS(type << " file"),SS('.' << type)));
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)
@@ -933,7 +964,7 @@ Lv2PortInfo::Lv2PortInfo(PluginHost *host, const LilvPlugin *plugin, const LilvP
this->toggled_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->core__toggled);
this->not_on_gui_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__not_on_gui_property_uri);
this->connection_optional_ = lilv_port_has_property(plugin, pPort, host->lilvUris->core__connectionOptional);
this->trigger_property_ = lilv_port_has_property(plugin,pPort,host->lilvUris->portprops__trigger);
this->trigger_property_ = lilv_port_has_property(plugin, pPort, host->lilvUris->portprops__trigger);
LilvScalePoints *pScalePoints = lilv_port_get_scale_points(plugin, pPort);
LILV_FOREACH(scale_points, iSP, pScalePoints)
@@ -1389,33 +1420,36 @@ std::shared_ptr<HostWorkerThread> PluginHost::GetHostWorkerThread()
return pHostWorkerThread;
}
class ResourceInfo {
class ResourceInfo
{
public:
ResourceInfo(const std::string&filePropertyDirectory,const std::string&resourceDirectory)
:filePropertyDirectory(filePropertyDirectory), resourceDirectory(resourceDirectory) {}
ResourceInfo(const std::string &filePropertyDirectory, const std::string &resourceDirectory)
: filePropertyDirectory(filePropertyDirectory), resourceDirectory(resourceDirectory) {}
std::string filePropertyDirectory;
std::string resourceDirectory;
bool operator==(const ResourceInfo&other) const {
bool operator==(const ResourceInfo &other) const
{
return this->filePropertyDirectory == other.filePropertyDirectory && this->resourceDirectory == other.resourceDirectory;
}
bool operator<(const ResourceInfo&other) const {
bool operator<(const ResourceInfo &other) const
{
if (this->filePropertyDirectory < other.filePropertyDirectory)
return true;
if (this->filePropertyDirectory > other.filePropertyDirectory)
return false;
return this->resourceDirectory < other.resourceDirectory;
return this->resourceDirectory < other.resourceDirectory;
}
};
static bool anyTargetFilesExist(const std::filesystem::path &sourceDirectory, const std::filesystem::path&targetDirectory)
static bool anyTargetFilesExist(const std::filesystem::path &sourceDirectory, const std::filesystem::path &targetDirectory)
{
namespace fs = std::filesystem;
try {
if (!fs::exists(targetDirectory)) return false;
for (auto&directoryEntry : fs::directory_iterator(sourceDirectory))
try
{
if (!fs::exists(targetDirectory))
return false;
for (auto &directoryEntry : fs::directory_iterator(sourceDirectory))
{
fs::path thisPath = directoryEntry.path();
if (directoryEntry.is_directory())
@@ -1423,11 +1457,13 @@ static bool anyTargetFilesExist(const std::filesystem::path &sourceDirectory, co
auto name = thisPath.filename();
fs::path childSource = sourceDirectory / name;
fs::path childTarget = targetDirectory / name;
if (anyTargetFilesExist(childSource,childTarget))
if (anyTargetFilesExist(childSource, childTarget))
{
return true;
}
} else {
}
else
{
fs::path targetPath = targetDirectory / thisPath.filename();
if (fs::exists(targetPath))
{
@@ -1435,9 +1471,9 @@ static bool anyTargetFilesExist(const std::filesystem::path &sourceDirectory, co
}
}
}
} catch (const std::exception&) {
}
catch (const std::exception &)
{
}
return false;
}
@@ -1452,48 +1488,52 @@ static void createTargetLinks(const std::filesystem::path &sourceDirectory, cons
{
fs::path childSource = dirEntry.path();
fs::path childTarget = targetDirectory / childSource.filename();
if (dirEntry.is_directory()) {
createTargetLinks(childSource,childTarget);
} else {
fs::create_symlink(childSource,childTarget);
if (dirEntry.is_directory())
{
createTargetLinks(childSource, childTarget);
}
else
{
fs::create_symlink(childSource, childTarget);
}
}
}
void PluginHost::CheckForResourceInitialization(const std::string &pluginUri,const std::filesystem::path&pluginUploadDirectory)
void PluginHost::CheckForResourceInitialization(const std::string &pluginUri, const std::filesystem::path &pluginUploadDirectory)
{
auto plugin = GetPluginInfo(pluginUri);
if (plugin)
{
std::filesystem::path bundlePath = plugin->bundle_path();
if (!plugin->piPedalUI())
if (!plugin->piPedalUI())
return;
const auto& fileProperties = plugin->piPedalUI()->fileProperties();
const auto &fileProperties = plugin->piPedalUI()->fileProperties();
if (fileProperties.size() != 0 && !pluginsThatHaveBeenCheckedForResources.contains(pluginUri))
{
pluginsThatHaveBeenCheckedForResources.insert(pluginUri);
// eliminate duplicates.
std::set<ResourceInfo> resourceInfoSet;
for (const auto&fileProperty: fileProperties)
for (const auto &fileProperty : fileProperties)
{
if (!fileProperty->resourceDirectory().empty() && !fileProperty->directory().empty())
{
resourceInfoSet.insert(ResourceInfo(fileProperty->directory(),fileProperty->resourceDirectory()));
resourceInfoSet.insert(ResourceInfo(fileProperty->directory(), fileProperty->resourceDirectory()));
}
}
try {
for (const ResourceInfo&resourceInfo: resourceInfoSet)
try
{
std::filesystem::path sourcePath = bundlePath / resourceInfo.resourceDirectory;
std::filesystem::path targetPath = pluginUploadDirectory / resourceInfo.filePropertyDirectory;
if (!anyTargetFilesExist(sourcePath,targetPath))
for (const ResourceInfo &resourceInfo : resourceInfoSet)
{
createTargetLinks(sourcePath,targetPath);
std::filesystem::path sourcePath = bundlePath / resourceInfo.resourceDirectory;
std::filesystem::path targetPath = pluginUploadDirectory / resourceInfo.filePropertyDirectory;
if (!anyTargetFilesExist(sourcePath, targetPath))
{
createTargetLinks(sourcePath, targetPath);
}
}
}
} catch (const std::exception &e)
catch (const std::exception &e)
{
Lv2Log::error(SS("CheckForResourceInitialization: " << e.what()));
}
@@ -1501,20 +1541,17 @@ void PluginHost::CheckForResourceInitialization(const std::string &pluginUri,con
}
}
json_variant PluginHost::MapPath(const json_variant&json)
json_variant PluginHost::MapPath(const json_variant &json)
{
AtomConverter converter(GetMapFeature());
return converter.MapPath(json,GetPluginStoragePath());
return converter.MapPath(json, GetPluginStoragePath());
}
json_variant PluginHost::AbstractPath(const json_variant&json)
json_variant PluginHost::AbstractPath(const json_variant &json)
{
AtomConverter converter(GetMapFeature());
return converter.AbstractPath(json,GetPluginStoragePath());
return converter.AbstractPath(json, GetPluginStoragePath());
}
std::string PluginHost::MapPath(const std::string &abstractPath)
{
auto storagePath = GetPluginStoragePath();
@@ -1523,10 +1560,8 @@ std::string PluginHost::MapPath(const std::string &abstractPath)
return "";
}
return SS(storagePath << '/' << abstractPath);
}
std::string PluginHost::AbstractPath(const std::string&path)
std::string PluginHost::AbstractPath(const std::string &path)
{
auto storagePath = GetPluginStoragePath();
if (path.starts_with(storagePath))
+203 -36
View File
@@ -22,6 +22,7 @@
#include "Storage.hpp"
#include "AudioConfig.hpp"
#include "PiPedalException.hpp"
#include <stdexcept>
#include <sstream>
#include "json.hpp"
#include <fstream>
@@ -32,14 +33,44 @@
#include "PluginHost.hpp"
#include "ss.hpp"
#include "ofstream_synced.hpp"
#include "ModFileTypes.hpp"
using namespace pipedal;
namespace fs = std::filesystem;
const char *BANK_EXTENSION = ".bank";
const char *BANKS_FILENAME = "index.banks";
#define USER_SETTINGS_FILENAME "userSettings.json";
static bool isSubdirectory(const fs::path&path, const fs::path&basePath)
{
auto iPath = path.begin();
for (auto i = basePath.begin(); i != basePath.end(); ++i)
{
if (iPath == path.end())
{
return false;
}
if ((*i) != (*iPath))
{
return false;
}
++iPath;
}
while (iPath != path.end())
{
if (iPath->string() == "..")
{
return false;
}
++iPath;
}
return true;
}
Storage::Storage()
{
SetConfigRoot("~/var/Config");
@@ -1648,63 +1679,190 @@ static bool ensureNoDotDot(const std::filesystem::path&path)
return true;
}
std::vector<FileEntry> Storage::GetFileList2(const std::string &relativePath,const UiFileProperty &fileProperty)
static void AddFilesToResult(
FileRequestResult&result,
const UiFileProperty &fileProperty,
const fs::path&rootPath)
{
if (!ensureNoDotDot(relativePath))
if (!fs::exists(rootPath))
{
ThrowPermissionDeniedError();
return; // silently without error.
}
if (!UiFileProperty::IsDirectoryNameValid(fileProperty.directory()))
auto & resultFiles = result.files_;
try
{
ThrowPermissionDeniedError();
}
std::vector<FileEntry> 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() / relativePath;
try
for (auto const &dir_entry : std::filesystem::directory_iterator(rootPath))
{
for (auto const &dir_entry : std::filesystem::directory_iterator(audioFileDirectory))
const auto &path = dir_entry.path();
auto name = path.filename().string();
if (dir_entry.is_regular_file())
{
if (dir_entry.is_regular_file())
if (name.length() > 0 && name[0] != '.') // don't show hidden files.
{
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()))
{
if (fileProperty.IsValidExtension(path.extension().string()))
{
// a relative path!
result.push_back(FileEntry(path,false));
}
resultFiles.push_back(
FileEntry(path,name,false,false)
);
}
} else if (dir_entry.is_directory()) {
result.push_back(FileEntry(dir_entry.path(),true));
}
} else if (dir_entry.is_directory()) {
resultFiles.push_back(FileEntry{path,name,true,fs::is_symlink(path)});
}
}
catch (const std::exception &error)
{
throw std::logic_error("GetFileList failed. Directory not found: " + audioFileDirectory.string());
}
}
catch (const std::exception &error)
{
throw std::logic_error("GetFileList failed. Directory not found: " + rootPath.string());
}
// sort lexicographically
auto collator = Locale::GetInstance()->GetCollator();
std::sort(result.begin(), result.end(),[&collator](const FileEntry&l, const FileEntry&r) {
std::sort(resultFiles.begin(), resultFiles.end(),[&collator](const FileEntry&l, const FileEntry&r) {
if (l.isDirectory_ != r.isDirectory_)
{
return l.isDirectory_ > r.isDirectory_;
}
return collator->Compare(l.filename_,r.filename_) < 0;
return collator->Compare(l.displayName_,r.displayName_) < 0;
});
}
FileRequestResult Storage::GetModFileList2(const std::string &relativePath,const UiFileProperty &fileProperty)
{
FileRequestResult result;
fs::path uploadsDirectory = GetPluginUploadDirectory();
if (relativePath.empty())
{
// return the synthetic root.
result.isProtected_ = true;
for (const auto&modDirectory: fileProperty.modDirectories())
{
const auto directoryInfo = ModFileTypes::GetModDirectory(modDirectory);
if (directoryInfo)
{
result.files_.push_back(
FileEntry(uploadsDirectory / directoryInfo->pipedalPath,directoryInfo->displayName,true,true)
);
}
}
if (fileProperty.useLegacyModDirectory())
{
result.files_.push_back(
FileEntry(uploadsDirectory / fileProperty.directory(),fs::path(fileProperty.directory()).filename().string(),true,true)
);
}
result.breadcrumbs_.push_back({"","Home"});
return result;
}
fs::path modDirectoryPath;
result.breadcrumbs_.push_back({"","Home"});
fs::path fsRelativePath { relativePath};
for (const auto &modDirectory: fileProperty.modDirectories())
{
const ModFileTypes::ModDirectory*modDirectoryInfo = ModFileTypes::GetModDirectory(modDirectory);
if (modDirectoryInfo)
{
if (isSubdirectory(fsRelativePath, uploadsDirectory / modDirectoryInfo->pipedalPath))
{
modDirectoryPath = uploadsDirectory / modDirectoryInfo->pipedalPath;
result.breadcrumbs_.push_back({modDirectoryPath.string(),modDirectoryInfo->displayName});
break;
}
}
}
if (modDirectoryPath.empty() && fileProperty.useLegacyModDirectory())
{
if (isSubdirectory(fsRelativePath, uploadsDirectory / fileProperty.directory()))
{
modDirectoryPath = uploadsDirectory / fileProperty.directory();
result.breadcrumbs_.push_back({modDirectoryPath.string(),fs::path(fileProperty.directory()).filename().string()});
} else {
ThrowPermissionDeniedError();
}
}
// add remaing path segements as breadcrumbs.
{
fs::path modPath {modDirectoryPath};
fs::path rp { relativePath};
auto iRp = rp.begin();
// skip past one or more segments in the modDiretoryPath.
for (auto iModPath = modPath.begin(); iModPath != modPath.end(); ++iModPath)
{
if (iRp != rp.end()) {
++iRp;
}
}
while (iRp != rp.end())
{
result.breadcrumbs_.push_back({*iRp,*iRp});
++iRp;
}
}
AddFilesToResult(result,fileProperty,relativePath);
return result;
}
FileRequestResult Storage::GetFileList2(const std::string &relativePath_,const UiFileProperty &fileProperty)
{
std::string absolutePath = relativePath_;
if (!ensureNoDotDot(absolutePath))
{
ThrowPermissionDeniedError();
}
if (fileProperty.modDirectories().size() > 1 || (fileProperty.modDirectories().size() == 1 && fileProperty.useLegacyModDirectory()))
{
return Storage::GetModFileList2(absolutePath,fileProperty);
}
FileRequestResult result;
if (fileProperty.directory().empty())
{
throw std::runtime_error("fileProperty.directory() not specified.");
}
std::filesystem::path pluginRootDirectory = this->GetPluginUploadDirectory() / fileProperty.directory();
if (absolutePath == "")
{
absolutePath = pluginRootDirectory.string();
}
{
result.breadcrumbs_.push_back({"","Home"});
fs::path fsAbsolutePath {absolutePath};
auto iAbsolutePath = fsAbsolutePath.begin();
for (auto i = pluginRootDirectory.begin(); i != pluginRootDirectory.end(); ++i)
{
if (iAbsolutePath == fsAbsolutePath.end() || (*i) != *iAbsolutePath)
{
throw std::runtime_error("Directory is not a subdirectory of the plugin root directory.");
}
++iAbsolutePath;
}
auto cumulativePath = pluginRootDirectory;
while (iAbsolutePath != fsAbsolutePath.end())
{
cumulativePath /= (*iAbsolutePath);
result.breadcrumbs_.push_back({cumulativePath.string(),iAbsolutePath->string()});
++iAbsolutePath;
}
}
if (!isSubdirectory(absolutePath,pluginRootDirectory))
{
throw std::runtime_error(SS("Improper location. " << absolutePath));
}
AddFilesToResult(result,fileProperty,absolutePath);
return result;
}
@@ -1753,9 +1911,18 @@ void Storage::DeleteSampleFile(const std::filesystem::path &fileName)
{
if (std::filesystem::is_directory(fileName))
{
if (fileName.string().length() > 1) // guard against rm -rf / (bitter experience)
if (fileName.string().length() <= 1) // guard against rm -rf / (bitter experience)
{
std::filesystem::remove_all(fileName);
throw std::logic_error("Invalid filename.");
}
if (std::filesystem::is_symlink(fileName))
{
std::filesystem::remove(fileName);
} else {
if (fileName.string().length() > 1) // guard against rm -rf / (bitter experience)
{
std::filesystem::remove_all(fileName);
}
}
}
else {
+3 -1
View File
@@ -152,7 +152,9 @@ public:
int64_t DeleteBank(int64_t bankId);
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
std::vector<FileEntry> GetFileList2(const std::string&relativePath,const UiFileProperty&fileProperty);
FileRequestResult GetFileList2(const std::string&relativePath,const UiFileProperty&fileProperty);
FileRequestResult GetModFileList2(const std::string &relativePath,const UiFileProperty &fileProperty);
void SetJackChannelSelection(const JackChannelSelection&channelSelection);