diff --git a/react/src/App.tsx b/react/src/App.tsx index 754b717..f45ba9f 100644 --- a/react/src/App.tsx +++ b/react/src/App.tsx @@ -704,7 +704,7 @@ const App = withStyles(appStyles)(class extends ResizeResponsiveComponent - { this.handleDrawerAboutClick() }}> + { this.handleDrawerAboutClick() }}> diff --git a/react/src/BankDialog.tsx b/react/src/BankDialog.tsx index d538950..be6e51d 100644 --- a/react/src/BankDialog.tsx +++ b/react/src/BankDialog.tsx @@ -12,6 +12,7 @@ import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import { Theme, withStyles, WithStyles, createStyles } from '@material-ui/core/styles'; import DraggableGrid, { ScrollDirection } from './DraggableGrid'; +import Fade from '@material-ui/core/Fade'; import SelectHoverBackground from './SelectHoverBackground'; import CloseIcon from '@material-ui/icons/Close'; @@ -19,6 +20,15 @@ import ArrowBackIcon from '@material-ui/icons/ArrowBack'; import EditIcon from '@material-ui/icons/Edit'; import RenameDialog from './RenameDialog'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import DialogEx from './DialogEx'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogActions from '@material-ui/core/DialogActions'; + interface BankDialogProps extends WithStyles { show: boolean; isEditDialog: boolean; @@ -35,6 +45,8 @@ interface BankDialogState { filenameDialogOpen: boolean; filenameSaveAs: boolean; + moreMenuAnchorEl: HTMLElement | null; + showDeletePrompt: boolean; }; @@ -101,10 +113,12 @@ const BankDialog = withStyles(styles, { withTheme: true })( model: PiPedalModel; - + refUpload: React.RefObject; constructor(props: BankDialogProps) { super(props); + + this.refUpload = React.createRef(); this.model = PiPedalModelFactory.getInstance(); this.handleDialogClose = this.handleDialogClose.bind(this); @@ -114,17 +128,65 @@ const BankDialog = withStyles(styles, { withTheme: true })( showActionBar: false, selectedItem: banks.selectedBank, filenameDialogOpen: false, - filenameSaveAs: false + filenameSaveAs: false, + moreMenuAnchorEl: null, + showDeletePrompt: false }; this.handleBanksChanged = this.handleBanksChanged.bind(this); this.handlePopState = this.handlePopState.bind(this); } + onMoreClick(e: React.SyntheticEvent): void { + this.setState({ moreMenuAnchorEl: e.currentTarget as HTMLElement }) + } - selectItemAtIndex(index: number) - { + handleDownloadBank() { + this.handleMoreClose(); + this.model.download("downloadBank", this.state.selectedItem); + } + + async uploadFiles(fileList: FileList): Promise { + let uploadAfter = this.state.selectedItem; + try { + for (let i = 0; i < fileList.length; ++i) { + uploadAfter = await this.model.uploadBank(fileList[i], uploadAfter); + } + } catch (error) { + this.model.showAlert(error); + }; + return uploadAfter; + } + + handleUpload(e: any) { + if (!e.target.files) return; + if (e.target.files.length === 0) return; + var fileList = e.target.files; + this.uploadFiles(fileList) + .then((newSelection) => { + e.target.value = ""; // clear the file list so we can get another change notice. + this.setState({ selectedItem: newSelection }); + }) + .catch(err => { + e.target.value = ""; // clear the file list so we can get another change notice. + this.model.showAlert(err); + }); + } + handleUploadBank() { + if (this.refUpload.current) { + this.refUpload.current.click(); + } + this.handleMoreClose(); + + } + handleMoreClose(): void { + this.setState({ moreMenuAnchorEl: null }); + + } + + + selectItemAtIndex(index: number) { let instanceId = this.state.banks.entries[index].instanceId; - this.setState({selectedItem: instanceId}); + this.setState({ selectedItem: instanceId }); } isEditMode() { return this.state.showActionBar || this.props.isEditDialog; @@ -133,46 +195,41 @@ const BankDialog = withStyles(styles, { withTheme: true })( handleBanksChanged() { let banks = this.model.banks.get(); - if (!banks.areEqual(this.state.banks,false)) // avoid a bunch of peculiar effects if we update while a drag is in progress + if (!banks.areEqual(this.state.banks, false)) // avoid a bunch of peculiar effects if we update while a drag is in progress { - // if we don't have a valid selection, then use the current preset. - if (this.state.banks.getEntry(this.state.selectedItem) == null) - { - this.setState({ banks: banks, selectedItem: banks.selectedBank}); + // if we don't have a valid selection, then use the current bank. + if (this.state.banks.getEntry(this.state.selectedItem) == null) { + this.setState({ banks: banks, selectedItem: banks.selectedBank }); } else { this.setState({ banks: banks }); } - } + } } mounted: boolean = false; hasHooks: boolean = false; - + stateWasPopped: boolean = false; handlePopState(e: any): any { let state: any = e.state; - if (!state || !state.bankDialog) - { + if (!state || !state.bankDialog) { this.stateWasPopped = true; this.props.onDialogClose(); } } - - updateBackButtonHooks() : void { + + updateBackButtonHooks(): void { let wantHooks = this.mounted && this.props.show; - if (wantHooks !== this.hasHooks) - { + if (wantHooks !== this.hasHooks) { this.hasHooks = wantHooks; - - if (this.hasHooks) - { + + if (this.hasHooks) { this.stateWasPopped = false; - window.addEventListener("popstate",this.handlePopState); + window.addEventListener("popstate", this.handlePopState); // eslint-disable-next-line no-restricted-globals let newState: any = history.state; - if (!newState) - { + if (!newState) { newState = {}; } newState.bankDialog = true; @@ -183,22 +240,20 @@ const BankDialog = withStyles(styles, { withTheme: true })( "#Banks" ); } else { - window.removeEventListener("popstate",this.handlePopState); - if (!this.stateWasPopped) - { + window.removeEventListener("popstate", this.handlePopState); + if (!this.stateWasPopped) { // eslint-disable-next-line no-restricted-globals history.back(); } // eslint-disable-next-line no-restricted-globals - history.replaceState({},"PiPedal","#"); + history.replaceState({}, "PiPedal", "#"); } - + } } - - - componentDidUpdate() - { + + + componentDidUpdate() { this.updateBackButtonHooks(); } componentDidMount() { @@ -215,31 +270,38 @@ const BankDialog = withStyles(styles, { withTheme: true })( } getSelectedIndex() { - let instanceId = this.isEditMode() ? this.state.selectedItem: this.state.banks.selectedBank; + let instanceId = this.isEditMode() ? this.state.selectedItem : this.state.banks.selectedBank; let banks = this.state.banks; for (let i = 0; i < banks.entries.length; ++i) { if (banks.entries[i].instanceId === instanceId) return i; } - return -1; + return -1; } handleDeleteClick() { if (!this.state.selectedItem) return; + this.setState({ showDeletePrompt: true }); + } + handleDeletePromptClose() { + this.setState({ showDeletePrompt: false }); + } + handleDeletePromptOk() { + this.handleDeletePromptClose(); + let selectedItem = this.state.selectedItem; if (selectedItem !== -1) { this.model.deleteBankItem(selectedItem) - .then((newSelection: number) => { - this.setState({ - selectedItem: newSelection - }); - }) - .catch((error) => { + .then((newSelection: number) => { + this.setState({ + selectedItem: newSelection + }); + }) + .catch((error) => { this.model.showAlert(error); }); } } - handleDialogClose() - { + handleDialogClose() { this.props.onDialogClose(); } @@ -258,23 +320,23 @@ const BankDialog = withStyles(styles, { withTheme: true })( mapElement(el: any): React.ReactNode { - let presetEntry = el as BankIndexEntry; + let bankEntry = el as BankIndexEntry; let classes = this.props.classes; let selectedItem = this.isEditMode() ? this.state.selectedItem : this.state.banks.selectedBank; return ( -
+
this.handleItemClick(presetEntry.instanceId)} + onClick={() => this.handleItemClick(bankEntry.instanceId)} > - +
- {presetEntry.name} + {bankEntry.name}
@@ -292,9 +354,9 @@ const BankDialog = withStyles(styles, { withTheme: true })( selectedItem: newBanks.entries[to].instanceId }); this.model.moveBank(from, to) - .catch((error)=> { - this.model.showAlert(error); - }); + .catch((error) => { + this.model.showAlert(error); + }); } getSelectedName(): string { @@ -313,8 +375,7 @@ const BankDialog = withStyles(styles, { withTheme: true })( let item = this.state.banks.getEntry(this.state.selectedItem); if (!item) return; if (item.name !== text) { - if (this.state.banks.hasName(text)) - { + if (this.state.banks.hasName(text)) { this.model.showAlert("A bank with that name already exists."); } this.model.renameBank(this.state.selectedItem, text) @@ -327,17 +388,15 @@ const BankDialog = withStyles(styles, { withTheme: true })( } handleSaveAsOk(text: string) { let item = this.state.banks.getEntry(this.state.selectedItem); - if (item) - { + if (item) { if (item.name !== text) { - if (this.state.banks.hasName(text)) - { + if (this.state.banks.hasName(text)) { this.model.showAlert("A bank with that name already exists."); return; } this.model.saveBankAs(this.state.selectedItem, text) .then((newSelection) => { - this.setState({selectedItem: newSelection}); + this.setState({ selectedItem: newSelection }); }) .catch((error) => { this.onError(error); @@ -358,6 +417,14 @@ const BankDialog = withStyles(styles, { withTheme: true })( this.model?.showAlert(error); } + getSelectedBankName() { + try { + return this.model.banks.get().getEntry(this.state.selectedItem)!.name; + } catch (error) { + return ""; + } + } + render() { @@ -368,7 +435,7 @@ const BankDialog = withStyles(styles, { withTheme: true })( return ( { this.handleDialogClose() }} TransitionComponent={Transition}> + onClose={() => { this.handleDialogClose() }} TransitionComponent={Transition}>
@@ -414,14 +481,13 @@ const BankDialog = withStyles(styles, { withTheme: true })( - { this.setState({ filenameDialogOpen: false }) }} onOk={(text: string) => { - if (this.state.filenameSaveAs) - { + if (this.state.filenameSaveAs) { this.handleSaveAsOk(text); } else { this.handleRenameOk(text); @@ -430,8 +496,43 @@ const BankDialog = withStyles(styles, { withTheme: true })( } /> this.handleDeleteClick()} > - Delete + Delete + { this.onMoreClick(e) }} > + + + this.handleMoreClose()} + TransitionComponent={Fade} + > + { this.handleDownloadBank(); }} > + + + + + Download bank + + + + + +
) } @@ -443,20 +544,44 @@ const BankDialog = withStyles(styles, { withTheme: true })( this.showActionBar(true)} canDrag={this.isEditMode()} - onDragStart={(index,x,y)=> {this.selectItemAtIndex(index) } } + onDragStart={(index, x, y) => { this.selectItemAtIndex(index) }} moveElement={(from, to) => { this.moveElement(from, to); }} scroll={ScrollDirection.Y} defaultSelectedIndex={defaultSelectedIndex} - > + > { - this.state.banks.entries.map((element) => - { + this.state.banks.entries.map((element) => { return this.mapElement(element); }) }
+ this.handleUpload(e)} + style={{ display: "none" }} + /> + + this.handleDeletePromptClose()}> + + Are you sure you want to delete bank '{this.getSelectedBankName()}'? + + + + + + + + ); diff --git a/react/src/PiPedalModel.tsx b/react/src/PiPedalModel.tsx index ff935cc..29c899b 100644 --- a/react/src/PiPedalModel.tsx +++ b/react/src/PiPedalModel.tsx @@ -323,6 +323,7 @@ export interface PiPedalModel { download(targetType: string, isntanceId: number): void; uploadPreset(file: File, uploadAfter: number): Promise; + uploadBank(file: File, uploadAfter: number): Promise; setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise; @@ -1457,6 +1458,48 @@ class PiPedalModelImpl implements PiPedalModel { }); return result; } + uploadBank(file: File, uploadAfter: number): Promise { + let result = new Promise((resolve, reject) => { + try { + console.log("File: " + file.name + " Size: " + file.size); + if (file.size > this.maxUploadSize) { + reject("File is too large."); + } + let url = this.varServerUrl + "uploadBank"; + if (uploadAfter && uploadAfter !== -1) { + url += "?uploadAfter=" + uploadAfter; + } + fetch( + url, + { + method: "POST", + body: file, + headers: { + 'Content-Type': 'application/json', + }, + } + ) + .then((response: Response) => { + if (!response.ok) { + reject("Upload failed. " + response.statusText); + return; + } else { + return response.json(); // read the empty body to keep the connection alive. + } + }) + .then((json) => { + resolve(json as number); + }) + .catch((error) => { + reject("Upload failed. " + error); + }) + ; + } catch (error) { + reject("Upload failed. " + error); + } + }); + return result; + } setWifiConfigSettings(wifiConfigSettings: WifiConfigSettings): Promise { let result = new Promise((resolve, reject) => { let oldSettings = this.wifiConfigSettings.get(); diff --git a/react/src/PresetDialog.tsx b/react/src/PresetDialog.tsx index 71c364b..cf92be1 100644 --- a/react/src/PresetDialog.tsx +++ b/react/src/PresetDialog.tsx @@ -145,7 +145,7 @@ const PresetDialog = withStyles(styles, { withTheme: true })( handleDownloadPreset() { this.handleMoreClose(); - this.model.download("downloadPreset", this.model.presets.get().selectedInstanceId); + this.model.download("downloadPreset", this.state.selectedItem); } handleUploadPreset() { this.handleMoreClose(); diff --git a/src/Banks.hpp b/src/Banks.hpp index 160ef73..d03c9aa 100644 --- a/src/Banks.hpp +++ b/src/Banks.hpp @@ -93,6 +93,16 @@ public: presets_.erase(presets_.begin()+from); presets_.insert(presets_.begin()+to,std::move(t)); } + void updateNextIndex() { + int64_t t = 0; + for (size_t i = 0; i < this->presets_.size(); ++i) + { + int64_t instanceId = this->presets_[i]->instanceId(); + if (instanceId > t) t = instanceId; + + } + this->nextInstanceId_ = t; + } int64_t addPreset(const PedalBoard&preset, int64_t afterItem = -1) { if (hasName(preset.name())) @@ -221,6 +231,14 @@ public: DECLARE_JSON_MAP(BankIndex); + bool hasName(const std::string&name) const { + for (size_t i = 0; i < this->entries_.size(); ++i) + { + if (this->entries_[i].name() == name) return true; + } + return false; + } + void move(size_t from, size_t to) { if (from >= this->entries_.size()) { diff --git a/src/BeastServer.cpp b/src/BeastServer.cpp index bc99de9..a3d77c8 100644 --- a/src/BeastServer.cpp +++ b/src/BeastServer.cpp @@ -325,7 +325,7 @@ void handle_request( { return send(server_error(ec.message())); } - req.prepare_payload(); + res.prepare_payload(); return send(std::move(res)); } else if (req.method() == http::verb::post) @@ -341,7 +341,7 @@ void handle_request( res.set(http::field::date, HtmlHelper::timeToHttpDate(time(nullptr))); res.set(http::field::access_control_allow_origin, req[http::field::origin]); requestHandler->post_response(request_uri, req, res, ec); - res.keep_alive(req.keep_alive()); + res.keep_alive(false); // bug in Beast Server -- posted body not reset. if (ec == boost::system::errc::no_such_file_or_directory) return send(not_found(req.target())); @@ -349,7 +349,7 @@ void handle_request( { return send(server_error(ec.message())); } - req.prepare_payload(); + res.prepare_payload(); return send(std::move(res)); } return send(bad_request("Unknown HTTP-method")); diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index 10341fa..2a8c952 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -313,6 +313,11 @@ PedalBoard PiPedalModel::getPreset(int64_t instanceId) std::lock_guard guard(mutex); return this->storage.GetPreset(instanceId); } +void PiPedalModel::getBank(int64_t instanceId, BankFile*pResult) +{ + std::lock_guard guard(mutex); + this->storage.GetBankFile(instanceId, pResult); +} void PiPedalModel::setPresetChanged(int64_t clientId, bool value) { @@ -372,6 +377,14 @@ int64_t PiPedalModel::uploadPreset(const BankFile &bankFile, int64_t uploadAfter firePresetsChanged(-1); return newPreset; } +int64_t PiPedalModel::uploadBank(BankFile &bankFile, int64_t uploadAfter) +{ + std::lock_guard(this->mutex); + + int64_t newPreset = this->storage.UploadBank(bankFile, uploadAfter); + fireBanksChanged(-1); + return newPreset; +} void PiPedalModel::loadPreset(int64_t clientId, int64_t instanceId) { diff --git a/src/PiPedalModel.hpp b/src/PiPedalModel.hpp index 364aad3..005a45c 100644 --- a/src/PiPedalModel.hpp +++ b/src/PiPedalModel.hpp @@ -126,6 +126,9 @@ public: void getPresets(PresetIndex*pResult); PedalBoard getPreset(int64_t instanceId); + void getBank(int64_t instanceId, BankFile*pBank); + + int64_t uploadBank(BankFile&bankFile, int64_t uploadAfter = -1); int64_t uploadPreset(const BankFile&bankFile, int64_t uploadAfter = -1); void saveCurrentPreset(int64_t clientId); int64_t saveCurrentPresetAs(int64_t clientId, const std::string& name,int64_t saveAfterInstanceId = -1); diff --git a/src/Storage.cpp b/src/Storage.cpp index b7742b9..8581d62 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -163,6 +163,7 @@ void Storage::Initialize() LoadWifiConfigSettings(); } + void Storage::LoadBank(int64_t instanceId) { auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId); @@ -190,11 +191,11 @@ std::filesystem::path Storage::GetChannelSelectionFileName() { return this->dataRoot / "JackChannelSelection.json"; } -std::filesystem::path Storage::GetIndexFileName() +std::filesystem::path Storage::GetIndexFileName() const { return this->GetPresetsDirectory() / BANKS_FILENAME; } -std::filesystem::path Storage::GetBankFileName(const std::string &name) +std::filesystem::path Storage::GetBankFileName(const std::string &name) const { std::string fileName = SafeEncodeName(name) + BANK_EXTENSION; return this->GetPresetsDirectory() / fileName; @@ -274,6 +275,16 @@ void Storage::CreateBank(const std::string &name) this->SaveBankIndex(); } +void Storage::GetBankFile(int64_t instanceId,BankFile *pBank) const { + auto indexEntry = this->bankIndex.getBankIndexEntry(instanceId); + auto name = indexEntry.name(); + std::filesystem::path fileName = GetBankFileName(name); + std::ifstream is(fileName); + json_reader reader(is); + reader.read(pBank); + pBank->name(indexEntry.name()); +} + void Storage::LoadBankFile(const std::string &name, BankFile *pBank) { std::filesystem::path fileName = GetBankFileName(name); @@ -400,6 +411,7 @@ void Storage::GetPresetIndex(PresetIndex *pResult) pResult->presets().push_back(entry); } } + PedalBoard Storage::GetPreset(int64_t instanceId) const { for (size_t i = 0; i < currentBank.presets().size(); ++i) @@ -687,6 +699,40 @@ int64_t Storage::UploadPreset(const BankFile&bankFile,int64_t uploadAfter) this->SaveCurrentBank(); return lastPreset; } +int64_t Storage::UploadBank(BankFile&bankFile,int64_t uploadAfter) +{ + int64_t lastBank = this->bankIndex.selectedBank(); + if (uploadAfter != -1) + { + lastBank = uploadAfter; + } + + if (bankFile.presets().size() == 0) { + throw PiPedalException("Invalid bank."); + } + bankFile.updateNextIndex(); + + int n = 2; + std::string baseName = bankFile.name(); + while (this->bankIndex.hasName(bankFile.name())) + { + std::stringstream s; + s << baseName << "(" << n++ << ")"; + bankFile.name(s.str()); + } + std::filesystem::path path = this->GetBankFileName(bankFile.name()); + std::ofstream f(path); + if (!f.is_open()) { + throw PiPedalException("Can't write to bank file."); + } + json_writer writer(f); + writer.write(bankFile); + + lastBank = this->bankIndex.addBank(lastBank,bankFile.name()); + this->SaveBankIndex(); + return lastBank; +} + void Storage::SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSettings) { diff --git a/src/Storage.hpp b/src/Storage.hpp index 47e13a3..4bb4456 100644 --- a/src/Storage.hpp +++ b/src/Storage.hpp @@ -23,8 +23,8 @@ private: static std::string SafeEncodeName(const std::string& name); static std::string SafeDecodeName(const std::string& name); std::filesystem::path GetPresetsDirectory() const; - std::filesystem::path GetIndexFileName(); - std::filesystem::path GetBankFileName(const std::string & name); + std::filesystem::path GetIndexFileName() const; + std::filesystem::path GetBankFileName(const std::string & name) const; std::filesystem::path GetChannelSelectionFileName(); void LoadBankIndex(); @@ -62,7 +62,9 @@ public: void GetPresetIndex(PresetIndex*pResult); void SetPresetIndex(const PresetIndex &presetIndex); PedalBoard GetPreset(int64_t instanceId) const; + void GetBankFile(int64_t instanceId,BankFile*pResult) const; int64_t UploadPreset(const BankFile&bankFile, int64_t uploadAfter); + int64_t UploadBank(BankFile&bankFile, int64_t uploadAfter); bool LoadPreset(int64_t presetId); diff --git a/src/main.cpp b/src/main.cpp index 25a54c9..59c3b68 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -62,8 +62,16 @@ public: std::string strInstanceId = request_uri.query("id"); if (strInstanceId != "") return true; + } else if (request_uri.segment(1) == "uploadPreset") + { + return true; } - if (request_uri.segment(1) == "uploadPreset") + if (request_uri.segment(1) == "downloadBank") + { + std::string strInstanceId = request_uri.query("id"); + if (strInstanceId != "") + return true; + } else if (request_uri.segment(1) == "uploadBank") { return true; } @@ -99,6 +107,20 @@ public: *pContent = s.str(); *pName = pedalBoard.name(); } + void GetBank(const uri &request_uri, std::string *pName, std::string *pContent) + { + std::string strInstanceId = request_uri.query("id"); + int64_t instanceId = std::stol(strInstanceId); + BankFile bank; + model->getBank(instanceId,&bank); + + + std::stringstream s; + json_writer writer(s,true); // do what we can to reduce the file size. + writer.write(bank); + *pContent = s.str(); + *pName = bank.name(); + } virtual void head_response( const uri &request_uri, @@ -120,6 +142,18 @@ public: res.set(http::field::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION)); return; } + if (request_uri.segment(1) == "downloadBank") + { + std::string name; + std::string content; + GetBank(request_uri, &name, &content); + + res.set(http::field::content_type, "application/octet-stream"); + res.set(http::field::cache_control, "no-cache"); + res.set(http::field::content_length, content.length()); + res.set(http::field::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION)); + return; + } throw PiPedalException("Not found."); } catch (const std::exception &e) @@ -154,7 +188,20 @@ public: res.set(http::field::content_length, content.length()); res.set(http::field::content_disposition, GetContentDispositionHeader(name, PRESET_EXTENSION)); res.body() = content; - } + } else if (request_uri.segment(1) == "downloadBank") + { + std::string name; + std::string content; + GetBank(request_uri, &name, &content); + + res.set(http::field::content_type, "application/octet-stream"); + res.set(http::field::cache_control, "no-cache"); + res.set(http::field::content_length, content.length()); + res.set(http::field::content_disposition, GetContentDispositionHeader(name, BANK_EXTENSION)); + res.body() = content; + } else { + throw PiPedalException("Not found"); + } } catch (const std::exception &e) { @@ -202,6 +249,36 @@ public: res.set(http::field::content_length, result.length()); res.body() = result; + } + else if (request_uri.segment(1) == "uploadBank" ) + { + std::string presetBody = req.body(); + std::stringstream s(presetBody); + json_reader reader(s); + + uint64_t uploadAfter = -1; + std::string strUploadAfter = request_uri.query("uploadAfter"); + if (strUploadAfter.length() != 0) + { + uploadAfter = std::stol(strUploadAfter); + } + + BankFile bankFile; + reader.read(&bankFile); + + uint64_t instanceId = model->uploadBank(bankFile,uploadAfter); + + res.set(http::field::content_type, "application/json"); + res.set(http::field::cache_control, "no-cache"); + std::stringstream sResult; + sResult << instanceId; + std::string result = sResult.str(); + res.set(http::field::content_length, result.length()); + + res.body() = result; + } + else { + throw PiPedalException("Not found"); } } catch (const std::exception &e)