diff --git a/react/src/FilePropertyDialog.tsx b/react/src/FilePropertyDialog.tsx index 5d79c9a..cd4bb90 100644 --- a/react/src/FilePropertyDialog.tsx +++ b/react/src/FilePropertyDialog.tsx @@ -32,7 +32,7 @@ import Button from '@mui/material/Button'; import FileUploadIcon from '@mui/icons-material/FileUpload'; import AudioFileIcon from '@mui/icons-material/AudioFile'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; -import FolderIcon from '@mui/icons-material/Folder'; +import FolderOutlinedIcon from '@mui/icons-material/FolderOutlined'; import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined'; import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'; import DialogActions from '@mui/material/DialogActions'; @@ -105,6 +105,7 @@ export interface FilePropertyDialogState { canDelete: boolean; fileResult: FileRequestResult; navDirectory: string; + uploadDirectory: string; isProtectedDirectory: boolean; columns: number; columnWidth: number; @@ -187,12 +188,14 @@ export default withStyles(styles, { withTheme: true })( this.model = PiPedalModelFactory.getInstance(); + let selectedFile = props.selectedFile; this.state = { fullScreen: this.getFullScreen(), - selectedFile: props.selectedFile, + selectedFile: selectedFile, selectedFileProtected: true, selectedFileIsDirectory: false, - navDirectory: this.getNavDirectoryFromFile(props.selectedFile, props.fileProperty), + navDirectory: this.getNavDirectoryFromFile(selectedFile, props.fileProperty), + uploadDirectory: "", hasSelection: false, canDelete: false, columns: 0, @@ -256,7 +259,8 @@ export default withStyles(styles, { withTheme: true })( fileResult: filesResult, hasSelection: !!fileEntry, selectedFileProtected: fileEntry ? fileEntry.isProtected: true, - navDirectory: navPath + navDirectory: navPath, + uploadDirectory: filesResult.currentDirectory }); } }).catch((error) => { @@ -300,6 +304,8 @@ export default withStyles(styles, { withTheme: true })( componentDidMount() { super.componentDidMount(); this.mounted = true; + this.requestFiles(this.state.navDirectory) + this.requestScroll = true; } componentWillUnmount() { super.componentWillUnmount(); @@ -310,14 +316,16 @@ export default withStyles(styles, { withTheme: true })( super.componentDidUpdate?.(prevProps, prevState, snapshot); if (prevProps.open !== this.props.open || prevProps.fileProperty !== this.props.fileProperty || prevProps.selectedFile !== this.props.selectedFile) { if (this.props.open) { - let navDirectory = this.getNavDirectoryFromFile(this.props.selectedFile, this.props.fileProperty); + let selectedFile = this.props.selectedFile; + let navDirectory = this.getNavDirectoryFromFile(selectedFile, this.props.fileProperty); this.setState({ - selectedFile: this.props.selectedFile, + selectedFile: selectedFile, selectedFileIsDirectory: false, selectedFileProtected: true, newFolderDialogOpen: false, renameDialogOpen: false, - moveDialogOpen: false + moveDialogOpen: false, + navDirectory: navDirectory }); this.requestFiles(navDirectory) this.requestScroll = true; @@ -549,7 +557,7 @@ export default withStyles(styles, { withTheme: true })( } if (fileEntry.isDirectory) { return ( - + ); } if (isAudioFile(fileEntry.pathname)) { @@ -741,7 +749,7 @@ export default withStyles(styles, { withTheme: true })( } } uploadPage={ - "uploadUserFile?directory=" + encodeURIComponent(this.state.navDirectory) + "uploadUserFile?directory=" + encodeURIComponent(this.state.uploadDirectory) + "&ext=" + encodeURIComponent(this.getFileExtensionList(this.props.fileProperty)) } diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index 2ed7ac1..e001630 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -99,6 +99,7 @@ export class FileRequestResult { files: FileEntry[] = []; isProtected: boolean = false; breadcrumbs: BreadcrumbEntry[] = []; + currentDirectory: string = ""; }; export type PluginPresetsChangedHandler = (pluginUri: string) => void; diff --git a/react/src/PluginControlView.tsx b/react/src/PluginControlView.tsx index 5620021..9027588 100644 --- a/react/src/PluginControlView.tsx +++ b/react/src/PluginControlView.tsx @@ -23,7 +23,7 @@ import { WithStyles } from '@mui/styles'; import createStyles from '@mui/styles/createStyles'; import withStyles from '@mui/styles/withStyles'; import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel'; -import { UiPlugin, UiControl, UiFileProperty,UiFrequencyPlot, ScalePoint } from './Lv2Plugin'; +import { UiPlugin, UiControl, UiFileProperty, UiFrequencyPlot, ScalePoint } from './Lv2Plugin'; import { Pedalboard, PedalboardItem, ControlValue } from './Pedalboard'; @@ -247,8 +247,7 @@ type PluginControlViewState = { const PluginControlView = withStyles(styles, { withTheme: true })( - class extends ResizeResponsiveComponent - { + class extends ResizeResponsiveComponent { model: PiPedalModel; constructor(props: PluginControlViewProps) { @@ -526,7 +525,7 @@ const PluginControlView = return false; } - controlKeyIndex : number = 0; + controlKeyIndex: number = 0; controlNodesToNodes(nodes: (ReactNode | ControlGroup)[]): ReactNode[] { let classes = this.props.classes; let isLandscapeGrid = this.state.landscapeGrid; @@ -543,7 +542,7 @@ const PluginControlView = let item = controlGroup.controls[j]; controls.push( ( -
+
{item}
@@ -552,7 +551,7 @@ const PluginControlView = } result.push(( -
+
{controlGroup.name}
@@ -566,7 +565,7 @@ const PluginControlView = } else { result.push(( -
+
{node as ReactNode}
)); @@ -584,7 +583,7 @@ const PluginControlView = render(): ReactNode { - this.controlKeyIndex = 0; + this.controlKeyIndex = 0; let classes = this.props.classes; let pedalboardItem: PedalboardItem; let pedalboard = this.model.pedalboard.get(); @@ -650,29 +649,33 @@ const PluginControlView = ) }
- { - this.setState({ showFileDialog: false }); - }} - onOk={(fileProperty, selectedFile) => { + {this.state.showFileDialog && ( - this.model.setPatchProperty( - this.props.instanceId, - fileProperty.patchProperty, - JsonAtom.Path(selectedFile) - ) - .then(() => { + { + this.setState({ showFileDialog: false }); + }} + onOk={(fileProperty, selectedFile) => { + + this.model.setPatchProperty( + this.props.instanceId, + fileProperty.patchProperty, + JsonAtom.Path(selectedFile) + ) + .then(() => { + + }) + .catch((error) => { + this.model.showAlert("Unable to complete the operation. Audio is not running." + error); + }); + this.setState({ showFileDialog: false }); + } + } + /> + )} - }) - .catch((error) => { - this.model.showAlert("Unable to complete the operation. Audio is not running." + error); - }); - this.setState({ showFileDialog: false }); - } - } - /> this.onImeValueChange(key, value)} diff --git a/src/FileEntry.cpp b/src/FileEntry.cpp index 2a6da62..3c77073 100644 --- a/src/FileEntry.cpp +++ b/src/FileEntry.cpp @@ -38,4 +38,5 @@ JSON_MAP_BEGIN(FileRequestResult) JSON_MAP_REFERENCE(FileRequestResult,files) JSON_MAP_REFERENCE(FileRequestResult,isProtected) JSON_MAP_REFERENCE(FileRequestResult,breadcrumbs) + JSON_MAP_REFERENCE(FileRequestResult,currentDirectory) JSON_MAP_END() diff --git a/src/FileEntry.hpp b/src/FileEntry.hpp index f1dc3ff..c040955 100644 --- a/src/FileEntry.hpp +++ b/src/FileEntry.hpp @@ -52,6 +52,7 @@ namespace pipedal { std::vector files_; bool isProtected_ = false; std::vector breadcrumbs_; + std::string currentDirectory_; DECLARE_JSON_MAP(FileRequestResult); }; diff --git a/src/Lv2Effect.cpp b/src/Lv2Effect.cpp index 9df333e..3f4863d 100644 --- a/src/Lv2Effect.cpp +++ b/src/Lv2Effect.cpp @@ -43,9 +43,21 @@ #include "RingBufferReader.hpp" using namespace pipedal; +namespace fs = std::filesystem; const float BYPASS_TIME_S = 0.1f; + +static fs::path makeAbsolutePath(const std::filesystem::path &path, const std::filesystem::path &parentPath) +{ + if (path.is_absolute()) + { + return path; + } + return parentPath / path; +} + + Lv2Effect::Lv2Effect( IHost *pHost_, const std::shared_ptr &info_, @@ -69,7 +81,6 @@ Lv2Effect::Lv2Effect( this->pathProperties.push_back(filePropertyUrid); this->pathPropertyWriters.push_back(PatchPropertyWriter(instanceId, filePropertyUrid)); - std::vector pathPropertyWriters; } } for (auto &pathProperty: pedalboardItem.pathProperties_) @@ -84,8 +95,10 @@ Lv2Effect::Lv2Effect( const LilvPlugins *plugins = lilv_world_get_all_plugins(pWorld); + // xxx: could we not stash the pPlugin in the plugin info? auto uriNode = lilv_new_uri(pWorld, pedalboardItem.uri().c_str()); const LilvPlugin *pPlugin = lilv_plugins_get_by_uri(plugins, uriNode); + lilv_node_free(uriNode); { AutoLilvNode bundleUri = lilv_plugin_get_bundle_uri(pPlugin); @@ -98,6 +111,25 @@ Lv2Effect::Lv2Effect( logFeature.GetLog(), bundleUriString, storagePath); + + + mapPathFeature.Prepare(&(pHost_->GetMapFeature())); + mapPathFeature.SetPluginStoragePath(pHost_->GetPluginStoragePath()); + if (info->piPedalUI()) + { + const auto &fileProperties = info_->piPedalUI()->fileProperties(); + for (const auto &fileProperty : fileProperties) + { + if (!fileProperty->resourceDirectory().empty()) + { + mapPathFeature.AddResourceFileMapping({ + makeAbsolutePath(fileProperty->resourceDirectory(),bundleUriString), + makeAbsolutePath(fileProperty->directory(),pHost_->GetPluginStoragePath()) + }); + } + } + } + lilv_free(bundleUriString); } @@ -109,6 +141,10 @@ Lv2Effect::Lv2Effect( { this->features.push_back(*p); } + this->features.push_back(mapPathFeature.GetMapPathFeature()); + this->features.push_back(mapPathFeature.GetMakePathFeature()); + this->features.push_back(mapPathFeature.GetFreePathFeature()); + this->features.push_back(this->fileBrowserFilesFeature.GetFeature()); diff --git a/src/Lv2Effect.hpp b/src/Lv2Effect.hpp index a85e5ae..a6d5417 100644 --- a/src/Lv2Effect.hpp +++ b/src/Lv2Effect.hpp @@ -27,6 +27,7 @@ #include "FileBrowserFilesFeature.hpp" #include "PatchPropertyWriter.hpp" #include +#include "MapPathFeature.hpp" #include "IEffect.hpp" #include "Worker.hpp" @@ -90,7 +91,7 @@ namespace pipedal std::vector outputAtomBuffers; std::vector features; LV2_Feature *work_schedule_feature = nullptr; - + MapPathFeature mapPathFeature; uint64_t maxInputControlPort = 0; std::vector isInputControlPort; diff --git a/src/MapPathFeature.cpp b/src/MapPathFeature.cpp index 85040cd..aedd4e7 100644 --- a/src/MapPathFeature.cpp +++ b/src/MapPathFeature.cpp @@ -26,8 +26,7 @@ using namespace pipedal; -MapPathFeature::MapPathFeature(const std::filesystem::path &storagePath) -:storagePath(storagePath) +MapPathFeature::MapPathFeature() { lv2_state_map_path.handle = (LV2_State_Map_Path_Handle *)this; lv2_state_map_path.absolute_path = FnAbsolutePath; diff --git a/src/MapPathFeature.hpp b/src/MapPathFeature.hpp index ab227a4..655c42f 100644 --- a/src/MapPathFeature.hpp +++ b/src/MapPathFeature.hpp @@ -24,12 +24,23 @@ namespace pipedal { + struct ResourceFileMapping { + std::string resourcePath; // absolute path of the resource directory. + std::string storagePath; // absolute path of where resource directory files get placed. + }; class MapPathFeature { public: - MapPathFeature(const std::filesystem::path &storagePath); + MapPathFeature(); void Prepare(MapFeature* map); void SetPluginStoragePath(const std::string&path) { storagePath = path;} + + void AddResourceFileMapping(const ResourceFileMapping &resourceFileMapping) + { + this->resourceFileMappings.push_back(resourceFileMapping); + } + + const std::vector &GetResourceFileMappings() const { return resourceFileMappings; } const std::string&GetPluginStoragePath() const { return storagePath; } const LV2_Feature*GetMapPathFeature() { return &mapPathFeature;} const LV2_Feature*GetMakePathFeature() { return &makePathFeature;} @@ -60,5 +71,6 @@ namespace pipedal private: std::string storagePath; + std::vector resourceFileMappings; }; } \ No newline at end of file diff --git a/src/PluginHost.cpp b/src/PluginHost.cpp index d987f67..1d0ae47 100644 --- a/src/PluginHost.cpp +++ b/src/PluginHost.cpp @@ -243,16 +243,15 @@ static float nodeAsFloat(const LilvNode *node, float default_ = 0) void PluginHost::SetPluginStoragePath(const std::filesystem::path &path) { - mapPathFeature.SetPluginStoragePath(path); + pluginStoragePath = path; } std::string PluginHost::GetPluginStoragePath() const { - return mapPathFeature.GetPluginStoragePath(); + return pluginStoragePath; } PluginHost::PluginHost() - : mapPathFeature("") { pWorld = nullptr; @@ -260,13 +259,9 @@ PluginHost::PluginHost() lv2Features.push_back(mapFeature.GetUnmapFeature()); optionsFeature.Prepare(mapFeature, 44100, this->GetMaxAudioBufferSize(), this->GetAtomBufferSize()); - - mapPathFeature.Prepare(&mapFeature); - lv2Features.push_back(mapPathFeature.GetMapPathFeature()); - lv2Features.push_back(mapPathFeature.GetMakePathFeature()); - lv2Features.push_back(mapPathFeature.GetFreePathFeature()); lv2Features.push_back(optionsFeature.GetFeature()); + lv2Features.push_back(nullptr); this->urids = new Urids(mapFeature); diff --git a/src/PluginHost.hpp b/src/PluginHost.hpp index 2638207..5fa685d 100644 --- a/src/PluginHost.hpp +++ b/src/PluginHost.hpp @@ -757,7 +757,7 @@ namespace pipedal std::vector lv2Features; MapFeature mapFeature; OptionsFeature optionsFeature; - MapPathFeature mapPathFeature; + std::string pluginStoragePath; static void fn_LilvSetPortValueFunc(const char *port_symbol, void *user_data, diff --git a/src/Storage.cpp b/src/Storage.cpp index a25e4d7..f60201f 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -1770,6 +1770,7 @@ FileRequestResult Storage::GetModFileList2(const std::string &relativePath, cons FileEntry(uploadsDirectory / fileProperty.directory(), fs::path(fileProperty.directory()).filename().string(), true, true)); } result.breadcrumbs_.push_back({"", "Home"}); + result.currentDirectory_ = relativePath; return result; } fs::path modDirectoryPath; @@ -1824,9 +1825,23 @@ FileRequestResult Storage::GetModFileList2(const std::string &relativePath, cons } AddFilesToResult(result, fileProperty, relativePath); + result.currentDirectory_ = relativePath; return result; } +static bool IsChildDirectory(const fs::path& child, const fs::path&parent) { + auto iChild = child.begin(); + for (auto i = parent.begin(); i != parent.end(); ++i) + { + if (iChild == child.end() || (*i) != *iChild) + { + return false; + } + ++iChild; + } + return true; +} + FileRequestResult Storage::GetFileList2(const std::string &relativePath_, const UiFileProperty &fileProperty) { std::string absolutePath = relativePath_; @@ -1843,15 +1858,35 @@ FileRequestResult Storage::GetFileList2(const std::string &relativePath_, const if (fileProperty.directory().empty()) { + throw std::runtime_error("fileProperty.directory() not specified."); } std::filesystem::path pluginRootDirectory = this->GetPluginUploadDirectory() / fileProperty.directory(); - if (absolutePath == "") + if (absolutePath.empty()) { absolutePath = pluginRootDirectory.string(); } + // handle resource directories. + if (!fileProperty.resourceDirectory().empty()) { + if (fileProperty.resourceDirectory() == absolutePath) + { + absolutePath = pluginRootDirectory; + } + } + + if (!IsChildDirectory(absolutePath,pluginRootDirectory)) + { + absolutePath = pluginRootDirectory; + } + + + result.currentDirectory_ = absolutePath; + + + { + // watch out for resource files!!! result.breadcrumbs_.push_back({"", "Home"}); fs::path fsAbsolutePath{absolutePath}; auto iAbsolutePath = fsAbsolutePath.begin(); diff --git a/src/main.cpp b/src/main.cpp index 2f22fb9..c1d4e97 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -398,6 +398,11 @@ int main(int argc, char *argv[]) } Lv2Log::info("Shutdown complete."); + if (systemd) + { + sd_notify(0, "STOPPING=1"); + } + if (g_restart) return EXIT_FAILURE; // indicate to systemd that we want a restart. }