Sync w/ build system

This commit is contained in:
Robin E. R. Davies
2025-06-16 02:21:54 -04:00
parent 13883f91ab
commit e893e95325
11 changed files with 553 additions and 79 deletions
+52 -2
View File
@@ -160,6 +160,16 @@ namespace
int32_t fromPosition,
int32_t toPosition) override;
virtual void SetFileMetadata(
const std::string &fileNameOnly,
const std::string &key,
const std::string &value) override;
virtual std::string GetFileMetadata(
const std::string &fileNameOnly,
const std::string &key) override;
private:
std::vector<DbFileInfo> QueryTracks();
@@ -242,12 +252,22 @@ static int DefaultedSortOrder(int track)
}
return track;
}
static bool isFolderArtwork(const std::string &name)
{
return name.ends_with(".jpg") || name.ends_with(".png");
}
static void SortDbFiles(std::vector<DbFileInfo> &dbFiles, Collator::ptr &collator)
{
std::sort(
dbFiles.begin(), dbFiles.end(),
[collator](const DbFileInfo &a, const DbFileInfo &b)
{
if (isFolderArtwork(a.fileName()) != isFolderArtwork(b.fileName()))
{
return isFolderArtwork(a.fileName()) < isFolderArtwork(b.fileName());
}
if (a.position() != b.position())
{
return DefaultedSortOrder(a.position()) < DefaultedSortOrder(b.position());
@@ -411,9 +431,8 @@ std::vector<DbFileInfo> AudioDirectoryInfoImpl::UpdateDbFiles()
else
{
// We have position info, so we need to update the position.
SortDbFiles(dbFiles, collator);
SortDbFiles(newFiles, collator);
dbFiles.insert(dbFiles.end(), newFiles.begin(), newFiles.end());
SortDbFiles(dbFiles, collator);
for (size_t i = 0; i < dbFiles.size(); ++i)
{
@@ -892,3 +911,34 @@ namespace pipedal
return IndexPathToShadowIndexPath(audioRootDirectory, path);
}
}
void AudioDirectoryInfoImpl::SetFileMetadata(
const std::string &fileNameOnly,
const std::string &key,
const std::string &value)
{
OpenAudioDb();
if (!audioFilesDb)
{
throw std::runtime_error("Directory is not writable.");
}
this->UpdateDbFiles();
audioFilesDb->SetFileExtraMetadata(fileNameOnly, key, value);
}
std::string AudioDirectoryInfoImpl::GetFileMetadata(
const std::string &fileNameOnly,
const std::string &key)
{
OpenAudioDb();
if (!audioFilesDb)
{
throw std::runtime_error("Directory is not writable.");
}
this->UpdateDbFiles(); // check for deletions.
return audioFilesDb->GetFileExtraMetadata(fileNameOnly, key);
}
+9
View File
@@ -95,6 +95,15 @@ namespace pipedal
virtual std::string GetNextAudioFile(const std::string &fileNameOnly) = 0;
virtual std::string GetPreviousAudioFile(const std::string &fileNameOnly) = 0;
virtual void SetFileMetadata(
const std::string &fileNameOnly,
const std::string &key,
const std::string &value) = 0;
virtual std::string GetFileMetadata(
const std::string &fileNameOnly,
const std::string &key) = 0;
virtual void MoveAudioFile(
const std::string &directory,
int32_t fromPosition,
+51
View File
@@ -191,6 +191,14 @@ void AudioFilesDb::CreateDb(const std::filesystem::path &dbPathName)
"thumbnail BLOB, "
"width INTEGER, "
"height INTEGER)");
db->exec("CREATE TABLE IF NOT EXISTS extraMetadata ("
"idExtra INTEGER PRIMARY KEY AUTOINCREMENT, "
"idFile INT64 NOT NULL, "
"key TEXT NOT NULL, "
"value TEXT NOT NULL, "
"FOREIGN KEY (idFile) REFERENCES files(idFile) ON DELETE CASCADE, "
"UNIQUE(idFile, key))");
}
catch (const SQLite::Exception &e)
{
@@ -501,3 +509,46 @@ void AudioFilesDb::UpdateFilePosition(
updateFileQuery->bind(2, idFile);
updateFileQuery->exec();
}
void AudioFilesDb::DeleteFileExtraMetadata(
const std::string &fileNameOnly,
const std::string &key)
{
auto deleteExtraMetadataQuery = std::make_unique<SQLite::Statement>(
*db,
"DELETE FROM extraMetadata WHERE idFile = (SELECT idFile FROM files WHERE fileName = ?) AND key = ?");
deleteExtraMetadataQuery->bind(1, fileNameOnly);
deleteExtraMetadataQuery->bind(2, key);
deleteExtraMetadataQuery->exec();
}
void AudioFilesDb::SetFileExtraMetadata(
const std::string &fileNameOnly,
const std::string &key,
const std::string &value)
{
auto setExtraMetadataQuery = std::make_unique<SQLite::Statement>(
*db,
"INSERT OR REPLACE INTO extraMetadata (idFile, key, value) "
"VALUES ((SELECT idFile FROM files WHERE fileName = ?), ?, ?)");
setExtraMetadataQuery->bind(1, fileNameOnly);
setExtraMetadataQuery->bind(2, key);
setExtraMetadataQuery->bind(3, value);
setExtraMetadataQuery->exec();
}
std::string AudioFilesDb::GetFileExtraMetadata(
const std::string &fileNameOnly,
const std::string &key)
{
auto getExtraMetadataQuery = std::make_unique<SQLite::Statement>(
*db,
"SELECT value FROM extraMetadata WHERE idFile = (SELECT idFile FROM files WHERE fileName = ?) AND key = ?");
getExtraMetadataQuery->bind(1, fileNameOnly);
getExtraMetadataQuery->bind(2, key);
if (getExtraMetadataQuery->executeStep())
{
return getExtraMetadataQuery->getColumn(0).getText();
}
return "";
}
+11
View File
@@ -110,6 +110,17 @@ namespace pipedal::impl {
int64_t idFile,
int32_t position);
void DeleteFileExtraMetadata(
const std::string &fileNameOnly,
const std::string &key);
void SetFileExtraMetadata(
const std::string &fileNameOnly,
const std::string &key,
const std::string &value);
std::string GetFileExtraMetadata(
const std::string &fileNameOnly,
const std::string &key);
private:
void CreateDb(const std::filesystem::path &dbPathName);
+1
View File
@@ -275,6 +275,7 @@ set (PIPEDAL_SOURCES
LogFeature.hpp LogFeature.cpp
Worker.hpp Worker.cpp
OptionsFeature.hpp OptionsFeature.cpp
FileMetadataFeature.hpp FileMetadataFeature.cpp
VuUpdate.hpp VuUpdate.cpp
Units.hpp Units.cpp
RingBuffer.hpp
+135
View File
@@ -0,0 +1,135 @@
// 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 "FileMetadataFeature.hpp"
#include "AudioFiles.hpp"
#include "Lv2Log.hpp"
#include "ss.hpp"
#include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h"
using namespace pipedal;
FileMetadataFeature::FileMetadataFeature()
{
feature.URI = PIPEDAL__FILE_METADATA_FEATURE;
feature.data = &interface;
interface.handle = (void *)this;
interface.setFileMetadata = &FileMetadataFeature::S_setFileMetadata;
interface.getFileMetadata = &FileMetadataFeature::S_getFileMetadata;
xxx publish the feature.
xxx restrict to tracks directory.
}
FileMetadataFeature::~FileMetadataFeature()
{
}
void FileMetadataFeature::Prepare(MapFeature &map)
{
this->mapFeature = &map;
}
PIPEDAL_FileMetadata_Status FileMetadataFeature::setFileMetadata(
const char *absolute_path,
LV2_URID key,
const char *fileMetadata)
{
const char *strKey = mapFeature->UridToString(key);
if (strKey == nullptr)
{
return PIPEDAL_FILE_METADATA_INVALID_KEY;
}
std::filesystem::path path{absolute_path};
if (!std::filesystem::exists(path))
{
return PIPEDAL_FILE_METADATA_INVALID_PATH; // File does not exist
}
if (!std::filesystem::is_regular_file(path))
{
return PIPEDAL_FILE_METADATA_INVALID_PATH; // Not a regular file
}
try {
AudioDirectoryInfo::Ptr audioDirectoryInfo = AudioDirectoryInfo::Create(path.parent_path());
audioDirectoryInfo->SetFileMetadata(path.filename(), strKey, fileMetadata);
} catch (const std::exception&e) {
Lv2Log::error(SS("Failed to create AudioDirectoryInfo for " << path << ": " << e.what()));
return PIPEDAL_FILE_METADATA_INVALID_PATH; // Failed to create AudioDirectoryInfo
}
return PIPEDAL_FILE_METADATA_SUCCESS; // TODO: Implement this
}
uint32_t FileMetadataFeature::getFileMetadata(
const char *absolute_path,
LV2_URID key,
char *fileMetadata,
uint32_t fileMetadataSize)
{
const char *strKey = mapFeature->UridToString(key);
if (strKey == nullptr)
{
return PIPEDAL_FILE_METADATA_INVALID_KEY;
}
std::filesystem::path path{absolute_path};
AudioDirectoryInfo::Ptr audioDirectoryInfo = AudioDirectoryInfo::Create(path.parent_path());
std::string metadata = audioDirectoryInfo->GetFileMetadata(path.filename(), strKey);
if (metadata.empty())
{
return 0; // No metadata found
}
size_t required = metadata.size() + 1;
if (required >= std::numeric_limits<uint32_t>::max())
{
return PIPEDAL_FILE_METADATA_ERR_UNKNOWNM; // Metadata too large
}
if (fileMetadata == nullptr || required > fileMetadataSize)
{
return static_cast<uint32_t>(required); // Return size needed including null terminator
}
if (fileMetadataSize < metadata.length() + 1)
{
return static_cast<uint32_t>(metadata.size() + 1); // Return size needed including null terminator
}
std::strncpy(fileMetadata, metadata.c_str(), fileMetadataSize);
return required;
}
PIPEDAL_FileMetadata_Status FileMetadataFeature::S_setFileMetadata(
PIPEDAL_FILE_METADATA_Handle handle,
const char *absolute_path,
LV2_URID key,
const char *fileMetadata)
{
return ((FileMetadataFeature *)handle)->setFileMetadata(absolute_path, key, fileMetadata);
}
uint32_t FileMetadataFeature::S_getFileMetadata(
PIPEDAL_FILE_METADATA_Handle handle,
LV2_URID key,
const char *absolute_path,
char *buffer,
uint32_t bufferSize)
{
return ((FileMetadataFeature *)handle)->getFileMetadata(absolute_path, key, buffer, bufferSize);
}
+63
View File
@@ -0,0 +1,63 @@
// 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.
#pragma once
#include "MapFeature.hpp"
#include "PiPedalUI.hpp"
#include "ext/PiPedal/FileMetadataFeature.h"
namespace pipedal
{
class FileMetadataFeature
{
private:
LV2_Feature feature;
PIPEDAL_FileMetadata_Interface interface;
static PIPEDAL_FileMetadata_Status S_setFileMetadata(PIPEDAL_FILE_METADATA_Handle handle, const char *absolute_path, LV2_URID key, const char *fileMetadata);
PIPEDAL_FileMetadata_Status setFileMetadata(const char *absolute_path, LV2_URID key, const char *fileMetadata);
static uint32_t S_getFileMetadata(
PIPEDAL_FILE_METADATA_Handle handle,
LV2_URID key,
const char *filePath,
char *buffer,
uint32_t bufferSize);
uint32_t getFileMetadata(
const char *absolute_path,
LV2_URID key,
char *fileMetadata,
uint32_t fileMetadataSize);
public:
FileMetadataFeature();
void Prepare(MapFeature &map);
~FileMetadataFeature();
public:
MapFeature *mapFeature = nullptr;
const LV2_Feature *GetFeature()
{
return &feature;
}
};
}
+74 -72
View File
@@ -1,18 +1,18 @@
/*
* MIT License
*
*
* Copyright (c) 2023 Robin E. R. 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
@@ -31,9 +31,9 @@
#include <filesystem>
#include <set>
#include "ModFileTypes.hpp"
#include "stdint.h"
#define PIPEDAL_HOST_FEATURE "http://github.com/rerdavies/pipedal#host" // Plugin can only be hosted by PiPedal
#define PIPEDAL_HOST_FEATURE "http://github.com/rerdavies/pipedal#host" // Plugin can only be hosted by PiPedal
#define PIPEDAL_PATCH "http://github.com/rerdavies/pipedal/patch"
#define PIPEDAL_PATCH_PREFIX PIPEDAL_PATCH "#"
@@ -47,17 +47,17 @@
#define PIPEDAL_UI__fileProperties PIPEDAL_UI_PREFIX "fileProperties"
#define PIPEDAL_UI__fileProperty PIPEDAL_UI_PREFIX "fileProperty"
#define PIPEDAL_UI__patchProperty PIPEDAL_UI_PREFIX "patchProperty"
#define PIPEDAL_UI__directory PIPEDAL_UI_PREFIX "directory"
#define PIPEDAL_UI__fileTypes PIPEDAL_UI_PREFIX "fileTypes"
#define PIPEDAL_UI__patchProperty PIPEDAL_UI_PREFIX "patchProperty"
#define PIPEDAL_UI__directory PIPEDAL_UI_PREFIX "directory"
#define PIPEDAL_UI__fileTypes PIPEDAL_UI_PREFIX "fileTypes"
#define PIPEDAL_UI__resourceDirectory PIPEDAL_UI_PREFIX "resourceDirectory"
#define PIPEDAL_UI__fileType PIPEDAL_UI_PREFIX "fileType"
#define PIPEDAL_UI__fileExtension PIPEDAL_UI_PREFIX "fileExtension"
#define PIPEDAL_UI__mimeType PIPEDAL_UI_PREFIX "mimeType"
#define PIPEDAL_UI__fileType PIPEDAL_UI_PREFIX "fileType"
#define PIPEDAL_UI__fileExtension PIPEDAL_UI_PREFIX "fileExtension"
#define PIPEDAL_UI__mimeType PIPEDAL_UI_PREFIX "mimeType"
#define PIPEDAL_UI__outputPorts PIPEDAL_UI_PREFIX "outputPorts"
#define PIPEDAL_UI__text PIPEDAL_UI_PREFIX "text"
#define PIPEDAL_UI__outputPorts PIPEDAL_UI_PREFIX "outputPorts"
#define PIPEDAL_UI__text PIPEDAL_UI_PREFIX "text"
#define PIPEDAL_UI__frequencyPlot PIPEDAL_UI_PREFIX "frequencyPlot"
#define PIPEDAL_UI__xLeft PIPEDAL_UI_PREFIX "xLeft"
@@ -67,57 +67,57 @@
#define PIPEDAL_UI__yBottom PIPEDAL_UI_PREFIX "yBottom"
#define PIPEDAL_UI__width PIPEDAL_UI_PREFIX "width"
#define PIPEDAL_UI__ledColor PIPEDAL_UI_PREFIX "ledColor"
#define PIPEDAL_UI__ledColor PIPEDAL_UI_PREFIX "ledColor"
#define PIPEDAL_UI__graphicEq PIPEDAL_UI_PREFIX "graphicEq"
namespace pipedal {
namespace pipedal
{
class PluginHost;
class UiFileType {
class UiFileType
{
private:
std::string label_;
std::string mimeType_;
std::string fileExtension_;
public:
UiFileType() { }
UiFileType(PluginHost*pHost, const LilvNode*node);
UiFileType(const std::string&label, const std::string &fileType);
UiFileType() {}
UiFileType(PluginHost *pHost, const LilvNode *node);
UiFileType(const std::string &label, const std::string &fileType);
static std::vector<UiFileType> GetArray(PluginHost *pHost, const LilvNode *node, const LilvNode *uri);
static std::vector<UiFileType> GetArray(PluginHost*pHost, const LilvNode*node,const LilvNode*uri);
const std::string& label() const { return label_;}
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;
bool IsValidExtension(const std::string &extension) const;
public:
DECLARE_JSON_MAP(UiFileType);
};
class UiPortNotification {
class UiPortNotification
{
private:
int32_t portIndex_;
std::string symbol_;
std::string plugin_;
std::string protocol_;
public:
using ptr = std::shared_ptr<UiPortNotification>;
UiPortNotification() { }
UiPortNotification(PluginHost*pHost, const LilvNode*node);
UiPortNotification() {}
UiPortNotification(PluginHost *pHost, const LilvNode *node);
public:
DECLARE_JSON_MAP(UiPortNotification);
};
class UiFileProperty {
class UiFileProperty
{
private:
std::string label_;
std::int32_t index_ = -1;
@@ -127,57 +127,59 @@ namespace pipedal {
std::string portGroup_;
std::string resourceDirectory_;
std::vector<std::string> modDirectories_;
bool useLegacyModDirectory_= false;
std::map<std::string,std::set<std::string>> fileExtensionsByModDirectory; // non-serialized.
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&label, const std::string&patchProperty,const std::string &directory);
UiFileProperty() {}
UiFileProperty(PluginHost *pHost, const LilvNode *node, const std::filesystem::path &resourcePath);
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);
const std::string &label,
const std::string &patchProperty,
const ModFileTypes &modFileType);
void setModFileTypes(const ModFileTypes&modFileType);
void setModFileTypes(const ModFileTypes &modFileType);
std::vector<std::string> &modDirectories() { return modDirectories_; }
const std::vector<std::string> &modDirectories() const { return modDirectories_; }
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_; }
void index(int32_t value) { index_ = value; }
const std::string &directory() const { return directory_; }
void directory(const std::string &path) { directory_ = path; }
const std::string&portGroup() const { return portGroup_; }
const std::string &portGroup() const { return portGroup_; }
const std::vector<UiFileType> &fileTypes() const { return fileTypes_; }
std::vector<UiFileType> &fileTypes() { return fileTypes_; }
const std::string &patchProperty() const { return patchProperty_; }
bool IsValidExtension(const std::filesystem::path&relativePath) const;
bool IsValidExtension(const std::filesystem::path &relativePath) const;
static bool IsDirectoryNameValid(const std::string&value);
static bool IsDirectoryNameValid(const std::string &value);
static std::string GetFileExtension(const std::filesystem::path&path);
const std::string&resourceDirectory() const { return resourceDirectory_; }
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;
const std::set<std::string> &GetPermittedFileExtensions(const std::string &modDirectory) const;
std::string getParentModDirectory(const std::filesystem::path &path) const;
std::string getParentModDirectory(const std::filesystem::path&path) const;
public:
DECLARE_JSON_MAP(UiFileProperty);
};
class UiFrequencyPlot {
class UiFrequencyPlot
{
private:
std::string patchProperty_;
std::int32_t index_ = -1;
@@ -188,15 +190,16 @@ namespace pipedal {
float yBottom_ = -30;
bool xLog_ = true;
float width_ = 60;
public:
using ptr = std::shared_ptr<UiFrequencyPlot>;
UiFrequencyPlot() { }
UiFrequencyPlot(PluginHost*pHost, const LilvNode*node,
const std::filesystem::path&resourcePath);
UiFrequencyPlot() {}
UiFrequencyPlot(PluginHost *pHost, const LilvNode *node,
const std::filesystem::path &resourcePath);
const std::string &patchProperty() const { return patchProperty_; }
int32_t index() const { return index_; }
const std::string&portGroup() const { return portGroup_; }
const std::string &portGroup() const { return portGroup_; }
float xLeft() const { return xLeft_; }
float xRight() const { return xRight_; }
bool xLog() const { return xLog_; }
@@ -208,32 +211,31 @@ namespace pipedal {
DECLARE_JSON_MAP(UiFrequencyPlot);
};
class PiPedalUI {
class PiPedalUI
{
public:
using ptr = std::shared_ptr<PiPedalUI>;
PiPedalUI(PluginHost*pHost, const LilvNode*uiNode, const std::filesystem::path&resourcePath);
PiPedalUI(PluginHost *pHost, const LilvNode *uiNode, const std::filesystem::path &resourcePath);
PiPedalUI(
std::vector<UiFileProperty::ptr> &&fileProperties,
std::vector<UiFrequencyPlot::ptr> &&frequencyPlots);
PiPedalUI(
std::vector<UiFileProperty::ptr> &&fileProperties);
const std::vector<UiFileProperty::ptr>& fileProperties() const
const std::vector<UiFileProperty::ptr> &fileProperties() const
{
return fileProperties_;
}
const std::vector<UiFrequencyPlot::ptr>& frequencyPlots() const
const std::vector<UiFrequencyPlot::ptr> &frequencyPlots() const
{
return frequencyPlots_;
}
const std::vector<UiPortNotification::ptr> &portNotifications() const { return portNotifications_; }
const UiFileProperty*GetFileProperty(const std::string &propertyUri) const
const UiFileProperty *GetFileProperty(const std::string &propertyUri) const
{
for (const auto&fileProperty : fileProperties())
for (const auto &fileProperty : fileProperties())
{
if (fileProperty->patchProperty() == propertyUri)
{
@@ -243,7 +245,8 @@ namespace pipedal {
return nullptr;
}
bool unsupportedPatchProperty() const { return unsupportedPatchProperty_; }
void unsupportedPatchProperty(bool value) { unsupportedPatchProperty_ = value; }
void unsupportedPatchProperty(bool value) { unsupportedPatchProperty_ = value; }
private:
bool unsupportedPatchProperty_ = false; // not serialized.
std::vector<UiFileProperty::ptr> fileProperties_;
@@ -252,7 +255,6 @@ namespace pipedal {
};
// utilities for validating file paths received via PiPedalFileProperty-related APIs.
bool IsAlphaNumeric(const std::string&value);
bool IsAlphaNumeric(const std::string &value);
};
+39 -5
View File
@@ -2182,6 +2182,44 @@ std::string Storage::RenameFilePropertyFile(
std::filesystem::rename(oldPath, newPath);
return newPath;
}
fs::path MakeVersionedPath(const fs::path &path)
{
if (!fs::exists(path))
{
return path; // no need to version a non-existing file.
}
fs::path newPath = path;
auto stem = newPath.stem().string();;
if (stem.ends_with(")"))
{
// remove the trailing (n) from the file name.
size_t pos = stem.find_last_of('(');
std::string stemVersion = stem.substr(pos+1, stem.length()-1-(pos+1));
// check if it is a number.
if (stemVersion.find_first_not_of("0123456789") == std::string::npos)
{
// it is a number, remove the trailing (n).
stem = stem.substr(0, pos);
while (stem.ends_with(" "))
{
// remove trailing space.
stem = stem.substr(0, stem.length() - 1);
}
}
}
int version = 1;
while (fs::exists(newPath))
{
// append (n) to the file name.
std::string newFileName = stem + " (" + std::to_string(version) + ")" + newPath.extension().string();
newPath = newPath.parent_path() / newFileName;
++version;
}
return newPath;
}
std::string Storage::CopyFilePropertyFile(
const std::string &oldRelativePath,
const std::string &newRelativePath,
@@ -2217,11 +2255,7 @@ std::string Storage::CopyFilePropertyFile(
}
else
{
if (!overwrite) {
return ""; // signal a portential overwrite.
} else {
fs::remove(newPath);
}
newPath = MakeVersionedPath(newPath);
}
}
+105
View File
@@ -0,0 +1,105 @@
/*
* Copyright (c) 2025 Robin E. R. Davies
* All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef PIPEDAL_FILE_METADAtA_FEATURE_H
#define PIPEDAL_FILE_METADAtA_FEATURE_H
#include "lv2/core/lv2.h"
#include "lv2/urid/urid.h"
#define PIPEDAL__FILE_METADATA_FEATURE "http://github.com/rerdavies/pipedal/ext/#fileMetadata"
#ifdef __cplusplus
extern "C"
{
#endif
typedef void *PIPEDAL_FILE_METADATA_Handle;
typedef enum
{
PIPEDAL_FILE_METADATA_SUCCESS = 0, /**< Completed successfully. */
PIPEDAL_FILE_METADATA_INVALID_PATH = 1, /**< Path is not in Tracks directory, or does not exist. */
PIPEDAL_FILE_METADATA_PERMISSION_DENIED = 2, /**< Permission denied. */
PIPEDAL_FILE_METADATA_ERR_UNKNOWNM = 3, /**< Unknown error. */
PIPEDAL_FILE_METADATA_INVALID_KEY = 4 /**< Key is not a valid URID. */
} PIPEDAL_FileMetadata_Status;
typedef struct
{
/**
Opaque host data.
*/
PIPEDAL_FILE_METADATA_Handle handle;
/**
Save a piece of plugin-defined metadata for a file.
@param handle MUST be the `handle` member of this struct.
@param absolute_path The absolute path of a file.
@param key A plugin-defined key (LV2_URID) used to identify the metdadata.
@param metdata A string containing the metadata to be saved for the file.
@return A status code indicating success or failure.
The path must be within the host-defined "Tracks" directory. The file must exist. The plugin does not
need write access to the directory containing the file in order to save metadata.
Plugins MUST NOT make any assumptions about abstract paths except that
they can be mapped back to the absolute path of the "same" file (though
not necessarily the same original path) using absolute_path().
Metatadata will be automatically deleted if the file is deleted, of the file is modified, or moved
to another directory.
This function should not be called from the plugin's realtime thread, as it may block for a long time.
*/
PIPEDAL_FileMetadata_Status (*setFileMetadata)(PIPEDAL_FILE_METADATA_Handle handle, const char *absolute_path, LV2_URID key, const char *fileMetadata);
/**
Restore previously saved metdata for the file.
@param handle MUST be the `handle` member of this struct.
@param absolute_path The absolute path of a file.
@param key A plugin-defined key (LV2_URID) used to identify the metdadata.
@param buffer A buffer in which to store the metadata.
@param buffersize The size of the buffer in bytes, including space for the null terminator.
@return A value indicating the number of bytes written to the buffer, or 0 if the metdata was not found.
If buffersize is insufficient to holed the metatdata, the function will return the number of bytes that would have been written,
including the space for the null terminator, but will not write any data to the buffer. The best way to find out
how much space is needed is to call this function with a buffer size of 0, which will return the size needed to contain the
metadata result.
This function should not be called from the plugin's realtime thread, as it may block for a long time.
*/
uint32_t (*getFileMetadata)(PIPEDAL_FILE_METADATA_Handle handle, LV2_URID key, const char *filePath, char *buffer, uint32_t bufferSize);
} PIPEDAL_FileMetadata_Interface;
typedef struct {
const char* URI;
PIPEDAL_FileMetadata_Interface*interface;
} PIPEDAL_FileMetadata_Feature;
#ifdef __cplusplus
}
#endif
#endif // PIPEDAL_FILE_METADAtA_FEATURE_H
+13
View File
@@ -789,6 +789,8 @@ export default withStyles(
}
return result;
}
private getIcon(fileEntry: FileEntry, largeIcon: boolean) {
let style = largeIcon
? {
@@ -1002,6 +1004,9 @@ export default withStyles(
if (this.state.reordering && !value.metadata) {
return null; // don't render non-track files when reordering.
}
if (this.state.reordering && this.isFolderArtwork(value.pathname)) {
return null;
}
let displayValue = value.displayName;
if (displayValue === "") {
displayValue = "<none>";
@@ -1400,6 +1405,14 @@ export default withStyles(
}
});
} else {
this.requestFiles(this.state.navDirectory);
this.setState({
selectedFile: filename,
selectedFileIsDirectory: this.isDirectory(filename),
selectedFileProtected: false
});
this.requestScroll = true;
}
})
.catch((e) => {