Convert all file ops to use absolute paths

This commit is contained in:
Robin Davies
2024-10-28 08:45:52 -04:00
parent 6bff83604d
commit 4b713f405a
11 changed files with 286 additions and 182 deletions
+1
View File
@@ -198,6 +198,7 @@ class DialogEx extends React.Component<DialogExProps,DialogExState> implements I
{ {
this.onEnterKey(); this.onEnterKey();
} }
evt.stopPropagation();
} }
render() { render() {
let { tag,onClose,...extra} = this.props; let { tag,onClose,...extra} = this.props;
+2 -4
View File
@@ -741,9 +741,7 @@ export default withStyles(styles, { withTheme: true })(
} }
} }
uploadPage={ uploadPage={
"uploadUserFile?directory=" + encodeURIComponent( "uploadUserFile?directory=" + encodeURIComponent(this.state.navDirectory)
pathConcat(this.props.fileProperty.directory, this.state.navDirectory)
)
+ "&ext=" + "&ext="
+ encodeURIComponent(this.getFileExtensionList(this.props.fileProperty)) + encodeURIComponent(this.getFileExtensionList(this.props.fileProperty))
} }
@@ -792,7 +790,7 @@ export default withStyles(styles, { withTheme: true })(
defaultPath={this.getDefaultPath()} defaultPath={this.getDefaultPath()}
excludeDirectory={ excludeDirectory={
this.isDirectory(this.state.selectedFile) this.isDirectory(this.state.selectedFile)
? pathConcat(this.state.navDirectory, pathFileName(this.state.selectedFile)) : ""} ? this.state.selectedFile : ""}
onClose={() => { this.setState({ moveDialogOpen: false }); }} onClose={() => { this.setState({ moveDialogOpen: false }); }}
onOk={ onOk={
(path) => { (path) => {
+24 -19
View File
@@ -38,28 +38,18 @@ import { isDarkMode } from './DarkMode';
import IconButton from '@mui/material/IconButton'; 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 { class DirectoryTree {
name: string = ""; name: string = "";
path: string = ""; path: string = "";
expanded: boolean = false; expanded: boolean = false;
isProtected: boolean = true;
children: DirectoryTree[] = []; children: DirectoryTree[] = [];
constructor(tree: FilePropertyDirectoryTree, parentTree: (DirectoryTree | null) = null) { constructor(tree: FilePropertyDirectoryTree, parentTree: (DirectoryTree | null) = null) {
this.name = tree.directoryName; this.name = tree.directoryName;
if (parentTree) { this.path = tree.directoryName;
this.path = pathConcat(parentTree.path,tree.directoryName); this.name = tree.displayName;
} else { this.isProtected = tree.isProtected;
this.name = "Home";
this.path = tree.directoryName;
this.expanded = true;
}
for (var treeChild of tree.children) { for (var treeChild of tree.children) {
this.children.push(new DirectoryTree(treeChild, this)); this.children.push(new DirectoryTree(treeChild, this));
} }
@@ -143,6 +133,7 @@ export interface FilePropertyDirectorySelectDialogState {
selectedPath: string; selectedPath: string;
directoryTree?: DirectoryTree; directoryTree?: DirectoryTree;
hasSelection: boolean; hasSelection: boolean;
isProtected: boolean;
directoryTreeInvalidatecount: number; directoryTreeInvalidatecount: number;
}; };
@@ -160,6 +151,7 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC
selectedPath: props.defaultPath, selectedPath: props.defaultPath,
directoryTree: undefined, directoryTree: undefined,
hasSelection: false, hasSelection: false,
isProtected: true,
directoryTreeInvalidatecount: 0 directoryTreeInvalidatecount: 0
}; };
@@ -192,14 +184,19 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC
this.model.getFilePropertyDirectoryTree(this.props.uiFileProperty) this.model.getFilePropertyDirectoryTree(this.props.uiFileProperty)
.then((filePropertyDirectoryTree) => { .then((filePropertyDirectoryTree) => {
let myTree = new DirectoryTree(filePropertyDirectoryTree); let myTree = new DirectoryTree(filePropertyDirectoryTree);
if (this.props.excludeDirectory) if (this.props.excludeDirectory.length !== 0)
{ {
myTree.remove(this.props.excludeDirectory); myTree.remove(this.props.excludeDirectory);
} }
myTree.expand(this.state.selectedPath); myTree.expand(this.state.selectedPath);
let hasSelection = myTree.find(this.state.selectedPath) != null; let selection = myTree.find(this.state.selectedPath);
this.setState({ directoryTree: myTree, hasSelection: hasSelection }); let hasSelection = !!selection;
this.setState({
directoryTree: myTree,
hasSelection: hasSelection,
isProtected: selection ? selection.isProtected : false
});
this.requestScroll = true; this.requestScroll = true;
}) })
.catch((e) => { .catch((e) => {
@@ -223,6 +220,9 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC
onOK() { onOK() {
if (this.state.isProtected) {
return;
}
if (this.state.hasSelection) { if (this.state.hasSelection) {
this.props.onOk(this.state.selectedPath); this.props.onOk(this.state.selectedPath);
} }
@@ -238,7 +238,11 @@ export default class FilePropertyDirectorySelectDialog extends ResizeResponsiveC
private onTreeClick(directoryTree: DirectoryTree) { private onTreeClick(directoryTree: DirectoryTree) {
if (!this.state.directoryTree) return; if (!this.state.directoryTree) return;
this.state.directoryTree.expand(directoryTree.path); 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) { private renderTree_(directoryTree: DirectoryTree) {
let ref: ((element: HTMLButtonElement | null) => void) | undefined = undefined; 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"> <Button variant="dialogSecondary" onClick={() => { this.onClose(); }} color="primary">
Cancel Cancel
</Button> </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 OK
</Button> </Button>
</DialogActions> </DialogActions>
+4
View File
@@ -21,6 +21,8 @@
export default class FilePropertyDirectoryTree { export default class FilePropertyDirectoryTree {
deserialize(input: any) : FilePropertyDirectoryTree { deserialize(input: any) : FilePropertyDirectoryTree {
this.directoryName = input.directoryName; this.directoryName = input.directoryName;
this.displayName = input.displayName;
this.isProtected = input.isProtected;
this.children = FilePropertyDirectoryTree.deserialize_array(input.children); this.children = FilePropertyDirectoryTree.deserialize_array(input.children);
return this; return this;
} }
@@ -35,5 +37,7 @@ export default class FilePropertyDirectoryTree {
directoryName: string = ""; directoryName: string = "";
displayName: string = "";
isProtected: boolean = false;
children: FilePropertyDirectoryTree[] = []; children: FilePropertyDirectoryTree[] = [];
} }
+13 -3
View File
@@ -2510,7 +2510,7 @@ export class PiPedalModel //implements PiPedalModel
link.click(); 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) => { let result = new Promise<string>((resolve, reject) => {
try { try {
if (file.size > this.maxFileUploadSize) { if (file.size > this.maxFileUploadSize) {
@@ -2551,10 +2551,20 @@ export class PiPedalModel //implements PiPedalModel
} }
}) })
.then((json) => { .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) => { .catch((error) => {
reject("Upload failed. " + error); if (error instanceof Error)
{
reject("Upload failed. " + (error as Error).message);
} else {
reject("Upload failed. " + error);
}
}) })
; ;
} catch (error) { } catch (error) {
+1 -1
View File
@@ -208,7 +208,7 @@ export default class UploadFileDialog extends ResizeResponsiveComponent<UploadFi
{ {
throw new Error("Invalid file extension."); throw new Error("Invalid file extension.");
} }
let filename = await this.model.uploadFile( let filename = await this.model.uploadUserFile(
this.props.uploadPage, this.props.uploadPage,
this.uploadList[i].file, this.uploadList[i].file,
"application/octet-stream", "application/octet-stream",
+11 -1
View File
@@ -20,16 +20,26 @@
#include "FilePropertyDirectoryTree.hpp" #include "FilePropertyDirectoryTree.hpp"
using namespace pipedal; using namespace pipedal;
namespace fs = std::filesystem;
FilePropertyDirectoryTree::FilePropertyDirectoryTree() FilePropertyDirectoryTree::FilePropertyDirectoryTree()
{ {
} }
FilePropertyDirectoryTree::FilePropertyDirectoryTree(const std::string &directoryName) 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_BEGIN(FilePropertyDirectoryTree)
JSON_MAP_REFERENCE(FilePropertyDirectoryTree, directoryName) JSON_MAP_REFERENCE(FilePropertyDirectoryTree, directoryName)
JSON_MAP_REFERENCE(FilePropertyDirectoryTree, displayName)
JSON_MAP_REFERENCE(FilePropertyDirectoryTree, isProtected)
JSON_MAP_REFERENCE(FilePropertyDirectoryTree, children) JSON_MAP_REFERENCE(FilePropertyDirectoryTree, children)
JSON_MAP_END() JSON_MAP_END()
+3
View File
@@ -29,7 +29,10 @@ namespace pipedal {
FilePropertyDirectoryTree(); FilePropertyDirectoryTree();
FilePropertyDirectoryTree(const std::string&directoryName); FilePropertyDirectoryTree(const std::string&directoryName);
FilePropertyDirectoryTree(const std::string&directoryName,const std::string&displayName);
std::string directoryName_; std::string directoryName_;
std::string displayName_;
bool isProtected_ = false;
std::vector<std::unique_ptr<FilePropertyDirectoryTree>> children_; std::vector<std::unique_ptr<FilePropertyDirectoryTree>> children_;
DECLARE_JSON_MAP(FilePropertyDirectoryTree); DECLARE_JSON_MAP(FilePropertyDirectoryTree);
+74 -34
View File
@@ -71,6 +71,12 @@ static bool isSubdirectory(const fs::path&path, const fs::path&basePath)
return true; return true;
} }
static bool hasSyntheticModRoot(const UiFileProperty &fileProperty)
{
return (fileProperty.modDirectories().size() > 1 || (fileProperty.modDirectories().size() == 1 && fileProperty.useLegacyModDirectory()));
}
Storage::Storage() Storage::Storage()
{ {
SetConfigRoot("~/var/Config"); SetConfigRoot("~/var/Config");
@@ -1819,7 +1825,7 @@ FileRequestResult Storage::GetFileList2(const std::string &relativePath_,const U
{ {
ThrowPermissionDeniedError(); ThrowPermissionDeniedError();
} }
if (fileProperty.modDirectories().size() > 1 || (fileProperty.modDirectories().size() == 1 && fileProperty.useLegacyModDirectory())) if (hasSyntheticModRoot(fileProperty))
{ {
return Storage::GetModFileList2(absolutePath,fileProperty); return Storage::GetModFileList2(absolutePath,fileProperty);
} }
@@ -1873,29 +1879,32 @@ bool Storage::IsValidSampleFileName(const std::filesystem::path &fileName)
{ {
return false; return false;
} }
if (!ensureNoDotDot(fileName))
{
return false;
}
std::filesystem::path audioFilePath = this->GetPluginUploadDirectory(); std::filesystem::path audioFilePath = this->GetPluginUploadDirectory();
std::filesystem::path parentDirectory = fileName.parent_path(); 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; return false;
} }
std::string name = parentDirectory.filename().string(); if (*i != *iTarget) {
if (parentDirectory == audioFilePath)
return true;
parentDirectory = parentDirectory.parent_path();
if (parentDirectory.string().length() < audioFilePath.string().length())
{
return false; return false;
} }
++iTarget;
} }
while (iTarget != parentDirectory.end())
{
if (iTarget->string() == "..")
{
return false;
}
++iTarget;
}
return true;
} }
void Storage::DeleteSampleFile(const std::filesystem::path &fileName) void Storage::DeleteSampleFile(const std::filesystem::path &fileName)
{ {
@@ -1936,13 +1945,13 @@ void Storage::DeleteSampleFile(const std::filesystem::path &fileName)
} }
std::filesystem::path Storage::MakeUserFilePath(const std::string &directory, const std::string &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 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)) if (!this->IsValidSampleFileName(result))
{ {
throw std::logic_error(SS("Invalid upload path: " << result)); throw std::logic_error(SS("Invalid upload path: " << result));
@@ -2065,8 +2074,8 @@ void Storage::FillSampleDirectoryTree(FilePropertyDirectoryTree*node, const std:
{ {
if (child.is_directory()) if (child.is_directory())
{ {
const auto& childPath = child.path(); const auto& childPath = child.path();
FilePropertyDirectoryTree::ptr childTree = std::make_unique<FilePropertyDirectoryTree>(childPath.filename()); FilePropertyDirectoryTree::ptr childTree = std::make_unique<FilePropertyDirectoryTree>(childPath);
FillSampleDirectoryTree(childTree.get(),childPath); FillSampleDirectoryTree(childTree.get(),childPath);
node->children_.push_back(std::move(childTree)); node->children_.push_back(std::move(childTree));
} }
@@ -2078,22 +2087,53 @@ void Storage::FillSampleDirectoryTree(FilePropertyDirectoryTree*node, const std:
return collator->Compare(left->directoryName_,right->directoryName_) < 0; 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>(""); fs::path uploadDirectory = this->GetPluginUploadDirectory();
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();
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;
} }
+1 -1
View File
@@ -226,7 +226,7 @@ public:
const std::string&oldRelativePath, const std::string&oldRelativePath,
const std::string&newRelativePath, const std::string&newRelativePath,
const UiFileProperty&uiFileProperty); const UiFileProperty&uiFileProperty);
FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty) const; FilePropertyDirectoryTree::ptr GetFilePropertydirectoryTree(const UiFileProperty&uiFileProperty);
}; };
+152 -119
View File
@@ -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 // 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 // this software and associated documentation files (the "Software"), to deal in
@@ -32,7 +32,7 @@
#include "UpdaterSecurity.hpp" #include "UpdaterSecurity.hpp"
#include "TemporaryFile.hpp" #include "TemporaryFile.hpp"
#include "PresetBundle.hpp" #include "PresetBundle.hpp"
#include "json.hpp"
#define OLD_PRESET_EXTENSION ".piPreset" #define OLD_PRESET_EXTENSION ".piPreset"
#define PRESET_EXTENSION ".piPreset" #define PRESET_EXTENSION ".piPreset"
@@ -50,40 +50,52 @@ using namespace pipedal;
using namespace boost::system; using namespace boost::system;
namespace fs = std::filesystem; 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) static bool IsZipFile(const std::filesystem::path &path)
{ {
std::ifstream f(path); std::ifstream f(path);
if (!f.is_open()) return false; if (!f.is_open())
return false;
char c[4]; char c[4];
memset(c,0,sizeof(c)); memset(c, 0, sizeof(c));
f >> c[0] >> c[1] >> c[2] >> c[3]; f >> c[0] >> c[1] >> c[2] >> c[3];
// official file header according to PKware documetnation. // official file header according to PKware documetnation.
return c[0] == 0x50 && c[1] == 0x4B && c[2] == 0x03 && c[3] == 0x04; return c[0] == 0x50 && c[1] == 0x4B && c[2] == 0x03 && c[3] == 0x04;
} }
class ExtensionChecker { class ExtensionChecker
{
public: public:
ExtensionChecker(const std::string&extensionList) ExtensionChecker(const std::string &extensionList)
:extensions(split(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; return true;
for (const auto&ext: extensions) for (const auto &ext : extensions)
{ {
if (ext == extension) if (ext == extension)
return true; return true;
} }
return false; return false;
} }
private: private:
std::vector<std::string> extensions; std::vector<std::string> extensions;
}; };
@@ -133,7 +145,8 @@ public:
else if (segment == "uploadBank") else if (segment == "uploadBank")
{ {
return true; return true;
} else if (segment == "uploadUserFile") }
else if (segment == "uploadUserFile")
{ {
return true; return true;
} }
@@ -159,7 +172,7 @@ public:
PluginPresets pluginPresets = model->GetPluginPresets(pluginUri); PluginPresets pluginPresets = model->GetPluginPresets(pluginUri);
std::stringstream s; std::stringstream s;
json_writer writer(s,false); json_writer writer(s, false);
writer.write(pluginPresets); writer.write(pluginPresets);
*pContent = s.str(); *pContent = s.str();
} }
@@ -176,7 +189,7 @@ public:
file.selectedPreset(newInstanceId); file.selectedPreset(newInstanceId);
std::stringstream s; std::stringstream s;
json_writer writer(s,false); json_writer writer(s, false);
writer.write(file); writer.write(file);
*pContent = s.str(); *pContent = s.str();
*pName = pedalboard.name(); *pName = pedalboard.name();
@@ -210,12 +223,11 @@ public:
std::string content; std::string content;
GetPluginPresets(request_uri, &name, &content); GetPluginPresets(request_uri, &name, &content);
TemporaryFile tmpFile { WEB_TEMP_DIR}; TemporaryFile tmpFile{WEB_TEMP_DIR};
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model),content); PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model), content);
presetbundleWriter->WriteToFile(tmpFile.Path()); presetbundleWriter->WriteToFile(tmpFile.Path());
size_t contentLength = std::filesystem::file_size(tmpFile.Path()); size_t contentLength = std::filesystem::file_size(tmpFile.Path());
res.set(HttpField::content_type, PLUGIN_PRESETS_MIME_TYPE); res.set(HttpField::content_type, PLUGIN_PRESETS_MIME_TYPE);
res.set(HttpField::cache_control, "no-cache"); res.set(HttpField::cache_control, "no-cache");
res.setContentLength(content.length()); res.setContentLength(content.length());
@@ -228,8 +240,8 @@ public:
std::string content; std::string content;
GetPreset(request_uri, &name, &content); GetPreset(request_uri, &name, &content);
TemporaryFile tmpFile { WEB_TEMP_DIR}; TemporaryFile tmpFile{WEB_TEMP_DIR};
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content); PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model), content);
presetbundleWriter->WriteToFile(tmpFile.Path()); presetbundleWriter->WriteToFile(tmpFile.Path());
size_t contentLength = std::filesystem::file_size(tmpFile.Path()); size_t contentLength = std::filesystem::file_size(tmpFile.Path());
@@ -245,14 +257,11 @@ public:
std::string content; std::string content;
GetBank(request_uri, &name, &content); GetBank(request_uri, &name, &content);
TemporaryFile tmpFile { WEB_TEMP_DIR}; TemporaryFile tmpFile{WEB_TEMP_DIR};
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content); PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model), content);
presetbundleWriter->WriteToFile(tmpFile.Path()); presetbundleWriter->WriteToFile(tmpFile.Path());
size_t contentLength = std::filesystem::file_size(tmpFile.Path()); size_t contentLength = std::filesystem::file_size(tmpFile.Path());
res.set(HttpField::content_type, BANK_MIME_TYPE); res.set(HttpField::content_type, BANK_MIME_TYPE);
res.set(HttpField::cache_control, "no-cache"); res.set(HttpField::cache_control, "no-cache");
res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION)); res.set(HttpField::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION));
@@ -290,12 +299,11 @@ public:
std::string content; std::string content;
GetPluginPresets(request_uri, &name, &content); GetPluginPresets(request_uri, &name, &content);
std::shared_ptr<TemporaryFile> tmpFile =std::make_shared<TemporaryFile>(WEB_TEMP_DIR); std::shared_ptr<TemporaryFile> tmpFile = std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model),content); PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePluginPresetsFile(*(this->model), content);
presetbundleWriter->WriteToFile(tmpFile->Path()); presetbundleWriter->WriteToFile(tmpFile->Path());
size_t contentLength = std::filesystem::file_size(tmpFile->Path()); size_t contentLength = std::filesystem::file_size(tmpFile->Path());
res.set(HttpField::content_type, PLUGIN_PRESETS_MIME_TYPE); res.set(HttpField::content_type, PLUGIN_PRESETS_MIME_TYPE);
res.set(HttpField::cache_control, "no-cache"); res.set(HttpField::cache_control, "no-cache");
res.setContentLength(contentLength); res.setContentLength(contentLength);
@@ -308,12 +316,11 @@ public:
std::string content; std::string content;
GetPreset(request_uri, &name, &content); GetPreset(request_uri, &name, &content);
std::shared_ptr<TemporaryFile> tmpFile =std::make_shared<TemporaryFile>(WEB_TEMP_DIR); std::shared_ptr<TemporaryFile> tmpFile = std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content); PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model), content);
presetbundleWriter->WriteToFile(tmpFile->Path()); presetbundleWriter->WriteToFile(tmpFile->Path());
size_t contentLength = std::filesystem::file_size(tmpFile->Path()); size_t contentLength = std::filesystem::file_size(tmpFile->Path());
res.set(HttpField::content_type, PRESET_MIME_TYPE); res.set(HttpField::content_type, PRESET_MIME_TYPE);
res.set(HttpField::cache_control, "no-cache"); res.set(HttpField::cache_control, "no-cache");
res.setContentLength(contentLength); res.setContentLength(contentLength);
@@ -326,13 +333,11 @@ public:
std::string content; std::string content;
GetBank(request_uri, &name, &content); GetBank(request_uri, &name, &content);
std::shared_ptr<TemporaryFile> tmpFile =std::make_shared<TemporaryFile>(WEB_TEMP_DIR); std::shared_ptr<TemporaryFile> tmpFile = std::make_shared<TemporaryFile>(WEB_TEMP_DIR);
PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model),content); PresetBundleWriter::ptr presetbundleWriter = PresetBundleWriter::CreatePresetsFile(*(this->model), content);
presetbundleWriter->WriteToFile(tmpFile->Path()); presetbundleWriter->WriteToFile(tmpFile->Path());
size_t contentLength = std::filesystem::file_size(tmpFile->Path()); size_t contentLength = std::filesystem::file_size(tmpFile->Path());
res.set(HttpField::content_type, BANK_MIME_TYPE); res.set(HttpField::content_type, BANK_MIME_TYPE);
res.set(HttpField::cache_control, "no-cache"); res.set(HttpField::cache_control, "no-cache");
res.setContentLength(contentLength); 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('/'); size_t nPos = fileName.find('/');
if (nPos != std::string::npos) { if (nPos != std::string::npos)
return fileName.substr(0,nPos); {
return fileName.substr(0, nPos);
} }
} }
if (fileNames.size() == 0) if (fileNames.size() == 0)
{ {
return 0; return 0;
} else { }
else
{
return fileNames[0]; return fileNames[0];
} }
} }
static bool HasSingleRootDirectory(ZipFileReader&zipFile) { static bool HasSingleRootDirectory(ZipFileReader &zipFile)
{
bool hasDirectory = false; bool hasDirectory = false;
std::string previousDirectory; std::string previousDirectory;
for (auto& file: zipFile.GetFiles()) for (auto &file : zipFile.GetFiles())
{ {
auto pos = file.find('/'); auto pos = file.find('/');
if (pos == std::string::npos) { if (pos == std::string::npos)
{
// a file in the root. // a file in the root.
return false; return false;
} else }
else
{ {
std::string currentRootDirectory = file.substr(0,pos); std::string currentRootDirectory = file.substr(0, pos);
if (hasDirectory) { if (hasDirectory)
{
if (currentRootDirectory != previousDirectory) if (currentRootDirectory != previousDirectory)
{ {
return false; return false;
} }
} else { }
else
{
hasDirectory = true; hasDirectory = true;
previousDirectory = currentRootDirectory; previousDirectory = currentRootDirectory;
} }
@@ -412,26 +426,26 @@ public:
{ {
PluginPresets presets; PluginPresets presets;
fs::path filePath = req.get_body_temporary_file(); fs::path filePath = req.get_body_temporary_file();
if (filePath.empty()) if (filePath.empty())
{ {
throw std::runtime_error("Unexpected."); throw std::runtime_error("Unexpected.");
} }
if (IsZipFile(filePath)) if (IsZipFile(filePath))
{ {
auto presetReader = PresetBundleReader::LoadPluginPresetsFile(*(this->model),filePath); auto presetReader = PresetBundleReader::LoadPluginPresetsFile(*(this->model), filePath);
presetReader->ExtractMediaFiles(); presetReader->ExtractMediaFiles();
std::stringstream ss(presetReader->GetPluginPresetsJson()); std::stringstream ss(presetReader->GetPluginPresetsJson());
json_reader reader(ss); json_reader reader(ss);
reader.read(&presets); reader.read(&presets);
}
} else { else
{
json_reader reader(req.get_body_input_stream()); json_reader reader(req.get_body_input_stream());
reader.read(&presets); reader.read(&presets);
} }
model->UploadPluginPresets(presets); model->UploadPluginPresets(presets);
res.set(HttpField::content_type, "application/json"); res.set(HttpField::content_type, "application/json");
res.set(HttpField::cache_control, "no-cache"); res.set(HttpField::cache_control, "no-cache");
std::stringstream sResult; std::stringstream sResult;
@@ -447,19 +461,21 @@ public:
BankFile bankFile; BankFile bankFile;
fs::path filePath = req.get_body_temporary_file(); fs::path filePath = req.get_body_temporary_file();
if (filePath.empty()) if (filePath.empty())
{ {
throw std::runtime_error("Unexpected."); throw std::runtime_error("Unexpected.");
} }
if (IsZipFile(filePath)) if (IsZipFile(filePath))
{ {
auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model),filePath); auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model), filePath);
presetReader->ExtractMediaFiles(); presetReader->ExtractMediaFiles();
std::stringstream ss(presetReader->GetPresetJson()); std::stringstream ss(presetReader->GetPresetJson());
json_reader reader(ss); json_reader reader(ss);
reader.read(&bankFile); reader.read(&bankFile);
} else { }
else
{
// legacy json format, no zip, no media files. // legacy json format, no zip, no media files.
json_reader reader(req.get_body_input_stream()); json_reader reader(req.get_body_input_stream());
reader.read(&bankFile); reader.read(&bankFile);
@@ -487,19 +503,21 @@ public:
BankFile bankFile; BankFile bankFile;
fs::path filePath = req.get_body_temporary_file(); fs::path filePath = req.get_body_temporary_file();
if (filePath.empty()) if (filePath.empty())
{ {
throw std::runtime_error("Unexpected."); throw std::runtime_error("Unexpected.");
} }
if (IsZipFile(filePath)) if (IsZipFile(filePath))
{ {
auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model),filePath); auto presetReader = PresetBundleReader::LoadPresetsFile(*(this->model), filePath);
presetReader->ExtractMediaFiles(); presetReader->ExtractMediaFiles();
std::stringstream ss(presetReader->GetPresetJson()); std::stringstream ss(presetReader->GetPresetJson());
json_reader reader(ss); json_reader reader(ss);
reader.read(&bankFile); reader.read(&bankFile);
} else { }
else
{
// legacy json format, no zip, no media files. // legacy json format, no zip, no media files.
json_reader reader(req.get_body_input_stream()); json_reader reader(req.get_body_input_stream());
reader.read(&bankFile); reader.read(&bankFile);
@@ -522,70 +540,88 @@ public:
res.setContentLength(result.length()); res.setContentLength(result.length());
res.setBody(result); res.setBody(result);
} else if (segment == "uploadUserFile") }
else if (segment == "uploadUserFile")
{ {
res.set(HttpField::content_type, "application/json"); UserUploadResponse result;
res.set(HttpField::cache_control, "no-cache"); try
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)
{ {
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"); if (patchProperty.length() == 0 && directory.length() == 0)
res.set(HttpField::cache_control, "no-cache"); {
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")) fs::path outputFileName = std::filesystem::path(directory) / filename;
{
ExtensionChecker extensionChecker { request_uri.query("ext") };
namespace fs = std::filesystem;
try { if (filename.ends_with(".zip"))
auto zipFile = ZipFileReader::Create(req.get_body_temporary_file()); {
std::vector<std::string> files = zipFile->GetFiles(); ExtensionChecker extensionChecker{request_uri.query("ext")};
bool hasSingleRootDirectory = HasSingleRootDirectory(*zipFile); namespace fs = std::filesystem;
if (!hasSingleRootDirectory) {
directory = (fs::path(directory) / fs::path(filename).filename().replace_extension("")).string(); try
}
for (const auto&inputFile : files)
{ {
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}; directory = (fs::path(directory) / fs::path(filename).filename().replace_extension("")).string();
std::string extension = inputPath.extension(); }
if (extensionChecker.IsValidExtension(extension)) for (const auto &inputFile : files)
{
if (!inputFile.ends_with("/")) // don't process directory entries.
{ {
auto si = zipFile->GetFileInputStream(inputFile); fs::path inputPath{inputFile};
std::string path = this->model->UploadUserFile(directory,patchProperty,inputFile, si,zipFile->GetFileSize(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. catch (const std::exception &e)
// almost always a single folder in the root. {
std::string returnPath = GetFirstFolderOrFile(files); Lv2Log::error(SS("Unzip failed. " << e.what()));
outputFileName = this->model->GetPluginUploadDirectory() / directory / returnPath; throw;
} catch (const std::exception &e) }
{ FileSystemSync();
Lv2Log::error(SS("Unzip failed. " << e.what())); }
throw; else
{
outputFileName = this->model->UploadUserFile(directory, patchProperty, filename, req.get_body_input_stream(), req.content_length());
} }
FileSystemSync();
} else { if (outputFileName.is_relative())
outputFileName = this->model->UploadUserFile(directory,patchProperty,filename,req.get_body_input_stream(), req.content_length()); {
outputFileName = this->model->GetPluginUploadDirectory() / outputFileName;
}
result.path_ = outputFileName.string();
}
catch (const std::exception &e)
{
result.errorMessage_ = e.what();
} }
std::stringstream ss; std::stringstream ss;
json_writer writer(ss); json_writer writer(ss);
writer.write(outputFileName); writer.write(result);
std::string response = ss.str(); std::string response = ss.str();
res.setContentLength(response.length()); res.setContentLength(response.length());
@@ -598,7 +634,7 @@ public:
} }
catch (const std::exception &e) 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) if (strcmp(e.what(), "Not found") == 0)
{ {
ec = boost::system::errc::make_error_code(boost::system::errc::no_such_file_or_directory); 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) static std::string StripPortNumber(const std::string &fromAddress)
{ {
std::string address = fromAddress; std::string address = fromAddress;
@@ -663,10 +698,10 @@ public:
std::stringstream s; std::stringstream s;
s << "{ \"socket_server_port\": " << portNumber s << "{ \"socket_server_port\": " << portNumber
<< ", \"socket_server_address\": \"" << webSocketAddress << ", \"socket_server_address\": \"" << webSocketAddress
<< "\", \"ui_plugins\": [ ], \"max_upload_size\": " << maxUploadSize << "\", \"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(); return s.str();
} }
@@ -720,16 +755,14 @@ public:
}; };
void pipedal::ConfigureWebServer( void pipedal::ConfigureWebServer(
WebServer&server, WebServer &server,
PiPedalModel&model, PiPedalModel &model,
int port, int port,
size_t maxUploadSize) size_t maxUploadSize)
{ {
std::shared_ptr<RequestHandler> interceptConfig{new InterceptConfig(port, maxUploadSize)}; std::shared_ptr<RequestHandler> interceptConfig{new InterceptConfig(port, maxUploadSize)};
server.AddRequestHandler(interceptConfig); server.AddRequestHandler(interceptConfig);
std::shared_ptr<DownloadIntercept> downloadIntercept = std::make_shared<DownloadIntercept>(&model);
server.AddRequestHandler(downloadIntercept);
std::shared_ptr<DownloadIntercept> downloadIntercept = std::make_shared<DownloadIntercept>(&model);
server.AddRequestHandler(downloadIntercept);
} }