+2
-2
@@ -1,10 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.16.0)
|
||||
project(pipedal
|
||||
VERSION 1.3.61
|
||||
VERSION 1.3.62
|
||||
DESCRIPTION "PiPedal Guitar Effect Pedal For Raspberry Pi"
|
||||
HOMEPAGE_URL "https://rerdavies.github.io/pipedal"
|
||||
)
|
||||
set (DISPLAY_VERSION "PiPedal v1.3.61-Release")
|
||||
set (DISPLAY_VERSION "PiPedal v1.3.62-Beta")
|
||||
set (PACKAGE_ARCHITECTURE "arm64")
|
||||
set (CMAKE_INSTALL_PREFIX "/usr/")
|
||||
|
||||
|
||||
@@ -51,6 +51,22 @@ namespace pipedal {
|
||||
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool contains(const std::vector<T>&vector,const T&value)
|
||||
{
|
||||
for (auto i = vector.begin(); i != vector.end(); ++i)
|
||||
{
|
||||
if ((*i) == value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
inline bool contains(const std::vector<std::string>&vector,const char*value)
|
||||
{
|
||||
return contains(vector,std::string(value));
|
||||
}
|
||||
|
||||
class NoCopy {
|
||||
public:
|
||||
NoCopy() { }
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
<a href="https://rerdavies.github.io/pipedal/LicensePiPedal.html"><img src="https://img.shields.io/badge/MIT-MIT?label=license&color=%23808080"/></a>
|
||||
<a href="https://github.com/rerdavies/pipedal/actions"><img src="https://img.shields.io/github/actions/workflow/status/rerdavies/pipedal/cmake.yml?branch=main"/></a>
|
||||
|
||||
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.3.61</a>
|
||||
Download: <a href='https://rerdavies.github.io/pipedal/download.html'>v1.3.62</a>
|
||||
Website: [https://rerdavies.github.io/pipedal](https://rerdavies.github.io/pipedal).
|
||||
Documentation: [https://rerdavies.github.io/pipedal/Documentation.html](https://rerdavies.github.io/pipedal/Documentation.html).
|
||||
|
||||
|
||||
|
||||
#### NEW version 1.3.61 Release, providing [snapshots](https://rerdavies.github.io/pipedal/Snaphots.html), and a new Performance View. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
|
||||
#### NEW version 1.3.62 Release, providing [snapshots](https://rerdavies.github.io/pipedal/Snaphots.html), and a new Performance View. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -13,17 +13,17 @@ page_icon: img/Install4.jpg
|
||||
|
||||
Download the most recent Debian (.deb) package for your platform:
|
||||
|
||||
- [Raspberry Pi OS bookworm (64-bit) v1.3.61](https://github.com/rerdavies/pipedal/releases/download/)
|
||||
- [Raspberry Pi OS bookworm (64-bit) v1.3.62](https://github.com/rerdavies/pipedal/releases/download/)
|
||||
- [Ubuntu/Raspberry Pi OS bullseyeye (64-bit) v1.2.31](https://github.com/rerdavies/pipedal/releases/download/v1.1.31/pipedal_1.1.31_arm64.deb)
|
||||
|
||||
Version 1.3.61 has not yet been tested on Ubuntu or Raspberry Pi OS bullseye. On these platforms, we recommend that you use version 1.1.31.
|
||||
Version 1.3.62 has not yet been tested on Ubuntu or Raspberry Pi OS bullseye. On these platforms, we recommend that you use version 1.1.31.
|
||||
|
||||
Install the package by running
|
||||
|
||||
```
|
||||
sudo apt update
|
||||
cd ~/Downloads
|
||||
sudo apt-get install pipedal_1.3.61_arm64.deb
|
||||
sudo apt-get install pipedal_1.3.62_arm64.deb
|
||||
```
|
||||
Adjust accordingly if you have downloaded v1.1.31.
|
||||
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# Release Notes
|
||||
|
||||
## PiPedal 1.3.62 Beta
|
||||
|
||||
Features:
|
||||
- Plugins can now share uploaded model and IR files by type. For plugins with appropriate mod:fileTypes attributes, uploaded
|
||||
files will be shared by category (Nam model, IR Files, Audio Tracks, Midi Songs, etc). Sharing of ML models are also supported, although
|
||||
that requires use of an extended mod:fileType (mlmodel) that MOD does not support.
|
||||
- TooB Tuner stability issues fixed. The pitch detection algorithm has been improved, and the tuner now does filtering to avoid
|
||||
displaying a value when the current input isn't a steady note.
|
||||
- LV2 trigger controls are now displayed as buttons instead of toggle controls (maybe useful for 3rd-party looper plugins).
|
||||
|
||||
Known issues:
|
||||
|
||||
- There are a number of outstanding minor compatibility issues with Ratatouille. I intend to work with the author to address them.
|
||||
|
||||
### Backwards Compatibility With the Previous Upload Directory Scheme
|
||||
|
||||
Sharing file types poses a problem with previous versions of PiPedal which did not share uploads by file type, but instead placed uploads in a private directory for each plugin.
|
||||
In order to accomodate this, Pipedal provides legacy support for this situation. If a private upload directory for a plugin exists, the file property selection dialog will show
|
||||
both the new shared directories, and the old private directory. The private directory has a name that reflects the name of the plugin (e.g. "Ratatoille.lv2"). This directory only
|
||||
shows up if you have used the plugin on previous versiouns of Pipedal. If you have nothing of particular value in the old private upload directory, you can delete the plugin's private directory (which can be found in `/var/pipedal/audio_uploads`), and the private directory will no longer be displayed in the PiPedal UI.
|
||||
|
||||
I anticipate providing a migration utility in the near future which will clean up and migrate legacy upload directories to the new directory structure (automatically uploading
|
||||
presets which reference uploaded files that have moved). In the meantime, I think you will find the current occomodation for legacy upload directories perfectly functional.
|
||||
|
||||
|
||||
|
||||
## PiPedal 1.3.61 Release
|
||||
|
||||
Bug fixes:
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
Download the most recent Debian (.deb) package for your platform:
|
||||
|
||||
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.3.61/pipedal_1.3.61_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.3.61</a>
|
||||
- <a href="https://github.com/rerdavies/pipedal/releases/download/v1.3.62/pipedal_1.3.62_arm64.deb">Raspberry Pi OS Bookworm (64-bit) v1.3.62</a>
|
||||
|
||||
|
||||
Install the package by running
|
||||
@@ -12,7 +12,7 @@ Install the package by running
|
||||
```
|
||||
sudo apt update
|
||||
cd ~/Downloads
|
||||
sudo apt-get install ./pipedal_1.3.61_arm64.deb
|
||||
sudo apt-get install ./pipedal_1.3.62_arm64.deb
|
||||
```
|
||||
|
||||
Follow the instructions in [_Configuring PiPedal After Installation_](https://rerdavies.github.io/pipedal/Configuring.html) to complete the installation.
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
<img src="GithubBanner.png" width="100%"/>
|
||||
<a href="Installing.html"><i>v1.3.61</i></a>
|
||||
<a href="Installing.html"><i>v1.3.62</i></a>
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ To download PiPedal, click [here](download.md).
|
||||
To view PiPedal documentation, click [here](Documentation.md).
|
||||
|
||||
|
||||
#### NEW version 1.3.61 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
|
||||
#### NEW version 1.3.62 Release. See the [release notes](https://rerdavies.github.io/pipedal/ReleaseNotes) for details.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ cabir:impulseFile
|
||||
a lv2:Parameter;
|
||||
rdfs:label "IR";
|
||||
pg:group cabir:impulseFile1Group ;
|
||||
mod:fileTypes "wav,flac";
|
||||
mod:fileTypes "ir,wav,flac";
|
||||
|
||||
rdfs:range atom:Path.
|
||||
|
||||
@@ -63,14 +63,14 @@ cabir:impulseFile2
|
||||
a lv2:Parameter;
|
||||
rdfs:label "IR";
|
||||
pg:group cabir:impulseFile2Group ;
|
||||
mod:fileTypes "wav,flac";
|
||||
mod:fileTypes "ir,wav,flac";
|
||||
rdfs:range atom:Path.
|
||||
|
||||
cabir:impulseFile3
|
||||
a lv2:Parameter;
|
||||
rdfs:label "IR";
|
||||
pg:group cabir:impulseFile3Group ;
|
||||
mod:fileTypes "wav,flac";
|
||||
mod:fileTypes "ir,wav,flac";
|
||||
rdfs:range atom:Path.
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ cabir:impulseFile3
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
rdfs:comment """
|
||||
|
||||
TooB Cab IR is a convolution-based guitar cabinet impulse response simulator.
|
||||
|
||||
@@ -49,7 +49,7 @@ toob:frequencyResponseVector
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "TooB CabSim";
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
toobimpulse:impulseFile
|
||||
a lv2:Parameter;
|
||||
rdfs:label "Reverb Impulse File";
|
||||
mod:fileTypes "wav,flac";
|
||||
mod:fileTypes "ir,wav,flac";
|
||||
rdfs:range atom:Path.
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ toobimpulse:impulseFile
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
rdfs:comment """
|
||||
|
||||
Convolution reverb is a notoriously compute-intensive effect. If you are having performance issues, use the Max T control to constrain the length of the impulse file to
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
toobimpulse:impulseFile
|
||||
a lv2:Parameter;
|
||||
rdfs:label "File";
|
||||
mod:fileTypes "wav,flac";
|
||||
mod:fileTypes "ir,wav,flac";
|
||||
rdfs:range atom:Path.
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ toobimpulse:impulseFile
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
rdfs:comment """
|
||||
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ inputStage:filterGroup
|
||||
doap:license <https://two-play.com/TooB/licenses/isc> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "TooB Input";
|
||||
|
||||
@@ -67,7 +67,7 @@ pstage:stage3
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
|
||||
mod:brand "TooB";
|
||||
mod:label "Power Stage";
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
rdfs:comment "TooB spectrum analyzer" ;
|
||||
|
||||
mod:brand "TooB";
|
||||
|
||||
@@ -55,7 +55,7 @@ tonestack:eqGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
|
||||
uiext:ui <http://two-play.com/plugins/toob-tone-stack-ui>;
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -40,7 +40,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
rdfs:comment """
|
||||
Emulation of a Boss CE-2 Chorus.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
rdfs:comment """
|
||||
A straightforward no-frills digital delay.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
rdfs:comment """
|
||||
Emulation of a Boss BF-2 Flanger, based on circuit analysis and simulation of the original.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
rdfs:comment """
|
||||
Digital emulation of a Boss BF-2 Flanger.
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
rdfs:comment """
|
||||
Toob Freeverb is an Lv2 implementation of the famous Freeverb reverb effect. FreeVerb delivers a well-balanced reverb with very little tonal coloration.
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
toobml:modelFile
|
||||
a lv2:Parameter;
|
||||
rdfs:label "Model";
|
||||
mod:fileTypes "nam,nammodel";
|
||||
mod:fileTypes "json,mlmodels";
|
||||
rdfs:range atom:Path.
|
||||
|
||||
toobml:filterGroup
|
||||
@@ -65,7 +65,7 @@ toobml:sagGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
rdfs:comment """
|
||||
The TooB ML Amplifier plugin provides emulation of a variety of amplifiers and overdrive pedals that are implemented
|
||||
using neural-network-based machine learning models of real amplifiers.
|
||||
|
||||
@@ -61,7 +61,7 @@ toobNam:eqGroup
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
rdfs:comment """
|
||||
A port of Steven Atkinson's Neural Amp Modeler to LV2.
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
doap:license <https://rerdavies.github.io/pipedal/LicenseToobAmp> ;
|
||||
doap:maintainer <http://two-play.com/rerdavies#me> ;
|
||||
lv2:minorVersion 0 ;
|
||||
lv2:microVersion 51 ;
|
||||
lv2:microVersion 52 ;
|
||||
rdfs:comment """
|
||||
TooB Tuner is a chromatic guitar tuner.
|
||||
""" ;
|
||||
|
||||
@@ -198,6 +198,7 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> implements I
|
||||
{
|
||||
this.onEnterKey();
|
||||
}
|
||||
evt.stopPropagation();
|
||||
}
|
||||
render() {
|
||||
let { tag,onClose,...extra} = this.props;
|
||||
|
||||
+706
-661
File diff suppressed because it is too large
Load Diff
@@ -38,28 +38,18 @@ import { isDarkMode } from './DarkMode';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
|
||||
|
||||
function pathConcat(l: string, r: string): string
|
||||
{
|
||||
if (l.length === 0) return r;
|
||||
if (r.length === 0) return l;
|
||||
return l +'/' + r;
|
||||
}
|
||||
|
||||
class DirectoryTree {
|
||||
name: string = "";
|
||||
path: string = "";
|
||||
expanded: boolean = false;
|
||||
isProtected: boolean = true;
|
||||
children: DirectoryTree[] = [];
|
||||
|
||||
constructor(tree: FilePropertyDirectoryTree, parentTree: (DirectoryTree | null) = null) {
|
||||
this.name = tree.directoryName;
|
||||
if (parentTree) {
|
||||
this.path = pathConcat(parentTree.path,tree.directoryName);
|
||||
} else {
|
||||
this.name = "Home";
|
||||
this.path = tree.directoryName;
|
||||
this.expanded = true;
|
||||
}
|
||||
this.path = tree.directoryName;
|
||||
this.name = tree.displayName;
|
||||
this.isProtected = tree.isProtected;
|
||||
for (var treeChild of tree.children) {
|
||||
this.children.push(new DirectoryTree(treeChild, this));
|
||||
}
|
||||
@@ -143,6 +133,7 @@ export interface FilePropertyDirectorySelectDialogState {
|
||||
selectedPath: string;
|
||||
directoryTree?: DirectoryTree;
|
||||
hasSelection: boolean;
|
||||
isProtected: boolean;
|
||||
directoryTreeInvalidatecount: number;
|
||||
|
||||
};
|
||||
@@ -160,6 +151,7 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC
|
||||
selectedPath: props.defaultPath,
|
||||
directoryTree: undefined,
|
||||
hasSelection: false,
|
||||
isProtected: true,
|
||||
directoryTreeInvalidatecount: 0
|
||||
|
||||
};
|
||||
@@ -192,14 +184,19 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC
|
||||
this.model.getFilePropertyDirectoryTree(this.props.uiFileProperty)
|
||||
.then((filePropertyDirectoryTree) => {
|
||||
let myTree = new DirectoryTree(filePropertyDirectoryTree);
|
||||
if (this.props.excludeDirectory)
|
||||
if (this.props.excludeDirectory.length !== 0)
|
||||
{
|
||||
myTree.remove(this.props.excludeDirectory);
|
||||
}
|
||||
myTree.expand(this.state.selectedPath);
|
||||
|
||||
let hasSelection = myTree.find(this.state.selectedPath) != null;
|
||||
this.setState({ directoryTree: myTree, hasSelection: hasSelection });
|
||||
let selection = myTree.find(this.state.selectedPath);
|
||||
let hasSelection = !!selection;
|
||||
this.setState({
|
||||
directoryTree: myTree,
|
||||
hasSelection: hasSelection,
|
||||
isProtected: selection ? selection.isProtected : false
|
||||
});
|
||||
this.requestScroll = true;
|
||||
})
|
||||
.catch((e) => {
|
||||
@@ -223,6 +220,9 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC
|
||||
|
||||
|
||||
onOK() {
|
||||
if (this.state.isProtected) {
|
||||
return;
|
||||
}
|
||||
if (this.state.hasSelection) {
|
||||
this.props.onOk(this.state.selectedPath);
|
||||
}
|
||||
@@ -238,7 +238,11 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC
|
||||
private onTreeClick(directoryTree: DirectoryTree) {
|
||||
if (!this.state.directoryTree) return;
|
||||
this.state.directoryTree.expand(directoryTree.path);
|
||||
this.setState({ selectedPath: directoryTree.path, hasSelection: true, directoryTreeInvalidatecount: this.state.directoryTreeInvalidatecount + 1 });
|
||||
this.setState({
|
||||
selectedPath: directoryTree.path,
|
||||
hasSelection: true,
|
||||
isProtected: directoryTree.isProtected,
|
||||
directoryTreeInvalidatecount: this.state.directoryTreeInvalidatecount + 1 });
|
||||
}
|
||||
private renderTree_(directoryTree: DirectoryTree) {
|
||||
let ref: ((element: HTMLButtonElement | null) => void) | undefined = undefined;
|
||||
@@ -338,7 +342,8 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC
|
||||
<Button variant="dialogSecondary" onClick={() => { this.onClose(); }} color="primary">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="dialogPrimary" onClick={() => { this.onOK(); }} color="secondary" disabled={!this.state.hasSelection} >
|
||||
<Button variant="dialogPrimary" onClick={() => { this.onOK(); }} color="secondary"
|
||||
disabled={(!this.state.hasSelection) || this.state.isProtected} >
|
||||
OK
|
||||
</Button>
|
||||
</DialogActions>
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
export default class FilePropertyDirectoryTree {
|
||||
deserialize(input: any) : FilePropertyDirectoryTree {
|
||||
this.directoryName = input.directoryName;
|
||||
this.displayName = input.displayName;
|
||||
this.isProtected = input.isProtected;
|
||||
this.children = FilePropertyDirectoryTree.deserialize_array(input.children);
|
||||
return this;
|
||||
}
|
||||
@@ -35,5 +37,7 @@ export default class FilePropertyDirectoryTree {
|
||||
|
||||
|
||||
directoryName: string = "";
|
||||
displayName: string = "";
|
||||
isProtected: boolean = false;
|
||||
children: FilePropertyDirectoryTree[] = [];
|
||||
}
|
||||
+15
-3
@@ -246,6 +246,8 @@ export class UiFileProperty {
|
||||
this.index = input.index;
|
||||
this.portGroup = input.portGroup;
|
||||
this.resourceDirectory = input.resourceDirectory ?? "";
|
||||
this.modDirectories = input.modDirectories;
|
||||
this.useLegacyModDirectory = input.useLegacyModDirectory;
|
||||
return this;
|
||||
}
|
||||
static deserialize_array(input: any): UiFileProperty[] {
|
||||
@@ -297,6 +299,8 @@ export class UiFileProperty {
|
||||
index: number = -1;
|
||||
portGroup: string = "";
|
||||
resourceDirectory: string = "";
|
||||
modDirectories: string[] = [];
|
||||
useLegacyModDirectory: boolean = false;
|
||||
|
||||
};
|
||||
export class Lv2Plugin implements Deserializable<Lv2Plugin> {
|
||||
@@ -436,6 +440,7 @@ export enum ControlType {
|
||||
OnOffSwitch,
|
||||
ABSwitch,
|
||||
Select,
|
||||
Trigger,
|
||||
|
||||
Tuner,
|
||||
Vu,
|
||||
@@ -444,7 +449,7 @@ export enum ControlType {
|
||||
}
|
||||
|
||||
export class UiControl implements Deserializable<UiControl> {
|
||||
deserialize(input: any): UiControl {
|
||||
deserialize(input: any): UiControl {
|
||||
this.symbol = input.symbol;
|
||||
this.name = input.name;
|
||||
this.index = input.index;
|
||||
@@ -458,7 +463,7 @@ export class UiControl implements Deserializable<UiControl> {
|
||||
this.integer_property = input.integer_property;
|
||||
this.enumeration_property = input.enumeration_property;
|
||||
this.toggled_property = input.toggled_property;
|
||||
this.trigger = input.trigger;
|
||||
this.trigger_property = input.trigger_property;
|
||||
this.not_on_gui = input.not_on_gui;
|
||||
this.scale_points = ScalePoint.deserialize_array(input.scale_points);
|
||||
this.port_group = input.port_group;
|
||||
@@ -499,6 +504,10 @@ export class UiControl implements Deserializable<UiControl> {
|
||||
this.controlType = ControlType.OnOffSwitch;
|
||||
}
|
||||
}
|
||||
if (this.is_input && this.trigger_property)
|
||||
{
|
||||
this.controlType = ControlType.Trigger;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
applyProperties(properties: Partial<UiControl>): UiControl {
|
||||
@@ -546,7 +555,7 @@ export class UiControl implements Deserializable<UiControl> {
|
||||
range_steps: number = 0;
|
||||
integer_property: boolean = false;
|
||||
enumeration_property: boolean = false;
|
||||
trigger: boolean = false;
|
||||
trigger_property: boolean = false;
|
||||
not_on_gui: boolean = false;
|
||||
toggled_property: boolean = false;
|
||||
scale_points: ScalePoint[] = [];
|
||||
@@ -590,6 +599,9 @@ export class UiControl implements Deserializable<UiControl> {
|
||||
isSelect(): boolean {
|
||||
return this.controlType === ControlType.Select;
|
||||
}
|
||||
isTrigger(): boolean {
|
||||
return this.controlType === ControlType.Trigger;
|
||||
}
|
||||
isOutputSelect(): boolean {
|
||||
return !this.is_input && this.controlType === ControlType.OutputSelect;
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ const MidiBindingView =
|
||||
isBinaryControl = (port.isAbToggle() || port.isOnOffSwitch());
|
||||
if (midiBinding.bindingType !== MidiBinding.BINDING_TYPE_NONE) {
|
||||
canLatch = isBinaryControl;
|
||||
canTrigger = port.trigger;
|
||||
canTrigger = port.trigger_property;
|
||||
showLinearControlTypeSelect = !(canLatch || canTrigger);
|
||||
showLinearRange = showLinearControlTypeSelect && midiBinding.linearControlType === MidiBinding.LINEAR_CONTROL_TYPE;
|
||||
canRotaryScale = showLinearControlTypeSelect && midiBinding.linearControlType === MidiBinding.CIRCULAR_CONTROL_TYPE;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// 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.
|
||||
|
||||
|
||||
|
||||
export interface ModDirectory {
|
||||
modType: string;
|
||||
pipedalPath: string;
|
||||
displayName: string;
|
||||
defaultFileExtensions: string[];
|
||||
};
|
||||
|
||||
|
||||
export const modDirectories: ModDirectory[] = [
|
||||
{modType: "audioloop", pipedalPath: "shared/audio/Loops",displayName: "Loops", defaultFileExtensions: ["audio/*"]}, // Audio Loops, meant to be used for looper-style plugins
|
||||
//"audiorecording","shared/audio/Audio Recordings", defaultFileExtensions: ["audio/*"]}, : Audio Recordings, triggered by plugins and stored in the unit
|
||||
{modType: "audiosample", pipedalPath: "shared/audio/Samples", displayName: "Samples", defaultFileExtensions: ["audio/*"]}, // One-shot Audio Samples, meant to be used for sampler-style plugins
|
||||
{modType: "audiotrack", pipedalPath: "shared/audio/Tracks",displayName: "Tracks", defaultFileExtensions: ["audio/*"]}, // Audio Tracks, meant to be used as full-performance/song or backtrack
|
||||
{modType: "cabsim", pipedalPath: "CabIR", displayName: "Cab IRs", defaultFileExtensions: ["audio/*"]}, // Speaker Cabinets, meant as small IR audio files
|
||||
|
||||
/// - h2drumkit: Hydrogen Drumkits, must use h2drumkit file extension
|
||||
{modType: "ir", pipedalPath: "ReverbImpulseFiles", displayName: "Impulse Responses", defaultFileExtensions: ["audio/*"]}, // Impulse Responses
|
||||
{modType: "midiclip", pipedalPath: "shared/midiClips",displayName: "MIDI Clips", defaultFileExtensions: [".mid", ".midi"]}, // MIDI Clips, to be used in sync with host tempo, must have mid or midi file extension
|
||||
{modType: "midisong", pipedalPath: "shared/midiSongs", displayName: "MIDI Songs", defaultFileExtensions: [".mid", ".midi"]}, // MIDI Songs, meant to be used as full-performance/song or backtrack
|
||||
{modType: "sf2", pipedalPath: "shared/sf2",displayName: "Sound Fonts", defaultFileExtensions: ["sf2", "sf3"]}, // SF2 Instruments, must have sf2 or sf3 file extension
|
||||
{modType: "sfz", pipedalPath: "shared/sfz",displayName: "Sfz Files", defaultFileExtensions: ["sfz"]}, // SFZ Instruments, must have sfz file extension
|
||||
// extensions observed in the field.
|
||||
{modType: "audio", pipedalPath: "shared/audio", displayName: "Audio", defaultFileExtensions: ["audio/*"]}, // all audio files (Ratatoille)
|
||||
{modType: "nammodel", pipedalPath: "NeuralAmpModels", displayName: "Neural Amp Models", defaultFileExtensions: [".nam"]}, // Ratatoille, Mike's NAM.
|
||||
{modType: "aidadspmodel", pipedalPath: "shared/aidaaix", displayName: "AIDA IAX Models", defaultFileExtensions: [".json", ".aidaiax"]}, // Ratatoille
|
||||
{modType: "mlmodel", pipedalPath: "ToobMlModels", displayName: "ML Models", defaultFileExtensions: [".json"]}, //
|
||||
|
||||
];
|
||||
|
||||
export function getModDirectory(modFileType: string): ModDirectory | undefined {
|
||||
for (let i = 0; i < modDirectories.length; ++i)
|
||||
{
|
||||
if (modDirectories[i].modType === modFileType)
|
||||
{
|
||||
return modDirectories[i];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -73,8 +73,20 @@ export enum ReconnectReason {
|
||||
export type ControlValueChangedHandler = (key: string, value: number) => void;
|
||||
|
||||
export interface FileEntry {
|
||||
filename: string;
|
||||
pathname: string;
|
||||
displayName: string;
|
||||
isDirectory: boolean;
|
||||
isProtected: boolean;
|
||||
};
|
||||
|
||||
export interface BreadcrumbEntry {
|
||||
pathname: string;
|
||||
displayName: string;
|
||||
};
|
||||
export class FileRequestResult {
|
||||
files: FileEntry[] = [];
|
||||
isProtected: boolean = false;
|
||||
breadcrumbs: BreadcrumbEntry[] = [];
|
||||
};
|
||||
|
||||
export type PluginPresetsChangedHandler = (pluginUri: string) => void;
|
||||
@@ -1989,9 +2001,9 @@ export class PiPedalModel //implements PiPedalModel
|
||||
return nullCast(this.webSocket)
|
||||
.request<string[]>('requestFileList', piPedalFileProperty);
|
||||
}
|
||||
requestFileList2(relativeDirectoryPath: string, piPedalFileProperty: UiFileProperty): Promise<FileEntry[]> {
|
||||
requestFileList2(relativeDirectoryPath: string, piPedalFileProperty: UiFileProperty): Promise<FileRequestResult> {
|
||||
return nullCast(this.webSocket)
|
||||
.request<FileEntry[]>('requestFileList2',
|
||||
.request<FileRequestResult>('requestFileList2',
|
||||
{ relativePath: relativeDirectoryPath, fileProperty: piPedalFileProperty }
|
||||
);
|
||||
}
|
||||
@@ -2498,7 +2510,7 @@ export class PiPedalModel //implements PiPedalModel
|
||||
link.click();
|
||||
}
|
||||
|
||||
uploadFile(uploadPage: string, file: File, contentType: string = "application/octet-stream", abortController?: AbortController): Promise<string> {
|
||||
uploadUserFile(uploadPage: string, file: File, contentType: string = "application/octet-stream", abortController?: AbortController): Promise<string> {
|
||||
let result = new Promise<string>((resolve, reject) => {
|
||||
try {
|
||||
if (file.size > this.maxFileUploadSize) {
|
||||
@@ -2539,10 +2551,20 @@ export class PiPedalModel //implements PiPedalModel
|
||||
}
|
||||
})
|
||||
.then((json) => {
|
||||
resolve(json as string);
|
||||
let response = json as {errorMessage: string, path: string};
|
||||
if (response.errorMessage !== "")
|
||||
{
|
||||
throw new Error(response.errorMessage);
|
||||
}
|
||||
resolve(response.path);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject("Upload failed. " + error);
|
||||
if (error instanceof Error)
|
||||
{
|
||||
reject("Upload failed. " + (error as Error).message);
|
||||
} else {
|
||||
reject("Upload failed. " + error);
|
||||
}
|
||||
})
|
||||
;
|
||||
} catch (error) {
|
||||
|
||||
+127
-117
@@ -18,6 +18,7 @@
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import React, { TouchEvent, PointerEvent, ReactNode, Component, SyntheticEvent } from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import { Theme } from '@mui/material/styles';
|
||||
import { WithStyles } from '@mui/styles';
|
||||
import createStyles from '@mui/styles/createStyles';
|
||||
@@ -27,10 +28,11 @@ import Typography from '@mui/material/Typography';
|
||||
import Input from '@mui/material/Input';
|
||||
import Select from '@mui/material/Select';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import Utility, {nullCast} from './Utility';
|
||||
import Utility, { nullCast } from './Utility';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import {PiPedalModel,PiPedalModelFactory} from './PiPedalModel';
|
||||
import {ReactComponent as DialIcon} from './svg/fx_dial.svg';
|
||||
import { PiPedalModel, PiPedalModelFactory } from './PiPedalModel';
|
||||
import { ReactComponent as DialIcon } from './svg/fx_dial.svg';
|
||||
import { isDarkMode } from './DarkMode';
|
||||
|
||||
const MIN_ANGLE = -135;
|
||||
const MAX_ANGLE = 135;
|
||||
@@ -94,8 +96,7 @@ type PluginControlState = {
|
||||
|
||||
const PluginControl =
|
||||
withStyles(styles, { withTheme: true })(
|
||||
class extends Component<PluginControlProps, PluginControlState>
|
||||
{
|
||||
class extends Component<PluginControlProps, PluginControlState> {
|
||||
|
||||
frameRef: React.RefObject<HTMLDivElement>;
|
||||
imgRef: React.RefObject<SVGSVGElement>;
|
||||
@@ -140,23 +141,18 @@ const PluginControl =
|
||||
return Utility.needsZoomedControls();
|
||||
}
|
||||
|
||||
showZoomedControl()
|
||||
{
|
||||
if (this.props.uiControl && this.frameRef.current)
|
||||
{
|
||||
this.model.zoomUiControl(this.frameRef.current,this.props.instanceId,this.props.uiControl);
|
||||
showZoomedControl() {
|
||||
if (this.props.uiControl && this.frameRef.current) {
|
||||
this.model.zoomUiControl(this.frameRef.current, this.props.instanceId, this.props.uiControl);
|
||||
}
|
||||
}
|
||||
hideZoomedControl()
|
||||
{
|
||||
if (this.frameRef.current && this.model.zoomedUiControl.get()?.source === this.frameRef.current)
|
||||
{
|
||||
hideZoomedControl() {
|
||||
if (this.frameRef.current && this.model.zoomedUiControl.get()?.source === this.frameRef.current) {
|
||||
this.model.clearZoomedControl();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount()
|
||||
{
|
||||
componentWillUnmount() {
|
||||
this.hideZoomedControl();
|
||||
}
|
||||
inputChanged: boolean = false;
|
||||
@@ -172,12 +168,11 @@ const PluginControl =
|
||||
}
|
||||
onInputFocus(event: SyntheticEvent): void {
|
||||
this.displayValueRef.current!.style.display = "none";
|
||||
if (Utility.hasIMEKeyboard())
|
||||
{
|
||||
if (Utility.hasIMEKeyboard()) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.inputRef.current?.blur();
|
||||
this.props.requestIMEEdit(nullCast(this.props.uiControl),this.props.value)
|
||||
this.props.requestIMEEdit(nullCast(this.props.uiControl), this.props.value)
|
||||
}
|
||||
}
|
||||
onInputKeyPress(e: any): void {
|
||||
@@ -221,7 +216,7 @@ const PluginControl =
|
||||
// clamp and quantize.
|
||||
let range = this.valueToRange(result);
|
||||
result = this.rangeToValue(range);
|
||||
let displayVal = this.props.uiControl?.formatShortValue(result)??"";
|
||||
let displayVal = this.props.uiControl?.formatShortValue(result) ?? "";
|
||||
if (event.currentTarget) {
|
||||
event.currentTarget.value = displayVal;
|
||||
}
|
||||
@@ -270,11 +265,9 @@ const PluginControl =
|
||||
|
||||
capturedPointers: number[] = [];
|
||||
|
||||
onBodyPointerDownCapture(e_: any): any
|
||||
{
|
||||
onBodyPointerDownCapture(e_: any): any {
|
||||
let e = e_ as PointerEvent;
|
||||
if (this.isExtraTouch(e))
|
||||
{
|
||||
if (this.isExtraTouch(e)) {
|
||||
this.isTap = false;
|
||||
this.captureElement!.setPointerCapture(e.pointerId);
|
||||
this.capturedPointers.push(e.pointerId);
|
||||
@@ -305,10 +298,8 @@ const PluginControl =
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (this.isTouchDevice())
|
||||
{
|
||||
if (this.props.uiControl?.isDial()??false)
|
||||
{
|
||||
if (this.isTouchDevice()) {
|
||||
if (this.props.uiControl?.isDial() ?? false) {
|
||||
this.isTap = false;
|
||||
this.showZoomedControl();
|
||||
return;
|
||||
@@ -318,8 +309,7 @@ const PluginControl =
|
||||
++this.pointersDown;
|
||||
|
||||
this.mouseDown = true;
|
||||
if (this.pointersDown === 1)
|
||||
{
|
||||
if (this.pointersDown === 1) {
|
||||
this.isTap = true;
|
||||
this.tapStartMs = Date.now();
|
||||
} else {
|
||||
@@ -340,8 +330,8 @@ const PluginControl =
|
||||
this.captureElement = img;
|
||||
document.body.addEventListener(
|
||||
"pointerdown",
|
||||
this.onBodyPointerDownCapture,true
|
||||
);
|
||||
this.onBodyPointerDownCapture, true
|
||||
);
|
||||
|
||||
img.setPointerCapture(e.pointerId);
|
||||
if (img.style) {
|
||||
@@ -350,11 +340,10 @@ const PluginControl =
|
||||
}
|
||||
|
||||
} else {
|
||||
if (this.isExtraTouch(e))
|
||||
{
|
||||
if (this.isExtraTouch(e)) {
|
||||
++this.pointersDown;
|
||||
this.isTap = false;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -367,7 +356,7 @@ const PluginControl =
|
||||
if (this.isCapturedPointer(e)) {
|
||||
--this.pointersDown;
|
||||
this.isTap = false;
|
||||
|
||||
|
||||
|
||||
this.releaseCapture(e);
|
||||
}
|
||||
@@ -378,21 +367,16 @@ const PluginControl =
|
||||
|
||||
let ultraHigh = false;
|
||||
let high = false;
|
||||
if (e.ctrlKey)
|
||||
{
|
||||
if (e.ctrlKey) {
|
||||
ultraHigh = true;
|
||||
}
|
||||
if (e.shiftKey)
|
||||
{
|
||||
if (e.shiftKey) {
|
||||
high = true;
|
||||
}
|
||||
if (e.pointerType === "touch")
|
||||
{
|
||||
if (this.pointersDown >= 3)
|
||||
{
|
||||
if (e.pointerType === "touch") {
|
||||
if (this.pointersDown >= 3) {
|
||||
ultraHigh = true;
|
||||
} else if (this.pointersDown === 2)
|
||||
{
|
||||
} else if (this.pointersDown === 2) {
|
||||
high = true;
|
||||
}
|
||||
}
|
||||
@@ -411,28 +395,24 @@ const PluginControl =
|
||||
}
|
||||
private lastTapMs = 0;
|
||||
|
||||
resetToDefaultValue(uiControl: UiControl): void
|
||||
{
|
||||
resetToDefaultValue(uiControl: UiControl): void {
|
||||
let value = uiControl.default_value;
|
||||
this.model.setPedalboardControl(this.props.instanceId,uiControl.symbol,value);
|
||||
this.model.setPedalboardControl(this.props.instanceId, uiControl.symbol, value);
|
||||
}
|
||||
onPointerDoubleTap() {
|
||||
let uiControl = this.props.uiControl;
|
||||
if (uiControl)
|
||||
{
|
||||
if (uiControl.isDial())
|
||||
{
|
||||
if (uiControl) {
|
||||
if (uiControl.isDial()) {
|
||||
this.resetToDefaultValue(uiControl);
|
||||
}
|
||||
}
|
||||
}
|
||||
onPointerTap() {
|
||||
let tapTime = Date.now();
|
||||
let dT = tapTime-this.lastTapMs;
|
||||
let dT = tapTime - this.lastTapMs;
|
||||
this.lastTapMs = tapTime;
|
||||
|
||||
if (dT < 500)
|
||||
{
|
||||
if (dT < 500) {
|
||||
this.onPointerDoubleTap();
|
||||
}
|
||||
}
|
||||
@@ -442,32 +422,29 @@ const PluginControl =
|
||||
|
||||
if (this.isCapturedPointer(e)) {
|
||||
--this.pointersDown;
|
||||
|
||||
|
||||
|
||||
e.preventDefault();
|
||||
let dRange = this.updateRange(e)
|
||||
this.previewRange(dRange, true);
|
||||
|
||||
this.releaseCapture(e);
|
||||
if (this.isTap)
|
||||
{
|
||||
let ms = Date.now()-this.tapStartMs;
|
||||
if (ms < 200)
|
||||
{
|
||||
if (this.isTap) {
|
||||
let ms = Date.now() - this.tapStartMs;
|
||||
if (ms < 200) {
|
||||
this.onPointerTap();
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
--this.pointersDown;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
releaseCapture(e: PointerEvent<SVGSVGElement>)
|
||||
{
|
||||
releaseCapture(e: PointerEvent<SVGSVGElement>) {
|
||||
let img = this.imgRef.current;
|
||||
|
||||
|
||||
if (img && img.style) {
|
||||
img.releasePointerCapture(e.pointerId);
|
||||
img.style.opacity = "" + DEFAULT_OPACITY;
|
||||
@@ -480,14 +457,14 @@ const PluginControl =
|
||||
}
|
||||
document.body.removeEventListener(
|
||||
"pointerdown",
|
||||
this.onBodyPointerDownCapture,true
|
||||
);
|
||||
this.onBodyPointerDownCapture, true
|
||||
);
|
||||
|
||||
this.mouseDown = false;
|
||||
|
||||
}
|
||||
|
||||
clickSlop() {
|
||||
clickSlop() {
|
||||
return 3.5; // maybe larger on touch devices.
|
||||
}
|
||||
onPointerMove(e: PointerEvent<SVGSVGElement>): void {
|
||||
@@ -499,15 +476,27 @@ const PluginControl =
|
||||
let x = e.clientX;
|
||||
let y = e.clientY;
|
||||
let dx = x - this.startX;
|
||||
let dy = y-this.startY;
|
||||
let distance = Math.sqrt(dx*dx+dy*dy);
|
||||
if (distance >= this.clickSlop())
|
||||
{
|
||||
let dy = y - this.startY;
|
||||
let distance = Math.sqrt(dx * dx + dy * dy);
|
||||
if (distance >= this.clickSlop()) {
|
||||
this.isTap = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleTriggerMouseDown() {
|
||||
let uiControl = this.props.uiControl;
|
||||
if (uiControl)
|
||||
{
|
||||
this.model.setPedalboardControl(this.props.instanceId,uiControl.symbol,1);
|
||||
}
|
||||
}
|
||||
handleTriggerMouseUp() {
|
||||
let uiControl = this.props.uiControl;
|
||||
if (uiControl)
|
||||
{
|
||||
this.model.setPedalboardControl(this.props.instanceId,uiControl.symbol,0);
|
||||
}
|
||||
}
|
||||
previewInputValue(value: number, commitValue: boolean) {
|
||||
let range = this.valueToRange(value);
|
||||
value = this.rangeToValue(range);
|
||||
@@ -547,13 +536,12 @@ const PluginControl =
|
||||
}
|
||||
let inputElement = this.inputRef.current;
|
||||
if (inputElement) {
|
||||
let v = this.props.uiControl?.formatShortValue(value)??"";
|
||||
let v = this.props.uiControl?.formatShortValue(value) ?? "";
|
||||
inputElement.value = v;
|
||||
}
|
||||
let displayValue = this.displayValueRef.current;
|
||||
if (displayValue)
|
||||
{
|
||||
let v = this.formatDisplayValue(this.props.uiControl,value);
|
||||
if (displayValue) {
|
||||
let v = this.formatDisplayValue(this.props.uiControl, value);
|
||||
displayValue.childNodes[0].textContent = v;
|
||||
}
|
||||
let selectElement = this.selectRef.current;
|
||||
@@ -577,9 +565,9 @@ const PluginControl =
|
||||
}
|
||||
onSelectChanged(e: any, value: any) {
|
||||
let target = e.target;
|
||||
setTimeout(()=> {
|
||||
setTimeout(() => {
|
||||
this.props.onChange(target.value);
|
||||
},0);
|
||||
}, 0);
|
||||
|
||||
|
||||
}
|
||||
@@ -610,7 +598,7 @@ const PluginControl =
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Select variant="standard"
|
||||
<Select variant="standard"
|
||||
ref={this.selectRef}
|
||||
value={control.clampSelectValue(value)}
|
||||
onChange={this.onSelectChanged}
|
||||
@@ -619,7 +607,7 @@ const PluginControl =
|
||||
id: 'id' + control.symbol,
|
||||
style: { fontSize: FONT_SIZE }
|
||||
}}
|
||||
style={{ marginLeft: 4, marginRight: 4, width: 140,fontSize: 14 }}
|
||||
style={{ marginLeft: 4, marginRight: 4, width: 140, fontSize: 14 }}
|
||||
>
|
||||
{control.scale_points.map((scale_point: ScalePoint) => (
|
||||
<MenuItem key={scale_point.value} value={scale_point.value}>{scale_point.label}</MenuItem>
|
||||
@@ -645,11 +633,10 @@ const PluginControl =
|
||||
value = Math.round(value);
|
||||
}
|
||||
let range: number;
|
||||
if (uiControl.is_logarithmic)
|
||||
{
|
||||
range = Math.log(value/uiControl.min_value)/Math.log(uiControl.max_value/uiControl.min_value);
|
||||
if (uiControl.range_steps > 1) {
|
||||
range = Math.round(range*(uiControl.range_steps-1))/(uiControl.range_steps-1);
|
||||
if (uiControl.is_logarithmic) {
|
||||
range = Math.log(value / uiControl.min_value) / Math.log(uiControl.max_value / uiControl.min_value);
|
||||
if (uiControl.range_steps > 1) {
|
||||
range = Math.round(range * (uiControl.range_steps - 1)) / (uiControl.range_steps - 1);
|
||||
}
|
||||
} else {
|
||||
range = (value - uiControl.min_value) / (uiControl.max_value - uiControl.min_value);
|
||||
@@ -666,17 +653,14 @@ const PluginControl =
|
||||
let uiControl = this.props.uiControl;
|
||||
if (uiControl) {
|
||||
if (uiControl.range_steps > 1) {
|
||||
range = Math.round(range * (uiControl.range_steps-1)) / (uiControl.range_steps-1);
|
||||
range = Math.round(range * (uiControl.range_steps - 1)) / (uiControl.range_steps - 1);
|
||||
}
|
||||
let value: number;
|
||||
if (uiControl.min_value === uiControl.max_value)
|
||||
{
|
||||
if (uiControl.min_value === uiControl.max_value) {
|
||||
value = uiControl.min_value;
|
||||
} else
|
||||
{
|
||||
if (uiControl.is_logarithmic)
|
||||
{
|
||||
value = uiControl.min_value*Math.pow(uiControl.max_value/uiControl.min_value,range);
|
||||
} else {
|
||||
if (uiControl.is_logarithmic) {
|
||||
value = uiControl.min_value * Math.pow(uiControl.max_value / uiControl.min_value, range);
|
||||
} else {
|
||||
value = range * (uiControl.max_value - uiControl.min_value) + uiControl.min_value;
|
||||
}
|
||||
@@ -724,45 +708,71 @@ const PluginControl =
|
||||
let isSelect = control.isSelect();
|
||||
let isAbSwitch = control.isAbToggle();
|
||||
let isOnOffSwitch = control.isOnOffSwitch();
|
||||
let isTrigger = control.isTrigger();
|
||||
|
||||
if (isAbSwitch) {
|
||||
switchText = control.scale_points[0].value === value ? control.scale_points[0].label : control.scale_points[1].label;
|
||||
}
|
||||
|
||||
let item_width = isSelect ? 160 : 80;
|
||||
let item_width: number | undefined = isSelect ? 160 : 80;
|
||||
if (isTrigger)
|
||||
{
|
||||
item_width = undefined;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div ref={this.frameRef} style={{ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "flex-start", width: item_width, margin: 8 }}>
|
||||
{/* TITLE SECTION */}
|
||||
<div style={{ flex: "0 0 auto", width: "100%", marginBottom: 8,marginLeft: isSelect? 16: 0, marginRight: 0 }}>
|
||||
<Typography variant="caption" display="block" noWrap style={{
|
||||
<div style={{ flex: "0 0 auto", alignSelf:"stretch", marginBottom: 8, marginLeft: isSelect ? 16 : 0, marginRight: 0 }}>
|
||||
<Typography variant="caption" display="block" noWrap style={{
|
||||
width: "100%",
|
||||
textAlign: isSelect ? "left" : "center"
|
||||
}}> {control.name}</Typography>
|
||||
}}> {isTrigger ? "\u00A0" : control.name}</Typography>
|
||||
</div>
|
||||
{/* CONTROL SECTION */}
|
||||
|
||||
<div style={{ flex: "0 0 auto" }}>
|
||||
{(isSelect || isAbSwitch || isOnOffSwitch) ? (
|
||||
this.makeSelect(control, value)
|
||||
) : (
|
||||
<div style={{ flex: "0 1 auto" }}>
|
||||
<DialIcon ref={this.imgRef}
|
||||
style={{ overscrollBehavior: "none", touchAction: "none", fill: dialColor,
|
||||
width: 36, height: 36, opacity: DEFAULT_OPACITY, transform: this.getRotationTransform() }}
|
||||
onTouchStart={this.onTouchStart} onTouchMove={this.onTouchMove}
|
||||
onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} onPointerMoveCapture={this.onPointerMove} onDrag={this.onDrag}
|
||||
|
||||
/>
|
||||
</div>
|
||||
{isTrigger ? (
|
||||
<Button variant="contained" color="primary" size="small"
|
||||
onMouseDown={
|
||||
(evt)=> { this.handleTriggerMouseDown(); }
|
||||
}
|
||||
onMouseUp={
|
||||
(evt)=> { this.handleTriggerMouseUp(); }
|
||||
}
|
||||
style={{
|
||||
textTransform: "none",
|
||||
background: (isDarkMode() ? "#6750A4" : undefined),
|
||||
marginLeft: 8, marginRight: 8,minWidth:60
|
||||
}}
|
||||
|
||||
>
|
||||
{control.name}
|
||||
</Button>
|
||||
)
|
||||
: ((isSelect || isAbSwitch || isOnOffSwitch) ? (
|
||||
this.makeSelect(control, value)
|
||||
) : (
|
||||
<div style={{ flex: "0 1 auto" }}>
|
||||
<DialIcon ref={this.imgRef}
|
||||
style={{
|
||||
overscrollBehavior: "none", touchAction: "none", fill: dialColor,
|
||||
width: 36, height: 36, opacity: DEFAULT_OPACITY, transform: this.getRotationTransform()
|
||||
}}
|
||||
onTouchStart={this.onTouchStart} onTouchMove={this.onTouchMove}
|
||||
onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} onPointerMoveCapture={this.onPointerMove} onDrag={this.onDrag}
|
||||
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* LABEL/EDIT SECTION*/}
|
||||
<div style={{ flex: "0 0 auto", position: "relative", width: 60 }}>
|
||||
{(!(isSelect || isOnOffSwitch)) &&
|
||||
{(!(isSelect || isOnOffSwitch || isTrigger)) &&
|
||||
(
|
||||
(isAbSwitch) ? (
|
||||
<Typography variant="caption" display="block" noWrap style={{
|
||||
@@ -770,7 +780,7 @@ const PluginControl =
|
||||
}}> {switchText} </Typography>
|
||||
) : (
|
||||
<div>
|
||||
<Input key={value}
|
||||
<Input key={value}
|
||||
type="number"
|
||||
defaultValue={control.formatShortValue(value)}
|
||||
error={this.state.error}
|
||||
@@ -785,10 +795,10 @@ const PluginControl =
|
||||
|
||||
onKeyPress={this.onInputKeyPress} />
|
||||
<div className={classes.displayValue} ref={this.displayValueRef} onClick={(e) => { this.inputRef.current!.focus(); }} >
|
||||
<Typography noWrap color="inherit" style={{ fontSize: "12.8px", paddingTop:4, paddingBottom: 6 }}
|
||||
|
||||
>
|
||||
{this.formatDisplayValue(control, value)}</Typography>
|
||||
<Typography noWrap color="inherit" style={{ fontSize: "12.8px", paddingTop: 4, paddingBottom: 6 }}
|
||||
|
||||
>
|
||||
{this.formatDisplayValue(control, value)}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -37,6 +37,7 @@ export const SplitTypeControl: UiControl = new UiControl().deserialize({
|
||||
enumeration_property: true,
|
||||
not_on_gui: false,
|
||||
toggled_property: false,
|
||||
trigger_property: false,
|
||||
scale_points: ScalePoint.deserialize_array(
|
||||
[
|
||||
{ value: 0, label: "A/B" },
|
||||
@@ -60,6 +61,7 @@ export const SplitAbControl: UiControl = new UiControl().deserialize({
|
||||
enumeration_property: true,
|
||||
not_on_gui: false,
|
||||
toggled_property: false,
|
||||
trigger_property: false,
|
||||
scale_points: ScalePoint.deserialize_array(
|
||||
[
|
||||
{ value: 0, label: "A" },
|
||||
@@ -83,6 +85,8 @@ export const SplitMixControl: UiControl = new UiControl().deserialize({
|
||||
enumeration_property: false,
|
||||
not_on_gui: false,
|
||||
toggled_property: false,
|
||||
trigger_property: false,
|
||||
|
||||
scale_points: []
|
||||
});
|
||||
|
||||
@@ -100,6 +104,8 @@ export const SplitPanLeftControl: UiControl = new UiControl().deserialize({
|
||||
enumeration_property: false,
|
||||
not_on_gui: false,
|
||||
toggled_property: false,
|
||||
trigger_property: false,
|
||||
|
||||
scale_points: []
|
||||
});
|
||||
|
||||
@@ -117,6 +123,8 @@ export const SplitVolLeftControl: UiControl = new UiControl().deserialize({
|
||||
enumeration_property: false,
|
||||
not_on_gui: false,
|
||||
toggled_property: false,
|
||||
trigger_property: false,
|
||||
|
||||
units: Units.db,
|
||||
scale_points: [
|
||||
new ScalePoint().deserialize({
|
||||
@@ -141,6 +149,8 @@ export const SplitPanRightControl: UiControl = new UiControl().deserialize({
|
||||
enumeration_property: false,
|
||||
not_on_gui: false,
|
||||
toggled_property: false,
|
||||
trigger_property: false,
|
||||
|
||||
scale_points: []
|
||||
});
|
||||
|
||||
@@ -158,6 +168,8 @@ export const SplitVolRightControl: UiControl = new UiControl().deserialize({
|
||||
enumeration_property: false,
|
||||
not_on_gui: false,
|
||||
toggled_property: false,
|
||||
trigger_property: false,
|
||||
|
||||
units: Units.db,
|
||||
scale_points: [
|
||||
new ScalePoint().deserialize({
|
||||
|
||||
@@ -208,7 +208,7 @@ export default class UploadFileDialog extends ResizeResponsiveComponent<UploadFi
|
||||
{
|
||||
throw new Error("Invalid file extension.");
|
||||
}
|
||||
let filename = await this.model.uploadFile(
|
||||
let filename = await this.model.uploadUserFile(
|
||||
this.props.uploadPage,
|
||||
this.uploadList[i].file,
|
||||
"application/octet-stream",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
};
|
||||
}
|
||||
@@ -20,16 +20,26 @@
|
||||
#include "FilePropertyDirectoryTree.hpp"
|
||||
|
||||
using namespace pipedal;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
FilePropertyDirectoryTree::FilePropertyDirectoryTree()
|
||||
{
|
||||
}
|
||||
FilePropertyDirectoryTree::FilePropertyDirectoryTree(const std::string &directoryName)
|
||||
: directoryName_(directoryName)
|
||||
: directoryName_(directoryName),
|
||||
displayName_(fs::path(directoryName).filename().string())
|
||||
{
|
||||
}
|
||||
|
||||
FilePropertyDirectoryTree::FilePropertyDirectoryTree(const std::string &directoryName, const std::string &displayName)
|
||||
: directoryName_(directoryName),
|
||||
displayName_(displayName)
|
||||
{
|
||||
}
|
||||
|
||||
JSON_MAP_BEGIN(FilePropertyDirectoryTree)
|
||||
JSON_MAP_REFERENCE(FilePropertyDirectoryTree, directoryName)
|
||||
JSON_MAP_REFERENCE(FilePropertyDirectoryTree, displayName)
|
||||
JSON_MAP_REFERENCE(FilePropertyDirectoryTree, isProtected)
|
||||
JSON_MAP_REFERENCE(FilePropertyDirectoryTree, children)
|
||||
JSON_MAP_END()
|
||||
|
||||
@@ -29,7 +29,10 @@ namespace pipedal {
|
||||
|
||||
FilePropertyDirectoryTree();
|
||||
FilePropertyDirectoryTree(const std::string&directoryName);
|
||||
FilePropertyDirectoryTree(const std::string&directoryName,const std::string&displayName);
|
||||
std::string directoryName_;
|
||||
std::string displayName_;
|
||||
bool isProtected_ = false;
|
||||
std::vector<std::unique_ptr<FilePropertyDirectoryTree>> children_;
|
||||
|
||||
DECLARE_JSON_MAP(FilePropertyDirectoryTree);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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_; }
|
||||
|
||||
+92
-54
@@ -46,6 +46,7 @@
|
||||
#include "PiPedalException.hpp"
|
||||
#include "StdErrorCapture.hpp"
|
||||
#include "util.hpp"
|
||||
#include "ModFileTypes.hpp"
|
||||
|
||||
#include "Locale.hpp"
|
||||
|
||||
@@ -118,6 +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);
|
||||
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);
|
||||
@@ -191,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()
|
||||
@@ -477,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);
|
||||
|
||||
@@ -624,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)
|
||||
@@ -932,6 +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);
|
||||
|
||||
LilvScalePoints *pScalePoints = lilv_port_get_scale_points(plugin, pPort);
|
||||
LILV_FOREACH(scale_points, iSP, pScalePoints)
|
||||
@@ -1387,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())
|
||||
@@ -1421,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))
|
||||
{
|
||||
@@ -1433,9 +1471,9 @@ static bool anyTargetFilesExist(const std::filesystem::path &sourceDirectory, co
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (const std::exception&) {
|
||||
|
||||
}
|
||||
catch (const std::exception &)
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1450,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()));
|
||||
}
|
||||
@@ -1499,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();
|
||||
@@ -1521,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))
|
||||
@@ -1593,7 +1630,7 @@ json_map::storage_type<Lv2PortInfo> Lv2PortInfo::jmap{
|
||||
MAP_REF(Lv2PortInfo, is_logarithmic),
|
||||
MAP_REF(Lv2PortInfo, display_priority),
|
||||
MAP_REF(Lv2PortInfo, range_steps),
|
||||
MAP_REF(Lv2PortInfo, trigger),
|
||||
MAP_REF(Lv2PortInfo, trigger_property),
|
||||
MAP_REF(Lv2PortInfo, integer_property),
|
||||
MAP_REF(Lv2PortInfo, enumeration_property),
|
||||
MAP_REF(Lv2PortInfo, toggled_property),
|
||||
@@ -1662,6 +1699,7 @@ json_map::storage_type<Lv2PluginUiPort> Lv2PluginUiPort::jmap{{
|
||||
MAP_REF(Lv2PluginUiPort, enumeration_property),
|
||||
MAP_REF(Lv2PluginUiPort, not_on_gui),
|
||||
MAP_REF(Lv2PluginUiPort, toggled_property),
|
||||
MAP_REF(Lv2PluginUiPort, trigger_property),
|
||||
MAP_REF(Lv2PluginUiPort, scale_points),
|
||||
MAP_REF(Lv2PluginUiPort, port_group),
|
||||
|
||||
|
||||
+6
-2
@@ -214,7 +214,7 @@ namespace pipedal
|
||||
bool is_logarithmic_ = false;
|
||||
int display_priority_ = -1;
|
||||
int range_steps_ = 0;
|
||||
bool trigger_ = false;
|
||||
bool trigger_property_ = false;
|
||||
bool integer_property_ = false;
|
||||
bool enumeration_property_ = false;
|
||||
bool toggled_property_ = false;
|
||||
@@ -285,7 +285,7 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET_SCALAR(is_logarithmic);
|
||||
LV2_PROPERTY_GETSET_SCALAR(display_priority);
|
||||
LV2_PROPERTY_GETSET_SCALAR(range_steps);
|
||||
LV2_PROPERTY_GETSET_SCALAR(trigger);
|
||||
LV2_PROPERTY_GETSET_SCALAR(trigger_property);
|
||||
LV2_PROPERTY_GETSET_SCALAR(integer_property);
|
||||
LV2_PROPERTY_GETSET_SCALAR(enumeration_property);
|
||||
LV2_PROPERTY_GETSET_SCALAR(toggled_property);
|
||||
@@ -496,6 +496,7 @@ namespace pipedal
|
||||
default_value_(pPort->default_value()), range_steps_(pPort->range_steps()), display_priority_(pPort->display_priority()),
|
||||
is_logarithmic_(pPort->is_logarithmic()), integer_property_(pPort->integer_property()), enumeration_property_(pPort->enumeration_property()),
|
||||
toggled_property_(pPort->toggled_property()), not_on_gui_(pPort->not_on_gui()), scale_points_(pPort->scale_points()),
|
||||
trigger_property_(pPort->trigger_property()),
|
||||
comment_(pPort->comment()), units_(pPort->units()),
|
||||
connection_optional_(pPort->connection_optional())
|
||||
{
|
||||
@@ -536,6 +537,7 @@ namespace pipedal
|
||||
bool enumeration_property_ = false;
|
||||
bool not_on_gui_ = false;
|
||||
bool toggled_property_ = false;
|
||||
bool trigger_property_ = false;
|
||||
std::vector<Lv2ScalePoint> scale_points_;
|
||||
std::string port_group_;
|
||||
|
||||
@@ -561,6 +563,7 @@ namespace pipedal
|
||||
LV2_PROPERTY_GETSET_SCALAR(integer_property);
|
||||
LV2_PROPERTY_GETSET_SCALAR(enumeration_property);
|
||||
LV2_PROPERTY_GETSET_SCALAR(toggled_property);
|
||||
LV2_PROPERTY_GETSET_SCALAR(trigger_property);
|
||||
LV2_PROPERTY_GETSET_SCALAR(not_on_gui);
|
||||
LV2_PROPERTY_GETSET(scale_points);
|
||||
LV2_PROPERTY_GETSET(units);
|
||||
@@ -664,6 +667,7 @@ namespace pipedal
|
||||
AutoLilvNode core__toggled;
|
||||
AutoLilvNode core__connectionOptional;
|
||||
AutoLilvNode portprops__not_on_gui_property_uri;
|
||||
AutoLilvNode portprops__trigger;
|
||||
AutoLilvNode midi__event;
|
||||
AutoLilvNode core__designation;
|
||||
AutoLilvNode portgroups__group;
|
||||
|
||||
+276
-69
@@ -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,50 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
static bool hasSyntheticModRoot(const UiFileProperty &fileProperty)
|
||||
{
|
||||
return (fileProperty.modDirectories().size() > 1 || (fileProperty.modDirectories().size() == 1 && fileProperty.useLegacyModDirectory()));
|
||||
|
||||
}
|
||||
|
||||
Storage::Storage()
|
||||
{
|
||||
SetConfigRoot("~/var/Config");
|
||||
@@ -1648,63 +1685,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 (hasSyntheticModRoot(fileProperty))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1715,29 +1879,32 @@ bool Storage::IsValidSampleFileName(const std::filesystem::path &fileName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!ensureNoDotDot(fileName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::filesystem::path audioFilePath = this->GetPluginUploadDirectory();
|
||||
|
||||
std::filesystem::path parentDirectory = fileName.parent_path();
|
||||
while (true)
|
||||
|
||||
auto iTarget = parentDirectory.begin();
|
||||
|
||||
for (auto i = audioFilePath.begin(); i != audioFilePath.end(); ++i)
|
||||
{
|
||||
if (!parentDirectory.has_parent_path())
|
||||
{
|
||||
if (iTarget == parentDirectory.end()) {
|
||||
return false;
|
||||
}
|
||||
std::string name = parentDirectory.filename().string();
|
||||
if (parentDirectory == audioFilePath)
|
||||
return true;
|
||||
parentDirectory = parentDirectory.parent_path();
|
||||
if (parentDirectory.string().length() < audioFilePath.string().length())
|
||||
{
|
||||
if (*i != *iTarget) {
|
||||
return false;
|
||||
}
|
||||
++iTarget;
|
||||
}
|
||||
while (iTarget != parentDirectory.end())
|
||||
{
|
||||
if (iTarget->string() == "..")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
++iTarget;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void Storage::DeleteSampleFile(const std::filesystem::path &fileName)
|
||||
{
|
||||
@@ -1753,9 +1920,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 {
|
||||
@@ -1769,13 +1945,13 @@ void Storage::DeleteSampleFile(const std::filesystem::path &fileName)
|
||||
}
|
||||
std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, const std::string &filename)
|
||||
{
|
||||
if (!ensureNoDotDot(directory))
|
||||
{
|
||||
throw std::logic_error(SS("Invalide filename: " << filename));
|
||||
}
|
||||
std::filesystem::path filePath{filename};
|
||||
|
||||
std::filesystem::path result = this->GetPluginUploadDirectory() / directory / filename;
|
||||
std::filesystem::path result = fs::path(directory) / filename;
|
||||
if (!result.is_absolute())
|
||||
{
|
||||
result = this->GetPluginUploadDirectory() / result;
|
||||
}
|
||||
if (!this->IsValidSampleFileName(result))
|
||||
{
|
||||
throw std::logic_error(SS("Invalid upload path: " << result));
|
||||
@@ -1898,8 +2074,8 @@ void Storage::FillSampleDirectoryTree(FilePropertyDirectoryTree*node, const std:
|
||||
{
|
||||
if (child.is_directory())
|
||||
{
|
||||
const auto& childPath = child.path();
|
||||
FilePropertyDirectoryTree::ptr childTree = std::make_unique<FilePropertyDirectoryTree>(childPath.filename());
|
||||
const auto& childPath = child.path();
|
||||
FilePropertyDirectoryTree::ptr childTree = std::make_unique<FilePropertyDirectoryTree>(childPath);
|
||||
FillSampleDirectoryTree(childTree.get(),childPath);
|
||||
node->children_.push_back(std::move(childTree));
|
||||
}
|
||||
@@ -1911,22 +2087,53 @@ void Storage::FillSampleDirectoryTree(FilePropertyDirectoryTree*node, const std:
|
||||
return collator->Compare(left->directoryName_,right->directoryName_) < 0;
|
||||
});
|
||||
}
|
||||
FilePropertyDirectoryTree::ptr Storage::GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty) const
|
||||
FilePropertyDirectoryTree::ptr Storage::GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty)
|
||||
{
|
||||
FilePropertyDirectoryTree::ptr result = std::make_unique<FilePropertyDirectoryTree>("");
|
||||
if (uiFileProperty.directory().empty())
|
||||
{
|
||||
throw std::runtime_error("Invalid uiFileProperty");
|
||||
}
|
||||
if (!ensureNoDotDot(uiFileProperty.directory()))
|
||||
{
|
||||
throw std::runtime_error("Invalid uiFileProperty");
|
||||
}
|
||||
std::filesystem::path rootDirectory = this->GetPluginUploadDirectory() / uiFileProperty.directory();
|
||||
fs::path uploadDirectory = this->GetPluginUploadDirectory();
|
||||
|
||||
FillSampleDirectoryTree(result.get(),rootDirectory);
|
||||
if (hasSyntheticModRoot(uiFileProperty))
|
||||
{
|
||||
FilePropertyDirectoryTree::ptr result = std::make_unique<FilePropertyDirectoryTree>("","Home");
|
||||
result->isProtected_ = true;
|
||||
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 (uiFileProperty.useLegacyModDirectory()) {
|
||||
auto childPath = uploadDirectory / uiFileProperty.directory();
|
||||
FilePropertyDirectoryTree::ptr child = std::make_unique<FilePropertyDirectoryTree>(
|
||||
childPath
|
||||
);
|
||||
FillSampleDirectoryTree(child.get(),childPath);
|
||||
result->children_.push_back(std::move(child));
|
||||
}
|
||||
return result;
|
||||
|
||||
} else {
|
||||
if (uiFileProperty.directory().empty())
|
||||
{
|
||||
throw std::runtime_error("Invalid uiFileProperty");
|
||||
}
|
||||
if (!ensureNoDotDot(uiFileProperty.directory()))
|
||||
{
|
||||
throw std::runtime_error("Invalid uiFileProperty");
|
||||
}
|
||||
std::filesystem::path rootDirectory = uploadDirectory / uiFileProperty.directory();
|
||||
FilePropertyDirectoryTree::ptr result = std::make_unique<FilePropertyDirectoryTree>(rootDirectory.string(),"Home");
|
||||
|
||||
FillSampleDirectoryTree(result.get(),rootDirectory);
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+4
-2
@@ -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);
|
||||
@@ -224,7 +226,7 @@ public:
|
||||
const std::string&oldRelativePath,
|
||||
const std::string&newRelativePath,
|
||||
const UiFileProperty&uiFileProperty);
|
||||
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty) const;
|
||||
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty);
|
||||
};
|
||||
|
||||
|
||||
|
||||
+152
-119
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2024 Robin Davies
|
||||
// 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
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "UpdaterSecurity.hpp"
|
||||
#include "TemporaryFile.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
#define OLD_PRESET_EXTENSION ".piPreset"
|
||||
#define PRESET_EXTENSION ".piPreset"
|
||||
@@ -50,40 +50,52 @@ using namespace pipedal;
|
||||
using namespace boost::system;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class UserUploadResponse
|
||||
{
|
||||
public:
|
||||
std::string errorMessage_;
|
||||
std::string path_;
|
||||
DECLARE_JSON_MAP(UserUploadResponse);
|
||||
};
|
||||
JSON_MAP_BEGIN(UserUploadResponse)
|
||||
JSON_MAP_REFERENCE(UserUploadResponse, errorMessage)
|
||||
JSON_MAP_REFERENCE(UserUploadResponse, path)
|
||||
JSON_MAP_END()
|
||||
|
||||
static bool IsZipFile(const std::filesystem::path &path)
|
||||
{
|
||||
std::ifstream f(path);
|
||||
if (!f.is_open()) return false;
|
||||
if (!f.is_open())
|
||||
return false;
|
||||
|
||||
char c[4];
|
||||
memset(c,0,sizeof(c));
|
||||
memset(c, 0, sizeof(c));
|
||||
|
||||
f >> c[0] >> c[1] >> c[2] >> c[3];
|
||||
|
||||
// official file header according to PKware documetnation.
|
||||
return c[0] == 0x50 && c[1] == 0x4B && c[2] == 0x03 && c[3] == 0x04;
|
||||
|
||||
|
||||
}
|
||||
|
||||
class ExtensionChecker {
|
||||
class ExtensionChecker
|
||||
{
|
||||
public:
|
||||
ExtensionChecker(const std::string&extensionList)
|
||||
:extensions(split(extensionList,','))
|
||||
ExtensionChecker(const std::string &extensionList)
|
||||
: extensions(split(extensionList, ','))
|
||||
{
|
||||
}
|
||||
bool IsValidExtension(const std::string&extension)
|
||||
bool IsValidExtension(const std::string &extension)
|
||||
{
|
||||
if (extensions.size() == 0)
|
||||
if (extensions.size() == 0)
|
||||
return true;
|
||||
for (const auto&ext: extensions)
|
||||
for (const auto &ext : extensions)
|
||||
{
|
||||
if (ext == extension)
|
||||
if (ext == extension)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> extensions;
|
||||
};
|
||||
@@ -133,7 +145,8 @@ public:
|
||||
else if (segment == "uploadBank")
|
||||
{
|
||||
return true;
|
||||
} else if (segment == "uploadUserFile")
|
||||
}
|
||||
else if (segment == "uploadUserFile")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -159,7 +172,7 @@ public:
|
||||
PluginPresets pluginPresets = model->GetPluginPresets(pluginUri);
|
||||
|
||||
std::stringstream s;
|
||||
json_writer writer(s,false);
|
||||
json_writer writer(s, false);
|
||||
writer.write(pluginPresets);
|
||||
*pContent = s.str();
|
||||
}
|
||||
@@ -176,7 +189,7 @@ public:
|
||||
file.selectedPreset(newInstanceId);
|
||||
|
||||
std::stringstream s;
|
||||
json_writer writer(s,false);
|
||||
json_writer writer(s, false);
|
||||
writer.write(file);
|
||||
*pContent = s.str();
|
||||
*pName = pedalboard.name();
|
||||
@@ -210,12 +223,11 @@ public:
|
||||
std::string content;
|
||||
GetPluginPresets(request_uri, &name, &content);
|
||||
|
||||
TemporaryFile tmpFile { WEB_TEMP_DIR};
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model),content);
|
||||
TemporaryFile tmpFile{WEB_TEMP_DIR};
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model), content);
|
||||
presetbundleWriter->WriteToFile(tmpFile.Path());
|
||||
size_t contentLength = std::filesystem::file_size(tmpFile.Path());
|
||||
|
||||
|
||||
res.set(HttpField::content_type, PLUGIN_PRESETS_MIME_TYPE);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
res.setContentLength(content.length());
|
||||
@@ -228,8 +240,8 @@ public:
|
||||
std::string content;
|
||||
GetPreset(request_uri, &name, &content);
|
||||
|
||||
TemporaryFile tmpFile { WEB_TEMP_DIR};
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content);
|
||||
TemporaryFile tmpFile{WEB_TEMP_DIR};
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model), content);
|
||||
presetbundleWriter->WriteToFile(tmpFile.Path());
|
||||
size_t contentLength = std::filesystem::file_size(tmpFile.Path());
|
||||
|
||||
@@ -245,14 +257,11 @@ public:
|
||||
std::string content;
|
||||
GetBank(request_uri, &name, &content);
|
||||
|
||||
TemporaryFile tmpFile { WEB_TEMP_DIR};
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content);
|
||||
TemporaryFile tmpFile{WEB_TEMP_DIR};
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model), content);
|
||||
presetbundleWriter->WriteToFile(tmpFile.Path());
|
||||
size_t contentLength = std::filesystem::file_size(tmpFile.Path());
|
||||
|
||||
|
||||
|
||||
|
||||
res.set(HttpField::content_type, BANK_MIME_TYPE);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
|
||||
@@ -290,12 +299,11 @@ public:
|
||||
std::string content;
|
||||
GetPluginPresets(request_uri, &name, &content);
|
||||
|
||||
std::shared_ptr<TemporaryFile> tmpFile =std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model),content);
|
||||
std::shared_ptr<TemporaryFile> tmpFile = std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model), content);
|
||||
presetbundleWriter->WriteToFile(tmpFile->Path());
|
||||
size_t contentLength = std::filesystem::file_size(tmpFile->Path());
|
||||
|
||||
|
||||
res.set(HttpField::content_type, PLUGIN_PRESETS_MIME_TYPE);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
res.setContentLength(contentLength);
|
||||
@@ -308,12 +316,11 @@ public:
|
||||
std::string content;
|
||||
GetPreset(request_uri, &name, &content);
|
||||
|
||||
std::shared_ptr<TemporaryFile> tmpFile =std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content);
|
||||
std::shared_ptr<TemporaryFile> tmpFile = std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model), content);
|
||||
presetbundleWriter->WriteToFile(tmpFile->Path());
|
||||
size_t contentLength = std::filesystem::file_size(tmpFile->Path());
|
||||
|
||||
|
||||
res.set(HttpField::content_type, PRESET_MIME_TYPE);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
res.setContentLength(contentLength);
|
||||
@@ -326,13 +333,11 @@ public:
|
||||
std::string content;
|
||||
GetBank(request_uri, &name, &content);
|
||||
|
||||
std::shared_ptr<TemporaryFile> tmpFile =std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content);
|
||||
std::shared_ptr<TemporaryFile> tmpFile = std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
|
||||
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model), content);
|
||||
presetbundleWriter->WriteToFile(tmpFile->Path());
|
||||
size_t contentLength = std::filesystem::file_size(tmpFile->Path());
|
||||
|
||||
|
||||
|
||||
res.set(HttpField::content_type, BANK_MIME_TYPE);
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
res.setContentLength(contentLength);
|
||||
@@ -356,40 +361,49 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
static std::string GetFirstFolderOrFile(const std::vector<std::string>&fileNames)
|
||||
static std::string GetFirstFolderOrFile(const std::vector<std::string> &fileNames)
|
||||
{
|
||||
for (const auto &fileName: fileNames)
|
||||
for (const auto &fileName : fileNames)
|
||||
{
|
||||
size_t nPos = fileName.find('/');
|
||||
if (nPos != std::string::npos) {
|
||||
return fileName.substr(0,nPos);
|
||||
if (nPos != std::string::npos)
|
||||
{
|
||||
return fileName.substr(0, nPos);
|
||||
}
|
||||
}
|
||||
if (fileNames.size() == 0)
|
||||
{
|
||||
return 0;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
return fileNames[0];
|
||||
}
|
||||
}
|
||||
static bool HasSingleRootDirectory(ZipFileReader&zipFile) {
|
||||
static bool HasSingleRootDirectory(ZipFileReader &zipFile)
|
||||
{
|
||||
bool hasDirectory = false;
|
||||
std::string previousDirectory;
|
||||
for (auto& file: zipFile.GetFiles())
|
||||
for (auto &file : zipFile.GetFiles())
|
||||
{
|
||||
auto pos = file.find('/');
|
||||
if (pos == std::string::npos) {
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
// a file in the root.
|
||||
return false;
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string currentRootDirectory = file.substr(0,pos);
|
||||
if (hasDirectory) {
|
||||
std::string currentRootDirectory = file.substr(0, pos);
|
||||
if (hasDirectory)
|
||||
{
|
||||
if (currentRootDirectory != previousDirectory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
hasDirectory = true;
|
||||
previousDirectory = currentRootDirectory;
|
||||
}
|
||||
@@ -412,26 +426,26 @@ public:
|
||||
{
|
||||
PluginPresets presets;
|
||||
fs::path filePath = req.get_body_temporary_file();
|
||||
if (filePath.empty())
|
||||
if (filePath.empty())
|
||||
{
|
||||
throw std::runtime_error("Unexpected.");
|
||||
}
|
||||
if (IsZipFile(filePath))
|
||||
{
|
||||
auto presetReader = PresetBundleReader::LoadPluginPresetsFile(*(this->model),filePath);
|
||||
auto presetReader = PresetBundleReader::LoadPluginPresetsFile(*(this->model), filePath);
|
||||
presetReader->ExtractMediaFiles();
|
||||
|
||||
std::stringstream ss(presetReader->GetPluginPresetsJson());
|
||||
json_reader reader(ss);
|
||||
reader.read(&presets);
|
||||
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
json_reader reader(req.get_body_input_stream());
|
||||
reader.read(&presets);
|
||||
}
|
||||
model->UploadPluginPresets(presets);
|
||||
|
||||
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
std::stringstream sResult;
|
||||
@@ -447,19 +461,21 @@ public:
|
||||
BankFile bankFile;
|
||||
|
||||
fs::path filePath = req.get_body_temporary_file();
|
||||
if (filePath.empty())
|
||||
if (filePath.empty())
|
||||
{
|
||||
throw std::runtime_error("Unexpected.");
|
||||
}
|
||||
if (IsZipFile(filePath))
|
||||
{
|
||||
auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model),filePath);
|
||||
{
|
||||
auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model), filePath);
|
||||
presetReader->ExtractMediaFiles();
|
||||
|
||||
std::stringstream ss(presetReader->GetPresetJson());
|
||||
json_reader reader(ss);
|
||||
reader.read(&bankFile);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// legacy json format, no zip, no media files.
|
||||
json_reader reader(req.get_body_input_stream());
|
||||
reader.read(&bankFile);
|
||||
@@ -487,19 +503,21 @@ public:
|
||||
BankFile bankFile;
|
||||
|
||||
fs::path filePath = req.get_body_temporary_file();
|
||||
if (filePath.empty())
|
||||
if (filePath.empty())
|
||||
{
|
||||
throw std::runtime_error("Unexpected.");
|
||||
}
|
||||
if (IsZipFile(filePath))
|
||||
{
|
||||
auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model),filePath);
|
||||
{
|
||||
auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model), filePath);
|
||||
presetReader->ExtractMediaFiles();
|
||||
|
||||
std::stringstream ss(presetReader->GetPresetJson());
|
||||
json_reader reader(ss);
|
||||
reader.read(&bankFile);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// legacy json format, no zip, no media files.
|
||||
json_reader reader(req.get_body_input_stream());
|
||||
reader.read(&bankFile);
|
||||
@@ -522,70 +540,88 @@ public:
|
||||
res.setContentLength(result.length());
|
||||
|
||||
res.setBody(result);
|
||||
} else if (segment == "uploadUserFile")
|
||||
}
|
||||
else if (segment == "uploadUserFile")
|
||||
{
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
std::string instanceId = 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");
|
||||
|
||||
|
||||
if (patchProperty.length() == 0 && directory.length() == 0)
|
||||
UserUploadResponse result;
|
||||
try
|
||||
{
|
||||
throw PiPedalException("Malformed request.");
|
||||
|
||||
}
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
std::string instanceId = 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");
|
||||
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
if (patchProperty.length() == 0 && directory.length() == 0)
|
||||
{
|
||||
throw PiPedalException("Malformed request.");
|
||||
}
|
||||
|
||||
std::string outputFileName = std::filesystem::path(directory) / filename;
|
||||
res.set(HttpField::content_type, "application/json");
|
||||
res.set(HttpField::cache_control, "no-cache");
|
||||
|
||||
if (filename.ends_with(".zip"))
|
||||
{
|
||||
ExtensionChecker extensionChecker { request_uri.query("ext") };
|
||||
namespace fs = std::filesystem;
|
||||
fs::path outputFileName = std::filesystem::path(directory) / filename;
|
||||
|
||||
try {
|
||||
auto zipFile = ZipFileReader::Create(req.get_body_temporary_file());
|
||||
std::vector<std::string> files = zipFile->GetFiles();
|
||||
bool hasSingleRootDirectory = HasSingleRootDirectory(*zipFile);
|
||||
if (!hasSingleRootDirectory) {
|
||||
directory = (fs::path(directory) / fs::path(filename).filename().replace_extension("")).string();
|
||||
}
|
||||
for (const auto&inputFile : files)
|
||||
if (filename.ends_with(".zip"))
|
||||
{
|
||||
ExtensionChecker extensionChecker{request_uri.query("ext")};
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
try
|
||||
{
|
||||
if (!inputFile.ends_with("/")) // don't process directory entries.
|
||||
auto zipFile = ZipFileReader::Create(req.get_body_temporary_file());
|
||||
std::vector<std::string> files = zipFile->GetFiles();
|
||||
bool hasSingleRootDirectory = HasSingleRootDirectory(*zipFile);
|
||||
if (!hasSingleRootDirectory)
|
||||
{
|
||||
fs::path inputPath { inputFile};
|
||||
std::string extension = inputPath.extension();
|
||||
if (extensionChecker.IsValidExtension(extension))
|
||||
directory = (fs::path(directory) / fs::path(filename).filename().replace_extension("")).string();
|
||||
}
|
||||
for (const auto &inputFile : files)
|
||||
{
|
||||
if (!inputFile.ends_with("/")) // don't process directory entries.
|
||||
{
|
||||
auto si = zipFile->GetFileInputStream(inputFile);
|
||||
std::string path = this->model->UploadUserFile(directory,patchProperty,inputFile, si,zipFile->GetFileSize(inputFile));
|
||||
fs::path inputPath{inputFile};
|
||||
std::string extension = inputPath.extension();
|
||||
if (extensionChecker.IsValidExtension(extension))
|
||||
{
|
||||
auto si = zipFile->GetFileInputStream(inputFile);
|
||||
std::string path = this->model->UploadUserFile(directory, patchProperty, inputFile, si, zipFile->GetFileSize(inputFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
// set outputPath to the file or folder we would like focus to go to.
|
||||
// almost always a single folder in the root.
|
||||
std::string returnPath = GetFirstFolderOrFile(files);
|
||||
outputFileName = fs::path(directory) / returnPath;
|
||||
}
|
||||
// set outputPath to the file or folder we would like focus to go to.
|
||||
// almost always a single folder in the root.
|
||||
std::string returnPath = GetFirstFolderOrFile(files);
|
||||
outputFileName = this->model->GetPluginUploadDirectory() / directory / returnPath;
|
||||
} catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error(SS("Unzip failed. " << e.what()));
|
||||
throw;
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error(SS("Unzip failed. " << e.what()));
|
||||
throw;
|
||||
}
|
||||
FileSystemSync();
|
||||
}
|
||||
else
|
||||
{
|
||||
outputFileName = this->model->UploadUserFile(directory, patchProperty, filename, req.get_body_input_stream(), req.content_length());
|
||||
}
|
||||
FileSystemSync();
|
||||
|
||||
} else {
|
||||
outputFileName = this->model->UploadUserFile(directory,patchProperty,filename,req.get_body_input_stream(), req.content_length());
|
||||
if (outputFileName.is_relative())
|
||||
{
|
||||
outputFileName = this->model->GetPluginUploadDirectory() / outputFileName;
|
||||
}
|
||||
result.path_ = outputFileName.string();
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
result.errorMessage_ = e.what();
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
json_writer writer(ss);
|
||||
writer.write(outputFileName);
|
||||
writer.write(result);
|
||||
std::string response = ss.str();
|
||||
|
||||
res.setContentLength(response.length());
|
||||
@@ -598,7 +634,7 @@ public:
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
Lv2Log::error(SS("Error uploading file: " << e.what()) );
|
||||
Lv2Log::error(SS("Error uploading file: " << e.what()));
|
||||
if (strcmp(e.what(), "Not found") == 0)
|
||||
{
|
||||
ec = boost::system::errc::make_error_code(boost::system::errc::no_such_file_or_directory);
|
||||
@@ -611,7 +647,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static std::string StripPortNumber(const std::string &fromAddress)
|
||||
{
|
||||
std::string address = fromAddress;
|
||||
@@ -663,10 +698,10 @@ public:
|
||||
std::stringstream s;
|
||||
|
||||
s << "{ \"socket_server_port\": " << portNumber
|
||||
<< ", \"socket_server_address\": \"" << webSocketAddress
|
||||
<< ", \"socket_server_address\": \"" << webSocketAddress
|
||||
<< "\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize
|
||||
<< ", \"enable_auto_update\": " << (ENABLE_AUTO_UPDATE ? " true": "false")
|
||||
<< " }";
|
||||
<< ", \"enable_auto_update\": " << (ENABLE_AUTO_UPDATE ? " true" : "false")
|
||||
<< " }";
|
||||
|
||||
return s.str();
|
||||
}
|
||||
@@ -720,16 +755,14 @@ public:
|
||||
};
|
||||
|
||||
void pipedal::ConfigureWebServer(
|
||||
WebServer&server,
|
||||
PiPedalModel&model,
|
||||
WebServer &server,
|
||||
PiPedalModel &model,
|
||||
int port,
|
||||
size_t maxUploadSize)
|
||||
{
|
||||
std::shared_ptr<RequestHandler> interceptConfig{new InterceptConfig(port, maxUploadSize)};
|
||||
server.AddRequestHandler(interceptConfig);
|
||||
|
||||
std::shared_ptr<DownloadIntercept> downloadIntercept = std::make_shared<DownloadIntercept>(&model);
|
||||
server.AddRequestHandler(downloadIntercept);
|
||||
|
||||
std::shared_ptr<RequestHandler> interceptConfig{new InterceptConfig(port, maxUploadSize)};
|
||||
server.AddRequestHandler(interceptConfig);
|
||||
|
||||
std::shared_ptr<DownloadIntercept> downloadIntercept = std::make_shared<DownloadIntercept>(&model);
|
||||
server.AddRequestHandler(downloadIntercept);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user