Shared upload directories
This commit is contained in:
@@ -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 {
|
class NoCopy {
|
||||||
public:
|
public:
|
||||||
NoCopy() { }
|
NoCopy() { }
|
||||||
|
|||||||
+148
-101
@@ -17,6 +17,7 @@
|
|||||||
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
// 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.
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Theme, createStyles } from '@mui/material/styles';
|
import { Theme, createStyles } from '@mui/material/styles';
|
||||||
import CreateNewFolderIcon from '@mui/icons-material/CreateNewFolder';
|
import CreateNewFolderIcon from '@mui/icons-material/CreateNewFolder';
|
||||||
@@ -25,7 +26,7 @@ import Divider from '@mui/material/Divider';
|
|||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import Menu from '@mui/material/Menu';
|
import Menu from '@mui/material/Menu';
|
||||||
import MoreIcon from '@mui/icons-material/MoreVert';
|
import MoreIcon from '@mui/icons-material/MoreVert';
|
||||||
import { PiPedalModel, PiPedalModelFactory, FileEntry } from './PiPedalModel';
|
import { PiPedalModel, PiPedalModelFactory, FileEntry, BreadcrumbEntry, FileRequestResult } from './PiPedalModel';
|
||||||
import { isDarkMode } from './DarkMode';
|
import { isDarkMode } from './DarkMode';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import FileUploadIcon from '@mui/icons-material/FileUpload';
|
import FileUploadIcon from '@mui/icons-material/FileUpload';
|
||||||
@@ -95,15 +96,16 @@ export interface FilePropertyDialogProps extends WithStyles<typeof styles> {
|
|||||||
onOk: (fileProperty: UiFileProperty, selectedItem: string) => void,
|
onOk: (fileProperty: UiFileProperty, selectedItem: string) => void,
|
||||||
onCancel: () => void
|
onCancel: () => void
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FilePropertyDialogState {
|
export interface FilePropertyDialogState {
|
||||||
fullScreen: boolean;
|
fullScreen: boolean;
|
||||||
selectedFile: string;
|
selectedFile: string;
|
||||||
selectedFileIsDirectory: boolean;
|
selectedFileIsDirectory: boolean;
|
||||||
navDirectory: string;
|
selectedFileProtected: boolean;
|
||||||
hasSelection: boolean;
|
hasSelection: boolean;
|
||||||
canDelete: boolean;
|
canDelete: boolean;
|
||||||
fileEntries: FileEntry[];
|
fileResult: FileRequestResult;
|
||||||
|
navDirectory: string;
|
||||||
|
isProtectedDirectory: boolean;
|
||||||
columns: number;
|
columns: number;
|
||||||
columnWidth: number;
|
columnWidth: number;
|
||||||
openUploadFileDialog: boolean;
|
openUploadFileDialog: boolean;
|
||||||
@@ -114,19 +116,22 @@ export interface FilePropertyDialogState {
|
|||||||
moveDialogOpen: boolean;
|
moveDialogOpen: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function pathExtension(path: string)
|
function pathExtension(path: string) {
|
||||||
{
|
|
||||||
let dotPos = path.lastIndexOf('.');
|
let dotPos = path.lastIndexOf('.');
|
||||||
if (dotPos === -1) return "";
|
if (dotPos === -1) return "";
|
||||||
|
|
||||||
let slashPos = path.lastIndexOf('/');
|
let slashPos = path.lastIndexOf('/');
|
||||||
if (slashPos !== -1)
|
if (slashPos !== -1) {
|
||||||
{
|
|
||||||
if (dotPos <= slashPos + 1) return "";
|
if (dotPos <= slashPos + 1) return "";
|
||||||
}
|
}
|
||||||
return path.substring(dotPos); // include the '.'.
|
return path.substring(dotPos); // include the '.'.
|
||||||
|
|
||||||
}
|
}
|
||||||
|
function pathParentDirectory(path: string) {
|
||||||
|
let npos = path.lastIndexOf('/');
|
||||||
|
if (npos === -1) return "";
|
||||||
|
return path.substring(0, npos);
|
||||||
|
}
|
||||||
function pathConcat(left: string, right: string) {
|
function pathConcat(left: string, right: string) {
|
||||||
if (left === "") return right;
|
if (left === "") return right;
|
||||||
if (right === "") return left;
|
if (right === "") return left;
|
||||||
@@ -170,19 +175,10 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
class FilePropertyDialog extends ResizeResponsiveComponent<FilePropertyDialogProps, FilePropertyDialogState> {
|
class FilePropertyDialog extends ResizeResponsiveComponent<FilePropertyDialogProps, FilePropertyDialogState> {
|
||||||
|
|
||||||
getNavDirectoryFromFile(selectedFile: string, fileProperty: UiFileProperty): string {
|
getNavDirectoryFromFile(selectedFile: string, fileProperty: UiFileProperty): string {
|
||||||
|
if (selectedFile === "") {
|
||||||
// would be easier if we had the data root (/var/pipedal/audio_downloads, but could be different when we're debugging. :-/
|
return "";
|
||||||
let nPos = selectedFile.indexOf("/" + fileProperty.directory + "/");
|
|
||||||
if (nPos === -1) return "";
|
|
||||||
let relativePath = selectedFile.substring(nPos + fileProperty.directory.length + 2);
|
|
||||||
let segments = relativePath.split('/');
|
|
||||||
|
|
||||||
let result = "";
|
|
||||||
for (let i = 0; i < segments.length - 1; ++i) {
|
|
||||||
if (result !== "") result += '/';
|
|
||||||
result += segments[i];
|
|
||||||
}
|
}
|
||||||
return result;
|
return pathParentDirectory(selectedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: FilePropertyDialogProps) {
|
constructor(props: FilePropertyDialogProps) {
|
||||||
@@ -194,13 +190,15 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
this.state = {
|
this.state = {
|
||||||
fullScreen: this.getFullScreen(),
|
fullScreen: this.getFullScreen(),
|
||||||
selectedFile: props.selectedFile,
|
selectedFile: props.selectedFile,
|
||||||
|
selectedFileProtected: true,
|
||||||
selectedFileIsDirectory: false,
|
selectedFileIsDirectory: false,
|
||||||
navDirectory: this.getNavDirectoryFromFile(props.selectedFile, props.fileProperty),
|
navDirectory: this.getNavDirectoryFromFile(props.selectedFile, props.fileProperty),
|
||||||
hasSelection: false,
|
hasSelection: false,
|
||||||
canDelete: false,
|
canDelete: false,
|
||||||
columns: 0,
|
columns: 0,
|
||||||
columnWidth: 1,
|
columnWidth: 1,
|
||||||
fileEntries: [],
|
fileResult: new FileRequestResult(),
|
||||||
|
isProtectedDirectory: true,
|
||||||
openUploadFileDialog: false,
|
openUploadFileDialog: false,
|
||||||
openConfirmDeleteDialog: false,
|
openConfirmDeleteDialog: false,
|
||||||
menuAnchorEl: null,
|
menuAnchorEl: null,
|
||||||
@@ -221,7 +219,7 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
this.maybeScrollIntoView();
|
this.maybeScrollIntoView();
|
||||||
}
|
}
|
||||||
private maybeScrollIntoView() {
|
private maybeScrollIntoView() {
|
||||||
if (this.scrollRef !== null && this.state.fileEntries.length !== 0 && this.mounted && this.requestScroll) {
|
if (this.scrollRef !== null && this.state.fileResult.files.length !== 0 && this.mounted && this.requestScroll) {
|
||||||
this.requestScroll = false;
|
this.requestScroll = false;
|
||||||
let options: ScrollIntoViewOptions = { block: "nearest" };
|
let options: ScrollIntoViewOptions = { block: "nearest" };
|
||||||
options.block = "nearest";
|
options.block = "nearest";
|
||||||
@@ -241,7 +239,7 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.model.requestFileList2(navPath, this.props.fileProperty)
|
this.model.requestFileList2(navPath, this.props.fileProperty)
|
||||||
.then((files) => {
|
.then((filesResult) => {
|
||||||
if (this.mounted) {
|
if (this.mounted) {
|
||||||
// let insertionPoint = files.length;
|
// let insertionPoint = files.length;
|
||||||
// for (let i = 0; i < files.length; ++i) {
|
// for (let i = 0; i < files.length; ++i) {
|
||||||
@@ -250,11 +248,25 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
// break;
|
// break;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
files.splice(0, 0, { filename: "", isDirectory: false });
|
filesResult.files.splice(0, 0, { pathname: "", displayName: "<none>", isDirectory: false, isProtected: true });
|
||||||
this.setState({ fileEntries: files, hasSelection: this.isFileInList(files, this.state.selectedFile), navDirectory: navPath });
|
|
||||||
|
let fileEntry = this.getFileEntry(filesResult.files, this.state.selectedFile);
|
||||||
|
this.setState({
|
||||||
|
isProtectedDirectory: filesResult.isProtected,
|
||||||
|
fileResult: filesResult,
|
||||||
|
hasSelection: !!fileEntry,
|
||||||
|
selectedFileProtected: fileEntry ? fileEntry.isProtected: true,
|
||||||
|
navDirectory: navPath
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
if (!this.mounted) return;
|
||||||
|
if (navPath !== "") // deleted sample directory maybe?
|
||||||
|
{
|
||||||
|
this.navigate("");
|
||||||
|
} else {
|
||||||
this.model.showAlert(error.toString())
|
this.model.showAlert(error.toString())
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,6 +313,8 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
let navDirectory = this.getNavDirectoryFromFile(this.props.selectedFile, this.props.fileProperty);
|
let navDirectory = this.getNavDirectoryFromFile(this.props.selectedFile, this.props.fileProperty);
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedFile: this.props.selectedFile,
|
selectedFile: this.props.selectedFile,
|
||||||
|
selectedFileIsDirectory: false,
|
||||||
|
selectedFileProtected: true,
|
||||||
newFolderDialogOpen: false,
|
newFolderDialogOpen: false,
|
||||||
renameDialogOpen: false,
|
renameDialogOpen: false,
|
||||||
moveDialogOpen: false
|
moveDialogOpen: false
|
||||||
@@ -313,8 +327,8 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isDirectory(path: string): boolean {
|
private isDirectory(path: string): boolean {
|
||||||
for (var fileEntry of this.state.fileEntries) {
|
for (var fileEntry of this.state.fileResult.files) {
|
||||||
if (fileEntry.filename === path) {
|
if (fileEntry.pathname === path) {
|
||||||
return fileEntry.isDirectory;
|
return fileEntry.isDirectory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -325,17 +339,32 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
let hasSelection = false;
|
let hasSelection = false;
|
||||||
if (file === "") return true;
|
if (file === "") return true;
|
||||||
for (var listFile of files) {
|
for (var listFile of files) {
|
||||||
if (listFile.filename === file) {
|
if (listFile.pathname === file) {
|
||||||
hasSelection = true;
|
hasSelection = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hasSelection;
|
return hasSelection;
|
||||||
}
|
}
|
||||||
|
private getFileEntry(files: FileEntry[], file: string): FileEntry | undefined {
|
||||||
|
|
||||||
onSelectValue(selectedFile: string, isDirectory: boolean) {
|
if (file === "") return undefined;
|
||||||
|
for (var listFile of files) {
|
||||||
|
if (listFile.pathname === file) {
|
||||||
|
return listFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectValue(fileEntry: FileEntry) {
|
||||||
this.requestScroll = true;
|
this.requestScroll = true;
|
||||||
this.setState({ selectedFile: selectedFile, selectedFileIsDirectory: isDirectory, hasSelection: this.isFileInList(this.state.fileEntries, selectedFile) })
|
this.setState({
|
||||||
|
selectedFile: fileEntry.pathname,
|
||||||
|
selectedFileIsDirectory: fileEntry.isDirectory,
|
||||||
|
selectedFileProtected: fileEntry.isProtected,
|
||||||
|
hasSelection: this.isFileInList(this.state.fileResult.files, fileEntry.pathname)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
onDoubleClickValue(selectedFile: string) {
|
onDoubleClickValue(selectedFile: string) {
|
||||||
this.openSelectedFile();
|
this.openSelectedFile();
|
||||||
@@ -349,25 +378,31 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
this.setState({ menuAnchorEl: null });
|
this.setState({ menuAnchorEl: null });
|
||||||
}
|
}
|
||||||
handleDelete() {
|
handleDelete() {
|
||||||
|
if (this.state.selectedFileProtected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.setState({ openConfirmDeleteDialog: true });
|
this.setState({ openConfirmDeleteDialog: true });
|
||||||
}
|
}
|
||||||
handleConfirmDelete() {
|
handleConfirmDelete() {
|
||||||
|
if (this.state.selectedFileProtected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.setState({ openConfirmDeleteDialog: false });
|
this.setState({ openConfirmDeleteDialog: false });
|
||||||
if (this.state.hasSelection) {
|
if (this.state.hasSelection) {
|
||||||
let selectedFile = this.state.selectedFile;
|
let selectedFile = this.state.selectedFile;
|
||||||
let position = -1;
|
let position = -1;
|
||||||
for (let i = 0; i < this.state.fileEntries.length; ++i) {
|
for (let i = 0; i < this.state.fileResult.files.length; ++i) {
|
||||||
let file = this.state.fileEntries[i];
|
let file = this.state.fileResult.files[i];
|
||||||
if (file.filename === selectedFile) {
|
if (file.pathname === selectedFile) {
|
||||||
position = i;
|
position = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let newSelection = "";
|
let newSelection: FileEntry | undefined = undefined;
|
||||||
if (position >= 0 && position < this.state.fileEntries.length - 1) {
|
if (position >= 0 && position < this.state.fileResult.files.length - 1) {
|
||||||
newSelection = this.state.fileEntries[position + 1].filename;
|
newSelection = this.state.fileResult.files[position + 1];
|
||||||
} else if (position === this.state.fileEntries.length - 1) {
|
} else if (position === this.state.fileResult.files.length - 1) {
|
||||||
if (position !== 0) {
|
if (position !== 0) {
|
||||||
newSelection = this.state.fileEntries[position - 1].filename;
|
newSelection = this.state.fileResult.files[position - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -375,7 +410,23 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
this.model.deleteUserFile(this.state.selectedFile)
|
this.model.deleteUserFile(this.state.selectedFile)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
this.setState({ selectedFile: newSelection, hasSelection: newSelection !== "" });
|
if (newSelection)
|
||||||
|
{
|
||||||
|
this.setState({
|
||||||
|
selectedFile: newSelection.pathname,
|
||||||
|
selectedFileIsDirectory: newSelection.isDirectory,
|
||||||
|
selectedFileProtected: newSelection.isProtected,
|
||||||
|
hasSelection: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
selectedFile: "",
|
||||||
|
selectedFileIsDirectory: false,
|
||||||
|
selectedFileProtected: true,
|
||||||
|
hasSelection: false
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
this.requestFiles(this.state.navDirectory);
|
this.requestFiles(this.state.navDirectory);
|
||||||
}
|
}
|
||||||
).catch(
|
).catch(
|
||||||
@@ -408,11 +459,10 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
let directories = this.state.navDirectory.split("/");
|
|
||||||
let target = "";
|
for (let i = 1; i < this.state.fileResult.breadcrumbs.length - 1; ++i) {
|
||||||
for (let i = 0; i < directories.length - 1; ++i) {
|
let breadcrumb: BreadcrumbEntry = this.state.fileResult.breadcrumbs[i];
|
||||||
target = pathConcat(target, directories[i]);
|
|
||||||
let myTarget = target;
|
|
||||||
breadcrumbs.push((
|
breadcrumbs.push((
|
||||||
<Typography key={"x" + (i)} variant="body2" style={{ marginTop: 6, marginBottom: 6 }} noWrap>/</Typography>
|
<Typography key={"x" + (i)} variant="body2" style={{ marginTop: 6, marginBottom: 6 }} noWrap>/</Typography>
|
||||||
|
|
||||||
@@ -420,29 +470,29 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
);
|
);
|
||||||
breadcrumbs.push((
|
breadcrumbs.push((
|
||||||
<Button variant="text"
|
<Button variant="text"
|
||||||
key={(i).toString()}
|
key={"bc" + i}
|
||||||
color="inherit"
|
color="inherit"
|
||||||
onClick={() => { this.navigate(myTarget) }}
|
onClick={() => { this.navigate(breadcrumb.pathname) }}
|
||||||
style={{ minWidth: 0, textTransform: "none", flexShrink: 0 }}
|
style={{ minWidth: 0, textTransform: "none", flexShrink: 0 }}
|
||||||
>
|
>
|
||||||
<Typography variant="body2" style={{}} noWrap>{directories[i]}</Typography>
|
<Typography variant="body2" style={{}} noWrap>{breadcrumb.displayName}</Typography>
|
||||||
|
|
||||||
|
|
||||||
</Button>
|
</Button>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
{
|
if (this.state.fileResult.breadcrumbs.length > 1) {
|
||||||
let lastdirectory = directories[directories.length - 1];
|
let lastdirectory = this.state.fileResult.breadcrumbs[this.state.fileResult.breadcrumbs.length - 1];
|
||||||
breadcrumbs.push((
|
breadcrumbs.push((
|
||||||
<Typography key={"x"+(directories.length)} variant="body2" style={{userSelect: "none",marginTop: 6, marginBottom: 6}} noWrap>/</Typography>
|
<Typography key={"xLast"} variant="body2" style={{ userSelect: "none", marginTop: 6, marginBottom: 6 }} noWrap>/</Typography>
|
||||||
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
breadcrumbs.push((
|
breadcrumbs.push((
|
||||||
<div style={{flexShrink: 0,paddingLeft: 8, paddingRight: 8, paddingTop: 6, paddingBottom: 6, overflowX:"clip"}}>
|
<div key={"bc-last"} style={{ flexShrink: 0, paddingLeft: 8, paddingRight: 8, paddingTop: 6, paddingBottom: 6, overflowX: "clip" }}>
|
||||||
<Typography key={directories.length.toString()} noWrap
|
<Typography noWrap
|
||||||
style={{}} variant="body2"
|
style={{}} variant="body2"
|
||||||
color="text.secondary"> {lastdirectory}</Typography>
|
color="text.secondary"> {lastdirectory.displayName}</Typography>
|
||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -463,12 +513,10 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
try {
|
try {
|
||||||
let storage = window.localStorage;
|
let storage = window.localStorage;
|
||||||
let result = storage.getItem("fpDefaultPath");
|
let result = storage.getItem("fpDefaultPath");
|
||||||
if (result)
|
if (result) {
|
||||||
{
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
} catch (e)
|
} catch (e) {
|
||||||
{
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,30 +535,24 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
}
|
}
|
||||||
getFileExtensionList(uiFileProperty: UiFileProperty): string {
|
getFileExtensionList(uiFileProperty: UiFileProperty): string {
|
||||||
let result = "";
|
let result = "";
|
||||||
for (var fileType of uiFileProperty.fileTypes)
|
for (var fileType of uiFileProperty.fileTypes) {
|
||||||
{
|
if (fileType.fileExtension !== "" && fileType.fileExtension !== ".zip") {
|
||||||
if (fileType.fileExtension !== "" && fileType.fileExtension !== ".zip")
|
|
||||||
{
|
|
||||||
if (result !== "") result = result + ",";
|
if (result !== "") result = result + ",";
|
||||||
result += fileType.fileExtension;
|
result += fileType.fileExtension;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
private getIcon(fileEntry: FileEntry)
|
private getIcon(fileEntry: FileEntry) {
|
||||||
{
|
if (fileEntry.pathname === "") {
|
||||||
if (fileEntry.filename === "")
|
|
||||||
{
|
|
||||||
return (<InsertDriveFileOutlinedIcon style={{ flex: "0 0 auto", opacity: 0.5, marginRight: 8, marginLeft: 8 }} />);
|
return (<InsertDriveFileOutlinedIcon style={{ flex: "0 0 auto", opacity: 0.5, marginRight: 8, marginLeft: 8 }} />);
|
||||||
}
|
}
|
||||||
if (fileEntry.isDirectory)
|
if (fileEntry.isDirectory) {
|
||||||
{
|
|
||||||
return (
|
return (
|
||||||
<FolderIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 8 }} />
|
<FolderIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 8 }} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (isAudioFile(fileEntry.filename))
|
if (isAudioFile(fileEntry.pathname)) {
|
||||||
{
|
|
||||||
return (<AudioFileIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 8 }} />);
|
return (<AudioFileIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 8 }} />);
|
||||||
}
|
}
|
||||||
return (<InsertDriveFileIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 8 }} />);
|
return (<InsertDriveFileIcon style={{ flex: "0 0 auto", opacity: 0.7, marginRight: 8, marginLeft: 8 }} />);
|
||||||
@@ -520,10 +562,17 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
let classes = this.props.classes;
|
let classes = this.props.classes;
|
||||||
let columnWidth = this.state.columnWidth;
|
let columnWidth = this.state.columnWidth;
|
||||||
let okButtonText = "Select";
|
let okButtonText = "Select";
|
||||||
if (this.state.hasSelection && this.state.selectedFileIsDirectory)
|
if (this.state.hasSelection && this.state.selectedFileIsDirectory) {
|
||||||
{
|
|
||||||
okButtonText = "Open";
|
okButtonText = "Open";
|
||||||
}
|
}
|
||||||
|
let protectedDirectory = this.state.fileResult.isProtected;
|
||||||
|
let protectedItem = true;
|
||||||
|
if (this.state.hasSelection)
|
||||||
|
{
|
||||||
|
protectedItem = this.state.selectedFileProtected;
|
||||||
|
}
|
||||||
|
let canMoveOrRename = this.hasSelectedFileOrFolder() && !protectedItem;
|
||||||
|
|
||||||
return this.props.open &&
|
return this.props.open &&
|
||||||
(
|
(
|
||||||
<DialogEx
|
<DialogEx
|
||||||
@@ -540,10 +589,12 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
this.state.fullScreen ?
|
this.state.fullScreen ?
|
||||||
{}
|
{}
|
||||||
:
|
:
|
||||||
{ style: {
|
{
|
||||||
|
style: {
|
||||||
minHeight: "90%",
|
minHeight: "90%",
|
||||||
maxHeight: "90%",
|
maxHeight: "90%",
|
||||||
} }}
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<DialogTitle style={{ paddingBottom: 0 }} >
|
<DialogTitle style={{ paddingBottom: 0 }} >
|
||||||
<Toolbar style={{ padding: 0 }}>
|
<Toolbar style={{ padding: 0 }}>
|
||||||
@@ -566,6 +617,7 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
color="inherit"
|
color="inherit"
|
||||||
style={{ opacity: 0.6, marginRight: 8 }}
|
style={{ opacity: 0.6, marginRight: 8 }}
|
||||||
onClick={(ev) => { this.onNewFolder(); }}
|
onClick={(ev) => { this.onNewFolder(); }}
|
||||||
|
disabled={protectedDirectory}
|
||||||
>
|
>
|
||||||
<CreateNewFolderIcon />
|
<CreateNewFolderIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -577,6 +629,8 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
color="inherit"
|
color="inherit"
|
||||||
style={{ opacity: 0.6 }}
|
style={{ opacity: 0.6 }}
|
||||||
onClick={(ev) => { this.handleMenuOpen(ev); }}
|
onClick={(ev) => { this.handleMenuOpen(ev); }}
|
||||||
|
disabled={protectedDirectory}
|
||||||
|
|
||||||
>
|
>
|
||||||
<MoreIcon />
|
<MoreIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -596,9 +650,9 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
onClose={() => { this.handleMenuClose(); }}
|
onClose={() => { this.handleMenuClose(); }}
|
||||||
>
|
>
|
||||||
<MenuItem onClick={() => { this.handleMenuClose(); this.onNewFolder(); }}>New folder</MenuItem>
|
<MenuItem onClick={() => { this.handleMenuClose(); this.onNewFolder(); }}>New folder</MenuItem>
|
||||||
{this.hasSelectedFileOrFolder() && (<Divider />)}
|
{canMoveOrRename && (<Divider />)}
|
||||||
{this.hasSelectedFileOrFolder() && (<MenuItem onClick={() => { this.handleMenuClose();this.onMove(); }}>Move</MenuItem>)}
|
{canMoveOrRename && (<MenuItem onClick={() => { this.handleMenuClose(); this.onMove(); }}>Move</MenuItem>)}
|
||||||
{this.hasSelectedFileOrFolder() && (<MenuItem onClick={() => { this.handleMenuClose();this.onRename(); }}>Rename</MenuItem>)}
|
{canMoveOrRename && this.hasSelectedFileOrFolder() && !protectedItem && (<MenuItem onClick={() => { this.handleMenuClose(); this.onRename(); }}>Rename</MenuItem>)}
|
||||||
</Menu>
|
</Menu>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
<div style={{ flex: "0 0 auto", marginLeft: 0, marginRight: 0, overflowX: "clip" }}>
|
<div style={{ flex: "0 0 auto", marginLeft: 0, marginRight: 0, overflowX: "clip" }}>
|
||||||
@@ -615,20 +669,13 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
}}>
|
}}>
|
||||||
{
|
{
|
||||||
(this.state.columns !== 0) && // don't render until we have number of columns derived from layout.
|
(this.state.columns !== 0) && // don't render until we have number of columns derived from layout.
|
||||||
this.state.fileEntries.map(
|
this.state.fileResult.files.map(
|
||||||
(value: FileEntry, index: number) => {
|
(value: FileEntry, index: number) => {
|
||||||
let displayValue = value.filename;
|
let displayValue = value.displayName;
|
||||||
if (displayValue === "") {
|
if (displayValue === "") {
|
||||||
displayValue = "<none>";
|
displayValue = "<none>";
|
||||||
} else {
|
|
||||||
if (value.isDirectory)
|
|
||||||
{
|
|
||||||
displayValue = pathFileName(displayValue);
|
|
||||||
} else {
|
|
||||||
displayValue = pathFileName(displayValue);
|
|
||||||
}
|
}
|
||||||
}
|
let selected = value.pathname === this.state.selectedFile;
|
||||||
let selected = value.filename === this.state.selectedFile;
|
|
||||||
let selectBg = selected ? "rgba(0,0,0,0.15)" : "rgba(0,0,0,0.0)";
|
let selectBg = selected ? "rgba(0,0,0,0.15)" : "rgba(0,0,0,0.0)";
|
||||||
if (isDarkMode()) {
|
if (isDarkMode()) {
|
||||||
selectBg = selected ? "rgba(255,255,255,0.10)" : "rgba(0,0,0,0.0)";
|
selectBg = selected ? "rgba(255,255,255,0.10)" : "rgba(0,0,0,0.0)";
|
||||||
@@ -639,10 +686,11 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonBase key={value.filename}
|
<ButtonBase key={value.pathname}
|
||||||
|
|
||||||
style={{ width: columnWidth, flex: "0 0 auto", height: 48, position: "relative" }}
|
style={{ width: columnWidth, flex: "0 0 auto", height: 48, position: "relative" }}
|
||||||
onClick={() => this.onSelectValue(value.filename,value.isDirectory)} onDoubleClick={() => { this.onDoubleClickValue(value.filename); }}
|
onClick={() => this.onSelectValue(value)}
|
||||||
|
onDoubleClick={() => { this.onDoubleClickValue(value.pathname); }}
|
||||||
>
|
>
|
||||||
<div ref={scrollRef} style={{ position: "absolute", background: selectBg, width: "100%", height: "100%", borderRadius: 4 }} />
|
<div ref={scrollRef} style={{ position: "absolute", background: selectBg, width: "100%", height: "100%", borderRadius: 4 }} />
|
||||||
<div style={{ display: "flex", flexFlow: "row nowrap", textOverflow: "ellipsis", justifyContent: "start", alignItems: "center", width: "100%", height: "100%" }}>
|
<div style={{ display: "flex", flexFlow: "row nowrap", textOverflow: "ellipsis", justifyContent: "start", alignItems: "center", width: "100%", height: "100%" }}>
|
||||||
@@ -660,13 +708,13 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
<DialogActions style={{ justifyContent: "stretch" }}>
|
<DialogActions style={{ justifyContent: "stretch" }}>
|
||||||
<div style={{ display: "flex", width: "100%", alignItems: "center", flexFlow: "row nowrap" }}>
|
<div style={{ display: "flex", width: "100%", alignItems: "center", flexFlow: "row nowrap" }}>
|
||||||
<IconButton style={{ visibility: (this.state.hasSelection ? "visible" : "hidden") }} aria-label="delete" component="label" color="primary"
|
<IconButton style={{ visibility: (this.state.hasSelection ? "visible" : "hidden") }} aria-label="delete" component="label" color="primary"
|
||||||
disabled={!this.state.hasSelection || this.state.selectedFile === ""}
|
disabled={!this.state.hasSelection || this.state.selectedFile === "" || protectedItem}
|
||||||
onClick={() => this.handleDelete()} >
|
onClick={() => this.handleDelete()} >
|
||||||
<OldDeleteIcon fontSize='small' />
|
<OldDeleteIcon fontSize='small' />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<Button style={{ flex: "0 0 auto" }} aria-label="upload" variant="text" startIcon={<FileUploadIcon />}
|
<Button style={{ flex: "0 0 auto" }} aria-label="upload" variant="text" startIcon={<FileUploadIcon />}
|
||||||
onClick={() => { this.setState({ openUploadFileDialog: true }) }}
|
onClick={() => { this.setState({ openUploadFileDialog: true }) }} disabled={protectedDirectory}
|
||||||
>
|
>
|
||||||
<div>Upload</div>
|
<div>Upload</div>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -763,10 +811,8 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
}
|
}
|
||||||
openSelectedFile(): void {
|
openSelectedFile(): void {
|
||||||
if (this.isDirectory(this.state.selectedFile)) {
|
if (this.isDirectory(this.state.selectedFile)) {
|
||||||
let directoryName = pathFileName(this.state.selectedFile);
|
this.requestFiles(this.state.selectedFile);
|
||||||
let navDirectory = pathConcat(this.state.navDirectory, directoryName);
|
this.setState({ navDirectory: this.state.selectedFile });
|
||||||
this.requestFiles(navDirectory);
|
|
||||||
this.setState({ navDirectory: navDirectory });
|
|
||||||
} else {
|
} else {
|
||||||
this.props.onOk(this.props.fileProperty, this.state.selectedFile);
|
this.props.onOk(this.props.fileProperty, this.state.selectedFile);
|
||||||
}
|
}
|
||||||
@@ -775,8 +821,7 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
private renameDefaultName(): string {
|
private renameDefaultName(): string {
|
||||||
let name = this.state.selectedFile;
|
let name = this.state.selectedFile;
|
||||||
if (name === "") return "";
|
if (name === "") return "";
|
||||||
if (this.isDirectory(name))
|
if (this.isDirectory(name)) {
|
||||||
{
|
|
||||||
return pathFileName(name);
|
return pathFileName(name);
|
||||||
} else {
|
} else {
|
||||||
return pathFileNameOnly(name);
|
return pathFileNameOnly(name);
|
||||||
@@ -785,8 +830,7 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
private onMove(): void {
|
private onMove(): void {
|
||||||
this.setState({ moveDialogOpen: true });
|
this.setState({ moveDialogOpen: true });
|
||||||
}
|
}
|
||||||
private onExecuteMove(newDirectory: string)
|
private onExecuteMove(newDirectory: string) {
|
||||||
{
|
|
||||||
let fileName = pathFileName(this.state.selectedFile);
|
let fileName = pathFileName(this.state.selectedFile);
|
||||||
let oldFilePath = pathConcat(this.state.navDirectory, fileName);
|
let oldFilePath = pathConcat(this.state.navDirectory, fileName);
|
||||||
let newFilePath = pathConcat(newDirectory, fileName);
|
let newFilePath = pathConcat(newDirectory, fileName);
|
||||||
@@ -807,8 +851,7 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
private onExecuteRename(newName: string) {
|
private onExecuteRename(newName: string) {
|
||||||
let newPath: string = "";
|
let newPath: string = "";
|
||||||
let oldPath: string = "";
|
let oldPath: string = "";
|
||||||
if (this.isDirectory(this.state.selectedFile))
|
if (this.isDirectory(this.state.selectedFile)) {
|
||||||
{
|
|
||||||
let oldName = pathFileName(this.state.selectedFile);
|
let oldName = pathFileName(this.state.selectedFile);
|
||||||
if (oldName === newName) return;
|
if (oldName === newName) return;
|
||||||
oldPath = pathConcat(this.state.navDirectory, oldName);
|
oldPath = pathConcat(this.state.navDirectory, oldName);
|
||||||
@@ -839,7 +882,11 @@ export default withStyles(styles, { withTheme: true })(
|
|||||||
private onExecuteNewFolder(newName: string) {
|
private onExecuteNewFolder(newName: string) {
|
||||||
this.model.createNewSampleDirectory(pathConcat(this.state.navDirectory, newName), this.props.fileProperty)
|
this.model.createNewSampleDirectory(pathConcat(this.state.navDirectory, newName), this.props.fileProperty)
|
||||||
.then((newPath) => {
|
.then((newPath) => {
|
||||||
this.setState({selectedFile: newPath});
|
this.setState({
|
||||||
|
selectedFile: newPath,
|
||||||
|
selectedFileIsDirectory:
|
||||||
|
true,selectedFileProtected: false
|
||||||
|
});
|
||||||
this.requestFiles(this.state.navDirectory);
|
this.requestFiles(this.state.navDirectory);
|
||||||
this.requestScroll = true
|
this.requestScroll = true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -246,6 +246,8 @@ export class UiFileProperty {
|
|||||||
this.index = input.index;
|
this.index = input.index;
|
||||||
this.portGroup = input.portGroup;
|
this.portGroup = input.portGroup;
|
||||||
this.resourceDirectory = input.resourceDirectory ?? "";
|
this.resourceDirectory = input.resourceDirectory ?? "";
|
||||||
|
this.modDirectories = input.modDirectories;
|
||||||
|
this.useLegacyModDirectory = input.useLegacyModDirectory;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
static deserialize_array(input: any): UiFileProperty[] {
|
static deserialize_array(input: any): UiFileProperty[] {
|
||||||
@@ -297,6 +299,8 @@ export class UiFileProperty {
|
|||||||
index: number = -1;
|
index: number = -1;
|
||||||
portGroup: string = "";
|
portGroup: string = "";
|
||||||
resourceDirectory: string = "";
|
resourceDirectory: string = "";
|
||||||
|
modDirectories: string[] = [];
|
||||||
|
useLegacyModDirectory: boolean = false;
|
||||||
|
|
||||||
};
|
};
|
||||||
export class Lv2Plugin implements Deserializable<Lv2Plugin> {
|
export class Lv2Plugin implements Deserializable<Lv2Plugin> {
|
||||||
|
|||||||
@@ -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 type ControlValueChangedHandler = (key: string, value: number) => void;
|
||||||
|
|
||||||
export interface FileEntry {
|
export interface FileEntry {
|
||||||
filename: string;
|
pathname: string;
|
||||||
|
displayName: string;
|
||||||
isDirectory: boolean;
|
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;
|
export type PluginPresetsChangedHandler = (pluginUri: string) => void;
|
||||||
@@ -1989,9 +2001,9 @@ export class PiPedalModel //implements PiPedalModel
|
|||||||
return nullCast(this.webSocket)
|
return nullCast(this.webSocket)
|
||||||
.request<string[]>('requestFileList', piPedalFileProperty);
|
.request<string[]>('requestFileList', piPedalFileProperty);
|
||||||
}
|
}
|
||||||
requestFileList2(relativeDirectoryPath: string, piPedalFileProperty: UiFileProperty): Promise<FileEntry[]> {
|
requestFileList2(relativeDirectoryPath: string, piPedalFileProperty: UiFileProperty): Promise<FileRequestResult> {
|
||||||
return nullCast(this.webSocket)
|
return nullCast(this.webSocket)
|
||||||
.request<FileEntry[]>('requestFileList2',
|
.request<FileRequestResult>('requestFileList2',
|
||||||
{ relativePath: relativeDirectoryPath, fileProperty: piPedalFileProperty }
|
{ relativePath: relativeDirectoryPath, fileProperty: piPedalFileProperty }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
set (PIPEDAL_SOURCES
|
set (PIPEDAL_SOURCES
|
||||||
|
ModFileTypes.cpp ModFileTypes.hpp
|
||||||
PatchPropertyWriter.hpp
|
PatchPropertyWriter.hpp
|
||||||
PresetBundle.cpp PresetBundle.hpp
|
PresetBundle.cpp PresetBundle.hpp
|
||||||
RealtimeMidiEventType.hpp
|
RealtimeMidiEventType.hpp
|
||||||
@@ -637,6 +638,7 @@ add_executable(pipedalconfig
|
|||||||
CommandLineParser.hpp
|
CommandLineParser.hpp
|
||||||
PiPedalException.hpp
|
PiPedalException.hpp
|
||||||
ConfigMain.cpp
|
ConfigMain.cpp
|
||||||
|
ModFileTypes.cpp ModFileTypes.hpp
|
||||||
PiPedalConfiguration.hpp PiPedalConfiguration.cpp
|
PiPedalConfiguration.hpp PiPedalConfiguration.cpp
|
||||||
JackServerSettings.hpp JackServerSettings.cpp
|
JackServerSettings.hpp JackServerSettings.cpp
|
||||||
SystemConfigFile.hpp SystemConfigFile.cpp
|
SystemConfigFile.hpp SystemConfigFile.cpp
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "CommandLineParser.hpp"
|
#include "CommandLineParser.hpp"
|
||||||
#include "SystemConfigFile.hpp"
|
#include "SystemConfigFile.hpp"
|
||||||
|
#include "ModFileTypes.hpp"
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -951,6 +952,7 @@ void Install(const fs::path &programPrefix, const std::string endpointAddress)
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
// apply policy changes we dropped into the polkit configuration files (allows pipedal_d to use NetworkManager dbus apis).
|
// apply policy changes we dropped into the polkit configuration files (allows pipedal_d to use NetworkManager dbus apis).
|
||||||
silentSysExec(SYSTEMCTL_BIN " restart polkit.service");
|
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");
|
sysExec(SYSTEMCTL_BIN " daemon-reload");
|
||||||
|
|
||||||
FixPermissions();
|
FixPermissions();
|
||||||
|
ModFileTypes::CreateDefaultDirectories("/var/pipedal/audio_uploads");
|
||||||
|
|
||||||
|
|
||||||
StopService(false);
|
StopService(false);
|
||||||
AvahiInstall();
|
AvahiInstall();
|
||||||
|
|||||||
+15
-2
@@ -22,7 +22,20 @@
|
|||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
|
||||||
JSON_MAP_BEGIN(FileEntry)
|
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_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()
|
JSON_MAP_END()
|
||||||
|
|||||||
+21
-3
@@ -26,15 +26,33 @@ namespace pipedal {
|
|||||||
class FileEntry {
|
class FileEntry {
|
||||||
public:
|
public:
|
||||||
FileEntry() { }
|
FileEntry() { }
|
||||||
FileEntry(std::string filename,bool isDirectory)
|
FileEntry(const std::string&pathname,const std::string &displayName,bool isDirectory, bool isProtected = false)
|
||||||
:filename_(filename), isDirectory_(isDirectory)
|
:pathname_(pathname), displayName_(displayName), isDirectory_(isDirectory),isProtected_(isProtected)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
std::string filename_;
|
std::string pathname_;
|
||||||
|
std::string displayName_;
|
||||||
bool isDirectory_ = false;
|
bool isDirectory_ = false;
|
||||||
|
bool isProtected_ = false;
|
||||||
|
|
||||||
DECLARE_JSON_MAP(FileEntry);
|
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);
|
||||||
|
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -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.
|
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
|
try
|
||||||
{
|
{
|
||||||
@@ -2305,7 +2305,7 @@ std::vector<FileEntry> PiPedalModel::GetFileList2(const std::string &relativePat
|
|||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
Lv2Log::warning("GetFileList() failed: (%s)", e.what());
|
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 SetUpdatePolicy(UpdatePolicyT updatePolicy);
|
||||||
void ForceUpdateCheck();
|
void ForceUpdateCheck();
|
||||||
std::vector<std::string> GetFileList(const UiFileProperty &fileProperty);
|
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);
|
void DeleteSampleFile(const std::filesystem::path &fileName);
|
||||||
std::string CreateNewSampleDirectory(const std::string &relativePath, const UiFileProperty &uiFileProperty);
|
std::string CreateNewSampleDirectory(const std::string &relativePath, const UiFileProperty &uiFileProperty);
|
||||||
|
|||||||
@@ -1589,8 +1589,8 @@ public:
|
|||||||
{
|
{
|
||||||
FileRequestArgs requestArgs;
|
FileRequestArgs requestArgs;
|
||||||
pReader->read(&requestArgs);
|
pReader->read(&requestArgs);
|
||||||
std::vector<FileEntry> list = this->model.GetFileList2(requestArgs.relativePath_, requestArgs.fileProperty_);
|
FileRequestResult result = this->model.GetFileList2(requestArgs.relativePath_, requestArgs.fileProperty_);
|
||||||
this->Reply(replyTo, "requestFileList2", list);
|
this->Reply(replyTo, "requestFileList2", result);
|
||||||
}
|
}
|
||||||
else if (message == "newPreset")
|
else if (message == "newPreset")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -465,6 +465,8 @@ JSON_MAP_REFERENCE(UiFileProperty, patchProperty)
|
|||||||
JSON_MAP_REFERENCE(UiFileProperty, fileTypes)
|
JSON_MAP_REFERENCE(UiFileProperty, fileTypes)
|
||||||
JSON_MAP_REFERENCE(UiFileProperty, portGroup)
|
JSON_MAP_REFERENCE(UiFileProperty, portGroup)
|
||||||
JSON_MAP_REFERENCE(UiFileProperty, resourceDirectory)
|
JSON_MAP_REFERENCE(UiFileProperty, resourceDirectory)
|
||||||
|
JSON_MAP_REFERENCE(UiFileProperty, modDirectories)
|
||||||
|
JSON_MAP_REFERENCE(UiFileProperty, useLegacyModDirectory)
|
||||||
JSON_MAP_END()
|
JSON_MAP_END()
|
||||||
|
|
||||||
JSON_MAP_BEGIN(UiFrequencyPlot)
|
JSON_MAP_BEGIN(UiFrequencyPlot)
|
||||||
|
|||||||
@@ -114,6 +114,8 @@ namespace pipedal {
|
|||||||
std::string patchProperty_;
|
std::string patchProperty_;
|
||||||
std::string portGroup_;
|
std::string portGroup_;
|
||||||
std::string resourceDirectory_;
|
std::string resourceDirectory_;
|
||||||
|
std::vector<std::string> modDirectories_;
|
||||||
|
bool useLegacyModDirectory_= false;
|
||||||
public:
|
public:
|
||||||
using ptr = std::shared_ptr<UiFileProperty>;
|
using ptr = std::shared_ptr<UiFileProperty>;
|
||||||
UiFileProperty() { }
|
UiFileProperty() { }
|
||||||
@@ -121,9 +123,17 @@ namespace pipedal {
|
|||||||
UiFileProperty(const std::string&name, const std::string&patchProperty,const std::string &directory);
|
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_; }
|
const std::string &label() const { return label_; }
|
||||||
int32_t index() const { return index_; }
|
int32_t index() const { return index_; }
|
||||||
|
|
||||||
const std::string &directory() const { return directory_; }
|
const std::string &directory() const { return directory_; }
|
||||||
|
void directory(const std::string &path) { directory_ = path; }
|
||||||
|
|
||||||
const std::string&portGroup() const { return portGroup_; }
|
const std::string&portGroup() const { return portGroup_; }
|
||||||
|
|
||||||
const std::vector<UiFileType> &fileTypes() const { return fileTypes_; }
|
const std::vector<UiFileType> &fileTypes() const { return fileTypes_; }
|
||||||
|
|||||||
+60
-25
@@ -46,6 +46,7 @@
|
|||||||
#include "PiPedalException.hpp"
|
#include "PiPedalException.hpp"
|
||||||
#include "StdErrorCapture.hpp"
|
#include "StdErrorCapture.hpp"
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
#include "ModFileTypes.hpp"
|
||||||
|
|
||||||
#include "Locale.hpp"
|
#include "Locale.hpp"
|
||||||
|
|
||||||
@@ -193,7 +194,6 @@ void PluginHost::LilvUris::Initialize(LilvWorld *pWorld)
|
|||||||
dc__format = lilv_new_uri(pWorld, "http://purl.org/dc/terms/format");
|
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()
|
void PluginHost::LilvUris::Free()
|
||||||
@@ -478,8 +478,7 @@ void PluginHost::Load(const char *lv2Path)
|
|||||||
const char *pb1 = left->name().c_str();
|
const char *pb1 = left->name().c_str();
|
||||||
const char *pb2 = right->name().c_str();
|
const char *pb2 = right->name().c_str();
|
||||||
return collator->Compare(
|
return collator->Compare(
|
||||||
left->name(),right->name())
|
left->name(), right->name()) < 0;
|
||||||
< 0;
|
|
||||||
};
|
};
|
||||||
std::sort(this->plugins_.begin(), this->plugins_.end(), compare);
|
std::sort(this->plugins_.begin(), this->plugins_.end(), compare);
|
||||||
|
|
||||||
@@ -630,11 +629,43 @@ std::shared_ptr<PiPedalUI> Lv2PluginInfo::FindWritablePathProperties(PluginHost
|
|||||||
// "nam,nammodel"
|
// "nam,nammodel"
|
||||||
AutoLilvNode lilvfileType{lilv_nodes_get(mod__fileTypes, i)};
|
AutoLilvNode lilvfileType{lilv_nodes_get(mod__fileTypes, i)};
|
||||||
std::string fileTypes = lilvfileType.AsString();
|
std::string fileTypes = lilvfileType.AsString();
|
||||||
|
ModFileTypes modFileTypes(fileTypes);
|
||||||
|
|
||||||
for (std::string&type: split(fileTypes,','))
|
for (const std::string &rootDirectory : modFileTypes.rootDirectories())
|
||||||
|
{
|
||||||
|
fileProperty->modDirectories().push_back(rootDirectory);
|
||||||
|
}
|
||||||
|
for (const std::string &type : modFileTypes.fileTypes())
|
||||||
{
|
{
|
||||||
fileProperty->fileTypes().push_back(UiFileType(SS(type << " file"), SS('.' << type)));
|
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)
|
if (!mod__fileTypes)
|
||||||
{
|
{
|
||||||
@@ -1389,32 +1420,35 @@ std::shared_ptr<HostWorkerThread> PluginHost::GetHostWorkerThread()
|
|||||||
return pHostWorkerThread;
|
return pHostWorkerThread;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ResourceInfo {
|
class ResourceInfo
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
ResourceInfo(const std::string &filePropertyDirectory, const std::string &resourceDirectory)
|
ResourceInfo(const std::string &filePropertyDirectory, const std::string &resourceDirectory)
|
||||||
: filePropertyDirectory(filePropertyDirectory), resourceDirectory(resourceDirectory) {}
|
: filePropertyDirectory(filePropertyDirectory), resourceDirectory(resourceDirectory) {}
|
||||||
|
|
||||||
std::string filePropertyDirectory;
|
std::string filePropertyDirectory;
|
||||||
std::string resourceDirectory;
|
std::string resourceDirectory;
|
||||||
bool operator==(const ResourceInfo&other) const {
|
bool operator==(const ResourceInfo &other) const
|
||||||
|
{
|
||||||
return this->filePropertyDirectory == other.filePropertyDirectory && this->resourceDirectory == other.resourceDirectory;
|
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)
|
if (this->filePropertyDirectory < other.filePropertyDirectory)
|
||||||
return true;
|
return true;
|
||||||
if (this->filePropertyDirectory > other.filePropertyDirectory)
|
if (this->filePropertyDirectory > other.filePropertyDirectory)
|
||||||
return false;
|
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;
|
namespace fs = std::filesystem;
|
||||||
try {
|
try
|
||||||
if (!fs::exists(targetDirectory)) return false;
|
{
|
||||||
|
if (!fs::exists(targetDirectory))
|
||||||
|
return false;
|
||||||
for (auto &directoryEntry : fs::directory_iterator(sourceDirectory))
|
for (auto &directoryEntry : fs::directory_iterator(sourceDirectory))
|
||||||
{
|
{
|
||||||
fs::path thisPath = directoryEntry.path();
|
fs::path thisPath = directoryEntry.path();
|
||||||
@@ -1427,7 +1461,9 @@ static bool anyTargetFilesExist(const std::filesystem::path &sourceDirectory, co
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
fs::path targetPath = targetDirectory / thisPath.filename();
|
fs::path targetPath = targetDirectory / thisPath.filename();
|
||||||
if (fs::exists(targetPath))
|
if (fs::exists(targetPath))
|
||||||
{
|
{
|
||||||
@@ -1435,9 +1471,9 @@ static bool anyTargetFilesExist(const std::filesystem::path &sourceDirectory, co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (const std::exception&) {
|
catch (const std::exception &)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1452,9 +1488,12 @@ static void createTargetLinks(const std::filesystem::path &sourceDirectory, cons
|
|||||||
{
|
{
|
||||||
fs::path childSource = dirEntry.path();
|
fs::path childSource = dirEntry.path();
|
||||||
fs::path childTarget = targetDirectory / childSource.filename();
|
fs::path childTarget = targetDirectory / childSource.filename();
|
||||||
if (dirEntry.is_directory()) {
|
if (dirEntry.is_directory())
|
||||||
|
{
|
||||||
createTargetLinks(childSource, childTarget);
|
createTargetLinks(childSource, childTarget);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
fs::create_symlink(childSource, childTarget);
|
fs::create_symlink(childSource, childTarget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1482,7 +1521,8 @@ void PluginHost::CheckForResourceInitialization(const std::string &pluginUri,con
|
|||||||
resourceInfoSet.insert(ResourceInfo(fileProperty->directory(), fileProperty->resourceDirectory()));
|
resourceInfoSet.insert(ResourceInfo(fileProperty->directory(), fileProperty->resourceDirectory()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
for (const ResourceInfo &resourceInfo : resourceInfoSet)
|
for (const ResourceInfo &resourceInfo : resourceInfoSet)
|
||||||
{
|
{
|
||||||
std::filesystem::path sourcePath = bundlePath / resourceInfo.resourceDirectory;
|
std::filesystem::path sourcePath = bundlePath / resourceInfo.resourceDirectory;
|
||||||
@@ -1491,9 +1531,9 @@ void PluginHost::CheckForResourceInitialization(const std::string &pluginUri,con
|
|||||||
{
|
{
|
||||||
createTargetLinks(sourcePath, targetPath);
|
createTargetLinks(sourcePath, targetPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (const std::exception &e)
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
Lv2Log::error(SS("CheckForResourceInitialization: " << e.what()));
|
Lv2Log::error(SS("CheckForResourceInitialization: " << e.what()));
|
||||||
}
|
}
|
||||||
@@ -1505,16 +1545,13 @@ json_variant PluginHost::MapPath(const json_variant&json)
|
|||||||
{
|
{
|
||||||
AtomConverter converter(GetMapFeature());
|
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());
|
AtomConverter converter(GetMapFeature());
|
||||||
return converter.AbstractPath(json, GetPluginStoragePath());
|
return converter.AbstractPath(json, GetPluginStoragePath());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string PluginHost::MapPath(const std::string &abstractPath)
|
std::string PluginHost::MapPath(const std::string &abstractPath)
|
||||||
{
|
{
|
||||||
auto storagePath = GetPluginStoragePath();
|
auto storagePath = GetPluginStoragePath();
|
||||||
@@ -1523,8 +1560,6 @@ std::string PluginHost::MapPath(const std::string &abstractPath)
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return SS(storagePath << '/' << abstractPath);
|
return SS(storagePath << '/' << abstractPath);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
std::string PluginHost::AbstractPath(const std::string &path)
|
std::string PluginHost::AbstractPath(const std::string &path)
|
||||||
{
|
{
|
||||||
|
|||||||
+193
-26
@@ -22,6 +22,7 @@
|
|||||||
#include "Storage.hpp"
|
#include "Storage.hpp"
|
||||||
#include "AudioConfig.hpp"
|
#include "AudioConfig.hpp"
|
||||||
#include "PiPedalException.hpp"
|
#include "PiPedalException.hpp"
|
||||||
|
#include <stdexcept>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include "json.hpp"
|
#include "json.hpp"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -32,14 +33,44 @@
|
|||||||
#include "PluginHost.hpp"
|
#include "PluginHost.hpp"
|
||||||
#include "ss.hpp"
|
#include "ss.hpp"
|
||||||
#include "ofstream_synced.hpp"
|
#include "ofstream_synced.hpp"
|
||||||
|
#include "ModFileTypes.hpp"
|
||||||
|
|
||||||
using namespace pipedal;
|
using namespace pipedal;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
const char *BANK_EXTENSION = ".bank";
|
const char *BANK_EXTENSION = ".bank";
|
||||||
const char *BANKS_FILENAME = "index.banks";
|
const char *BANKS_FILENAME = "index.banks";
|
||||||
|
|
||||||
#define USER_SETTINGS_FILENAME "userSettings.json";
|
#define USER_SETTINGS_FILENAME "userSettings.json";
|
||||||
|
|
||||||
|
|
||||||
|
static bool isSubdirectory(const fs::path&path, const fs::path&basePath)
|
||||||
|
{
|
||||||
|
auto iPath = path.begin();
|
||||||
|
for (auto i = basePath.begin(); i != basePath.end(); ++i)
|
||||||
|
{
|
||||||
|
if (iPath == path.end())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*i) != (*iPath))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++iPath;
|
||||||
|
}
|
||||||
|
while (iPath != path.end())
|
||||||
|
{
|
||||||
|
if (iPath->string() == "..")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
++iPath;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Storage::Storage()
|
Storage::Storage()
|
||||||
{
|
{
|
||||||
SetConfigRoot("~/var/Config");
|
SetConfigRoot("~/var/Config");
|
||||||
@@ -1648,63 +1679,190 @@ static bool ensureNoDotDot(const std::filesystem::path&path)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void AddFilesToResult(
|
||||||
std::vector<FileEntry> Storage::GetFileList2(const std::string &relativePath,const UiFileProperty &fileProperty)
|
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_;
|
||||||
{
|
|
||||||
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
|
try
|
||||||
{
|
{
|
||||||
for (auto const &dir_entry : std::filesystem::directory_iterator(audioFileDirectory))
|
for (auto const &dir_entry : std::filesystem::directory_iterator(rootPath))
|
||||||
{
|
{
|
||||||
|
const auto &path = dir_entry.path();
|
||||||
|
auto name = path.filename().string();
|
||||||
if (dir_entry.is_regular_file())
|
if (dir_entry.is_regular_file())
|
||||||
{
|
{
|
||||||
auto &path = dir_entry.path();
|
|
||||||
auto name = path.filename().string();
|
|
||||||
if (name.length() > 0 && name[0] != '.') // don't show hidden files.
|
if (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!
|
resultFiles.push_back(
|
||||||
result.push_back(FileEntry(path,false));
|
FileEntry(path,name,false,false)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (dir_entry.is_directory()) {
|
} else if (dir_entry.is_directory()) {
|
||||||
result.push_back(FileEntry(dir_entry.path(),true));
|
resultFiles.push_back(FileEntry{path,name,true,fs::is_symlink(path)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception &error)
|
catch (const std::exception &error)
|
||||||
{
|
{
|
||||||
throw std::logic_error("GetFileList failed. Directory not found: " + audioFileDirectory.string());
|
throw std::logic_error("GetFileList failed. Directory not found: " + rootPath.string());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort lexicographically
|
// sort lexicographically
|
||||||
|
|
||||||
auto collator = Locale::GetInstance()->GetCollator();
|
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_)
|
if (l.isDirectory_ != r.isDirectory_)
|
||||||
{
|
{
|
||||||
return l.isDirectory_ > r.isDirectory_;
|
return l.isDirectory_ > r.isDirectory_;
|
||||||
}
|
}
|
||||||
return collator->Compare(l.filename_,r.filename_) < 0;
|
return collator->Compare(l.displayName_,r.displayName_) < 0;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
FileRequestResult Storage::GetModFileList2(const std::string &relativePath,const UiFileProperty &fileProperty)
|
||||||
|
{
|
||||||
|
FileRequestResult result;
|
||||||
|
fs::path uploadsDirectory = GetPluginUploadDirectory();
|
||||||
|
|
||||||
|
if (relativePath.empty())
|
||||||
|
{
|
||||||
|
// return the synthetic root.
|
||||||
|
result.isProtected_ = true;
|
||||||
|
|
||||||
|
for (const auto&modDirectory: fileProperty.modDirectories())
|
||||||
|
{
|
||||||
|
const auto directoryInfo = ModFileTypes::GetModDirectory(modDirectory);
|
||||||
|
if (directoryInfo)
|
||||||
|
{
|
||||||
|
result.files_.push_back(
|
||||||
|
FileEntry(uploadsDirectory / directoryInfo->pipedalPath,directoryInfo->displayName,true,true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (fileProperty.useLegacyModDirectory())
|
||||||
|
{
|
||||||
|
result.files_.push_back(
|
||||||
|
FileEntry(uploadsDirectory / fileProperty.directory(),fs::path(fileProperty.directory()).filename().string(),true,true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
result.breadcrumbs_.push_back({"","Home"});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
fs::path modDirectoryPath;
|
||||||
|
|
||||||
|
result.breadcrumbs_.push_back({"","Home"});
|
||||||
|
fs::path fsRelativePath { relativePath};
|
||||||
|
|
||||||
|
for (const auto &modDirectory: fileProperty.modDirectories())
|
||||||
|
{
|
||||||
|
const ModFileTypes::ModDirectory*modDirectoryInfo = ModFileTypes::GetModDirectory(modDirectory);
|
||||||
|
if (modDirectoryInfo)
|
||||||
|
{
|
||||||
|
if (isSubdirectory(fsRelativePath, uploadsDirectory / modDirectoryInfo->pipedalPath))
|
||||||
|
{
|
||||||
|
modDirectoryPath = uploadsDirectory / modDirectoryInfo->pipedalPath;
|
||||||
|
result.breadcrumbs_.push_back({modDirectoryPath.string(),modDirectoryInfo->displayName});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (modDirectoryPath.empty() && fileProperty.useLegacyModDirectory())
|
||||||
|
{
|
||||||
|
if (isSubdirectory(fsRelativePath, uploadsDirectory / fileProperty.directory()))
|
||||||
|
{
|
||||||
|
modDirectoryPath = uploadsDirectory / fileProperty.directory();
|
||||||
|
result.breadcrumbs_.push_back({modDirectoryPath.string(),fs::path(fileProperty.directory()).filename().string()});
|
||||||
|
} else {
|
||||||
|
ThrowPermissionDeniedError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add remaing path segements as breadcrumbs.
|
||||||
|
{
|
||||||
|
fs::path modPath {modDirectoryPath};
|
||||||
|
fs::path rp { relativePath};
|
||||||
|
auto iRp = rp.begin();
|
||||||
|
// skip past one or more segments in the modDiretoryPath.
|
||||||
|
for (auto iModPath = modPath.begin(); iModPath != modPath.end(); ++iModPath)
|
||||||
|
{
|
||||||
|
if (iRp != rp.end()) {
|
||||||
|
++iRp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (iRp != rp.end())
|
||||||
|
{
|
||||||
|
result.breadcrumbs_.push_back({*iRp,*iRp});
|
||||||
|
++iRp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddFilesToResult(result,fileProperty,relativePath);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FileRequestResult Storage::GetFileList2(const std::string &relativePath_,const UiFileProperty &fileProperty)
|
||||||
|
{
|
||||||
|
std::string absolutePath = relativePath_;
|
||||||
|
if (!ensureNoDotDot(absolutePath))
|
||||||
|
{
|
||||||
|
ThrowPermissionDeniedError();
|
||||||
|
}
|
||||||
|
if (fileProperty.modDirectories().size() > 1 || (fileProperty.modDirectories().size() == 1 && fileProperty.useLegacyModDirectory()))
|
||||||
|
{
|
||||||
|
return Storage::GetModFileList2(absolutePath,fileProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileRequestResult result;
|
||||||
|
|
||||||
|
if (fileProperty.directory().empty())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("fileProperty.directory() not specified.");
|
||||||
|
}
|
||||||
|
std::filesystem::path pluginRootDirectory = this->GetPluginUploadDirectory() / fileProperty.directory();
|
||||||
|
if (absolutePath == "")
|
||||||
|
{
|
||||||
|
absolutePath = pluginRootDirectory.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
result.breadcrumbs_.push_back({"","Home"});
|
||||||
|
fs::path fsAbsolutePath {absolutePath};
|
||||||
|
auto iAbsolutePath = fsAbsolutePath.begin();
|
||||||
|
for (auto i = pluginRootDirectory.begin(); i != pluginRootDirectory.end(); ++i)
|
||||||
|
{
|
||||||
|
if (iAbsolutePath == fsAbsolutePath.end() || (*i) != *iAbsolutePath)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Directory is not a subdirectory of the plugin root directory.");
|
||||||
|
}
|
||||||
|
++iAbsolutePath;
|
||||||
|
}
|
||||||
|
auto cumulativePath = pluginRootDirectory;
|
||||||
|
|
||||||
|
while (iAbsolutePath != fsAbsolutePath.end())
|
||||||
|
{
|
||||||
|
cumulativePath /= (*iAbsolutePath);
|
||||||
|
result.breadcrumbs_.push_back({cumulativePath.string(),iAbsolutePath->string()});
|
||||||
|
++iAbsolutePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isSubdirectory(absolutePath,pluginRootDirectory))
|
||||||
|
{
|
||||||
|
throw std::runtime_error(SS("Improper location. " << absolutePath));
|
||||||
|
}
|
||||||
|
AddFilesToResult(result,fileProperty,absolutePath);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1753,11 +1911,20 @@ void Storage::DeleteSampleFile(const std::filesystem::path &fileName)
|
|||||||
{
|
{
|
||||||
if (std::filesystem::is_directory(fileName))
|
if (std::filesystem::is_directory(fileName))
|
||||||
{
|
{
|
||||||
|
if (fileName.string().length() <= 1) // guard against rm -rf / (bitter experience)
|
||||||
|
{
|
||||||
|
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)
|
if (fileName.string().length() > 1) // guard against rm -rf / (bitter experience)
|
||||||
{
|
{
|
||||||
std::filesystem::remove_all(fileName);
|
std::filesystem::remove_all(fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
std::filesystem::remove(fileName);
|
std::filesystem::remove(fileName);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -152,7 +152,9 @@ public:
|
|||||||
int64_t DeleteBank(int64_t bankId);
|
int64_t DeleteBank(int64_t bankId);
|
||||||
|
|
||||||
std::vector<std::string> GetFileList(const UiFileProperty&fileProperty);
|
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);
|
void SetJackChannelSelection(const JackChannelSelection&channelSelection);
|
||||||
|
|||||||
Reference in New Issue
Block a user