diff --git a/src/ConfigMain.cpp b/src/ConfigMain.cpp index 33b33da..4231899 100644 --- a/src/ConfigMain.cpp +++ b/src/ConfigMain.cpp @@ -107,15 +107,18 @@ void DisableService() } } -void StopService() +void StopService(bool excludeShutdownService = false) { if (SysExec(SYSTEMCTL_BIN " stop " NATIVE_SERVICE ".service") != EXIT_SUCCESS) { cout << "Error: Failed to stop the " NATIVE_SERVICE " service."; } - if (SysExec(SYSTEMCTL_BIN " stop " SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS) + if (!excludeShutdownService) { - cout << "Error: Failed to stop the " SHUTDOWN_SERVICE " service."; + if (SysExec(SYSTEMCTL_BIN " stop " SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS) + { + cout << "Error: Failed to stop the " SHUTDOWN_SERVICE " service."; + } } if (SysExec(SYSTEMCTL_BIN " stop " JACK_SERVICE ".service") != EXIT_SUCCESS) { @@ -123,20 +126,24 @@ void StopService() } } -void Uninstall() { +void Uninstall() +{ StopService(); DisableService(); system(SYSTEMCTL_BIN " stop jack"); system(SYSTEMCTL_BIN " disable jack"); } -void StartService() +void StartService(bool excludeShutdownService = false) { SilentSysExec("/usr/bin/pulseaudio --kill"); // interferes with Jack audio service startup. - if (SysExec(SYSTEMCTL_BIN " start " SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS) + if (!excludeShutdownService) { - throw PiPedalException("Failed to start the " SHUTDOWN_SERVICE " service."); + if (SysExec(SYSTEMCTL_BIN " start " SHUTDOWN_SERVICE ".service") != EXIT_SUCCESS) + { + throw PiPedalException("Failed to start the " SHUTDOWN_SERVICE " service."); + } } if (SysExec(SYSTEMCTL_BIN " start " NATIVE_SERVICE ".service") != EXIT_SUCCESS) { @@ -152,11 +159,11 @@ void StartService() } } } -void RestartService() +void RestartService(bool excludeShutdownService) { - StopService(); - StartService(); + StopService(excludeShutdownService); + StartService(excludeShutdownService); } static bool userExists(const char *userName) @@ -202,16 +209,18 @@ void InstallLimits() output << "@audio - rtprio 95" << endl; output << "@audio - memlock unlimited" << endl; } - - } -void MaybeStartJackService() { +void MaybeStartJackService() +{ std::filesystem::path drcFile = "/etc/jackdrc"; - if (std::filesystem::exists(drcFile) && std::filesystem::file_size(drcFile) != 0) { + if (std::filesystem::exists(drcFile) && std::filesystem::file_size(drcFile) != 0) + { system(SYSTEMCTL_BIN " start jack"); - } else { + } + else + { system(SYSTEMCTL_BIN " mask jack"); } } @@ -237,16 +246,13 @@ void InstallJackService() SysExec(USERMOD_BIN " -a -G" AUDIO_SERVICE_GROUP_NAME " " JACK_SERVICE_ACCOUNT_NAME); // deploy the systemd service file - std::map map; // nothing to customize. + std::map map; // nothing to customize. WriteTemplateFile(map, std::filesystem::path("/etc/pipedal/templateJack.service"), GetServiceFileName(JACK_SERVICE)); - MaybeStartJackService(); - } - void Install(const std::filesystem::path &programPrefix, const std::string endpointAddress) { @@ -391,7 +397,7 @@ void Install(const std::filesystem::path &programPrefix, const std::string endpo SysExec(SYSTEMCTL_BIN " daemon-reload"); cout << "Starting service" << endl; - RestartService(); + RestartService(false); EnableService(); cout << "Complete" << endl; @@ -426,6 +432,7 @@ int main(int argc, char **argv) bool enable = false, disable = false, restart = false; bool enable_ap = false, disable_ap = false; bool nopkexec = false; + bool excludeShutdownService = false; std::string prefixOption; std::string portOption; @@ -443,6 +450,7 @@ int main(int argc, char **argv) parser.AddOption("--port", &portOption); parser.AddOption("--enable_ap", &enable_ap); parser.AddOption("--disable_ap", &disable_ap); + parser.AddOption("--excludeShutdownService", &excludeShutdownService); // private (unstable) option used by shutdown service. try { parser.Parse(argc, (const char **)argv); @@ -540,30 +548,18 @@ int main(int argc, char **argv) auto uid = getuid(); if (uid != 0 && !nopkexec) { - // re-execute with PKEXEC in order to prompt form SUDO credentials. + // re-execute with PKEXEC in order to prompt for SUDO credentials once only. std::vector args; std::string pkexec = "/usr/bin/pkexec"; // staged because "ISO C++ forbids converting a string constant to std::vector::value_type"(!) args.push_back((char *)(pkexec.c_str())); - std::filesystem::path path = std::filesystem::absolute(argv[0]); - std::string sPath = path; - args.push_back((char *)path.c_str()); + std::string sPath = GetSelfExePath(); + args.push_back(const_cast(sPath.c_str())); for (int arg = 1; arg < argc; ++arg) { - args.push_back((char *)argv[arg]); + args.push_back(const_cast(argv[arg])); } - std::string prefixArg; // lifetime management for the prefix arguments - std::string prefixOptionArg; - - if (prefixOption.length() == 0) - { - std::filesystem::path prefix = std::filesystem::path(argv[0]).parent_path().parent_path(); - prefixOptionArg = "-prefix"; - args.push_back((char *)(prefixOptionArg.c_str())); - prefixArg = prefix; - args.push_back((char *)prefixArg.c_str()); - } args.push_back(nullptr); char **rawArgv = &args[0]; @@ -616,7 +612,7 @@ int main(int argc, char **argv) } else if (restart) { - RestartService(); + RestartService(excludeShutdownService); } else if (enable) { diff --git a/src/PiPedalModel.cpp b/src/PiPedalModel.cpp index e6a03f1..3b4210b 100644 --- a/src/PiPedalModel.cpp +++ b/src/PiPedalModel.cpp @@ -76,6 +76,11 @@ void PiPedalModel::Close() PiPedalModel::~PiPedalModel() { + CurrentPreset currentPreset; + currentPreset.modified_ = this->hasPresetChanged; + currentPreset.preset_ = this->pedalBoard; + storage.SaveCurrentPreset(currentPreset); + if (jackHost) { jackHost->Close(); @@ -109,7 +114,15 @@ void PiPedalModel::Load(const PiPedalConfiguration &configuration) } lv2Host.Load(configuration.GetLv2Path().c_str()); - this->pedalBoard = storage.GetCurrentPreset(); + this->pedalBoard = storage.GetCurrentPreset(); // the current *saved* preset. + + // the current edited preset, saved only across orderly shutdowns. + CurrentPreset currentPreset; + if (storage.RestoreCurrentPreset(¤tPreset)) + { + this->pedalBoard = currentPreset.preset_; + this->hasPresetChanged = currentPreset.modified_; + } updateDefaults(&this->pedalBoard); std::unique_ptr p{JackHost::CreateInstance(lv2Host.asIHost())}; @@ -373,6 +386,7 @@ void PiPedalModel::firePresetsChanged(int64_t clientId) } } + void PiPedalModel::saveCurrentPreset(int64_t clientId) { std::lock_guard(this->mutex); diff --git a/src/ShutdownMain.cpp b/src/ShutdownMain.cpp index 8c7048e..fa40e29 100644 --- a/src/ShutdownMain.cpp +++ b/src/ShutdownMain.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include "JackServerSettings.hpp" #include @@ -38,6 +39,7 @@ #include "WifiConfigSettings.hpp" #include "SetWifiConfig.hpp" #include "SysExec.hpp" +#include using namespace std; @@ -48,6 +50,8 @@ using ip::tcp; const size_t MAX_LENGTH = 128; + + static bool startsWith(const std::string&s, const char*text) { if (s.length() < strlen(text)) return false; @@ -79,12 +83,24 @@ static void ReleaseAccessPoint(const std::string gatewayAddress) { } +void delayedRestartProc() +{ + sleep(1); // give a chance for websocket messages to propagate. + std::string pipedalConfigPath = std::filesystem::path(GetSelfExePath()).parent_path() / "pipedalConfig"; + + std::stringstream s; + s << pipedalConfigPath.c_str() << " --restart --excludeShutdownService"; + ::system(s.str().c_str()); +} bool setJackConfiguration(JackServerSettings serverSettings) { bool success = true; serverSettings.Write(); + + std::thread delayedRestartThread(delayedRestartProc); + delayedRestartThread.detach(); return true; @@ -319,9 +335,9 @@ void runServer(uint16_t port) } } + int main(int argc, char **argv) { - Lv2Log::set_logger(MakeLv2SystemdLogger()); CommandLineParser parser; diff --git a/src/Storage.cpp b/src/Storage.cpp index 1611c4f..b13c183 100644 --- a/src/Storage.cpp +++ b/src/Storage.cpp @@ -205,6 +205,11 @@ std::filesystem::path Storage::GetPresetsDirectory() const { return this->dataRoot / "presets"; } +std::filesystem::path Storage::GetCurrentPresetPath() const +{ + return this->dataRoot / "currentPreset.json"; +} + std::filesystem::path Storage::GetChannelSelectionFileName() { @@ -821,3 +826,42 @@ WifiConfigSettings Storage::GetWifiConfigSettings() } +void Storage::SaveCurrentPreset(const CurrentPreset ¤tPreset) +{ + try { + std::filesystem::path path = GetCurrentPresetPath(); + + std::ofstream f(path); + json_writer writer(f); + writer.write(currentPreset); + } catch (std::exception&) + { + // called from destructor. Must be nothrow(). + } + +} +bool Storage::RestoreCurrentPreset(CurrentPreset*pResult) +{ + std::filesystem::path path = GetCurrentPresetPath(); + if (std::filesystem::exists(path)) { + try { + std::ifstream f(path); + json_reader reader(f); + reader.read(pResult); + std::filesystem::remove(path); // one-shot only, restore the state from the last *orderly* shutdown. + } catch (const std::exception&) + { + return false; + } + return true; + } else { + return false; + } + +} + +JSON_MAP_BEGIN(CurrentPreset) + JSON_MAP_REFERENCE(CurrentPreset,modified) + JSON_MAP_REFERENCE(CurrentPreset,preset) +JSON_MAP_END() + diff --git a/src/Storage.hpp b/src/Storage.hpp index 096828c..7ee3642 100644 --- a/src/Storage.hpp +++ b/src/Storage.hpp @@ -30,6 +30,14 @@ namespace pipedal { +class CurrentPreset { +public: + bool modified_ = false; + PedalBoard preset_; + + DECLARE_JSON_MAP(CurrentPreset); +}; + // controls user-defined storage. Implmentation hidden to allow to later migration to a database (perhaps) class Storage { @@ -45,6 +53,8 @@ private: std::filesystem::path GetIndexFileName() const; std::filesystem::path GetBankFileName(const std::string & name) const; std::filesystem::path GetChannelSelectionFileName(); + std::filesystem::path GetCurrentPresetPath() const; + void LoadBankIndex(); void SaveBankIndex(); @@ -104,6 +114,10 @@ public: void SetWifiConfigSettings(const WifiConfigSettings & wifiConfigSettings); WifiConfigSettings GetWifiConfigSettings(); + void SaveCurrentPreset(const CurrentPreset ¤tPreset); + bool RestoreCurrentPreset(CurrentPreset*pResult); + + }; diff --git a/src/SysExec.cpp b/src/SysExec.cpp index 6dcd04d..8a6e5cc 100644 --- a/src/SysExec.cpp +++ b/src/SysExec.cpp @@ -24,6 +24,7 @@ #include "PiPedalException.hpp" #include #include +#include // for PATH_MAX using namespace pipedal; using namespace std; @@ -129,4 +130,14 @@ int pipedal::SysExec(const char *szCommand) int exitStatus = WEXITSTATUS(returnValue); return exitStatus; } +} + + +std::string pipedal::GetSelfExePath() +{ + char result[PATH_MAX+1]; + ssize_t count = readlink("/proc/self/exe", result, PATH_MAX); + if (count < 0) throw PiPedalException("Can't find EXE path."); + result[count] = 0; + return result; } \ No newline at end of file diff --git a/src/SysExec.hpp b/src/SysExec.hpp index 3dd728b..a80c7a4 100644 --- a/src/SysExec.hpp +++ b/src/SysExec.hpp @@ -27,4 +27,5 @@ int SysExec(const char*szCommand); // execute a command, suppressing output. void SilentSysExec(const char *szCommand); +std::string GetSelfExePath(); } \ No newline at end of file