diff --git a/build_scripts/msw/xSchedule_64bit.iss b/build_scripts/msw/xSchedule_64bit.iss index cba8099..4b3e459 100644 --- a/build_scripts/msw/xSchedule_64bit.iss +++ b/build_scripts/msw/xSchedule_64bit.iss @@ -28,6 +28,7 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "D Source: "../../xSchedule/x64/Release/xSchedule.exe"; DestDir: "{app}" Source: "../../xlights/include/xSchedule64.ico"; DestDir: "{app}"; Flags: "ignoreversion" Source: "../../bin/xScheduleWeb\*.*"; DestDir: "{app}/xScheduleWeb"; Flags: ignoreversion recursesubdirs +Source: "../../xlights/controllers\*.xcontroller"; DestDir: "{app}/controllers"; Flags: ignoreversion ; xSMSDaemon Source: "../../xSchedule/xSMSDaemon/x64/Release/xSMSDaemon.dll"; DestDir: "{app}" diff --git a/xSchedule/PluginManager.cpp b/xSchedule/PluginManager.cpp index 651f538..923c8a1 100644 --- a/xSchedule/PluginManager.cpp +++ b/xSchedule/PluginManager.cpp @@ -61,8 +61,10 @@ PluginManager::PluginState::PluginState(wxDynamicLibrary* dl, const std::string& PluginManager::PluginState::~PluginState() { if (_dl) { + spdlog::debug("PluginState destructor unloading dynamic library '{}'.", _filename); ///_dl->Detach(); ???? delete _dl; + spdlog::debug("PluginState destructor unloaded dynamic library '{}'.", _filename); } } @@ -151,6 +153,7 @@ void PluginManager::DoUnload(const std::string& plugin) if (fn != nullptr) { spdlog::debug("Unloading plugin {}", plugin); fn(); + spdlog::debug("Unloaded plugin {}", plugin); } } @@ -353,6 +356,8 @@ void PluginManager::RegisterStaticPlugins() { void PluginManager::Initialise(const std::string& showDir) { + spdlog::debug("PluginManager::Initialise starting for show dir '{}'.", showDir); + // scan xSchedule folder first wxFileName fi(wxStandardPaths::Get().GetExecutablePath().ToStdString()); @@ -364,10 +369,14 @@ void PluginManager::Initialise(const std::string& showDir) // then add anything that is statically compiled in RegisterStaticPlugins(); - + for (auto it : _plugins) { + spdlog::debug("PluginManager::Initialise loading plugin '{}'.", it.first); DoLoad(it.first, (char*)showDir.c_str()); + spdlog::debug("PluginManager::Initialise loaded plugin '{}'.", it.first); } + + spdlog::debug("PluginManager::Initialise complete. {} plugin(s).", _plugins.size()); } bool PluginManager::StartPlugin(const std::string& plugin, const std::string& showDir, const std::string& xScheduleURL) @@ -416,12 +425,17 @@ void PluginManager::WipeSettings() void PluginManager::Uninitialise() { + spdlog::debug("PluginManager::Uninitialise starting. {} plugin(s).", _plugins.size()); for (auto it : _plugins) { + spdlog::debug("PluginManager::Uninitialise unloading plugin '{}'.", it.first); DoUnload(it.first); + spdlog::debug("PluginManager::Uninitialise unloaded plugin '{}'. Deleting plugin state.", it.first); delete it.second; + spdlog::debug("PluginManager::Uninitialise deleted plugin state for '{}'.", it.first); } _plugins.clear(); + spdlog::debug("PluginManager::Uninitialise complete."); } std::vector PluginManager::GetPlugins() const diff --git a/xSchedule/RemoteFalcon/RemoteFalconMain.cpp b/xSchedule/RemoteFalcon/RemoteFalconMain.cpp index d30abd5..5380a53 100644 --- a/xSchedule/RemoteFalcon/RemoteFalconMain.cpp +++ b/xSchedule/RemoteFalcon/RemoteFalconMain.cpp @@ -27,7 +27,7 @@ #include #include -#include "../../xlights/xLights/xLightsVersion.h" +#include "../xScheduleVersion.h" #include "RemoteFalconMain.h" #include "RemoteFalconSettingsDialog.h" #include "RemoteFalconOptions.h" @@ -50,6 +50,20 @@ static bool IsHttpErrorCode(int responseCode) return responseCode != 0 && (responseCode < 200 || responseCode >= 300); } +// Remote Falcon has been observed returning some fields (eg queuelength, leftms) +// as native JSON numbers rather than strings depending on the account/backend +// version. nlohmann's val.value(key, std::string()) still throws type_error.302 +// in that case, so pull the value out ourselves and stringify whatever we find. +static std::string JsonAsString(const nlohmann::json& val, const std::string& key, const std::string& def = "") +{ + if (!val.is_object()) return def; + auto it = val.find(key); + if (it == val.end() || it->is_null()) return def; + if (it->is_string()) return it->get(); + if (it->is_boolean()) return it->get() ? "true" : "false"; + return it->dump(); +} + enum wxbuildinfoformat { short_f, long_f }; @@ -135,10 +149,11 @@ void RemoteFalconFrame::DoSendPlaylists() { try { nlohmann::json const val = nlohmann::json::parse(res); - if (!val.is_null() && !val["message"].is_null() && val["message"].get() == "Success") { + std::string message = JsonAsString(val, "message"); + if (!val.is_null() && message == "Success") { _oldSteps = plsteps; - } else if (!val.is_null() && !val["message"].is_null()) { - AddMessage(MESSAGE_LEVEL::ML_ERROR, "ERROR: " + val["message"].get()); + } else if (!val.is_null() && !message.empty()) { + AddMessage(MESSAGE_LEVEL::ML_ERROR, "ERROR: " + message); } else if (val.is_null()) { AddMessage(MESSAGE_LEVEL::ML_ERROR, "ERROR: uploading playlist to remote falcon."); } @@ -162,6 +177,23 @@ void RemoteFalconFrame::AddMessage(MESSAGE_LEVEL msgLevel, const std::string& ms } } +void RemoteFalconFrame::LogViewerControlResponse(const std::string& res) +{ + try { + nlohmann::json const val = nlohmann::json::parse(res); + std::string message = JsonAsString(val, "message"); + if (!message.empty() && message != "Success") { + AddMessage(MESSAGE_LEVEL::ML_ERROR, " ERROR: " + message); + } else { + AddMessage(MESSAGE_LEVEL::ML_DEBUG, " OK."); + } + } catch (const nlohmann::json::exception& ex) { + AddMessage(MESSAGE_LEVEL::ML_ERROR, " ERROR: " + std::string(ex.what())); + } catch (const std::exception& ex) { + AddMessage(MESSAGE_LEVEL::ML_ERROR, " ERROR: " + std::string(ex.what())); + } +} + RemoteFalconFrame::RemoteFalconFrame(wxWindow* parent, const std::string& showDir, const std::string& xScheduleURL, p_xSchedule_Action action, wxWindowID id) { _toProcess = 0; @@ -233,7 +265,7 @@ RemoteFalconFrame::RemoteFalconFrame(wxWindow* parent, const std::string& showDi // only start the timer when we start the service Timer_UpdatePlaylist.Stop(); - SetTitle("Remote Falcon " + GetDisplayVersionString()); + SetTitle("Remote Falcon " + GetXScheduleDisplayVersionString()); xSchedule::Initialise(action); @@ -284,10 +316,11 @@ RemoteFalconFrame::RemoteFalconFrame(wxWindow* parent, const std::string& showDi try { nlohmann::json const val = nlohmann::json::parse(res); - if (!val.is_null() && !val["message"].is_null() && val["message"].get() == "Success") { + std::string message = JsonAsString(val, "message"); + if (!val.is_null() && message == "Success") { AddMessage(MESSAGE_LEVEL::ML_INFO, "Cleared remote falcon list of songs."); - } else if (!val.is_null() && !val["message"].is_null()) { - AddMessage(MESSAGE_LEVEL::ML_ERROR, "Error: " + val["message"].get()); + } else if (!val.is_null() && !message.empty()) { + AddMessage(MESSAGE_LEVEL::ML_ERROR, "Error: " + message); } else { AddMessage(MESSAGE_LEVEL::ML_ERROR, "Error: " + res); } @@ -337,7 +370,7 @@ void RemoteFalconFrame::OnQuit(wxCommandEvent& event) void RemoteFalconFrame::OnAbout(wxCommandEvent& event) { - auto about = wxString::Format(wxT("RemoteFalcon v%s."), GetDisplayVersionString()); + auto about = wxString::Format(wxT("RemoteFalcon v%s."), GetXScheduleDisplayVersionString()); wxMessageBox(about, _("Welcome to...")); } @@ -463,7 +496,7 @@ void RemoteFalconFrame::Start() if (_options.IsEnableDisable()) { AddMessage(MESSAGE_LEVEL::ML_INFO, "Asking remote falcon to enable viewer control."); - AddMessage(MESSAGE_LEVEL::ML_INFO, " " + _remoteFalcon->EnableViewerControl(true)); + LogViewerControlResponse(_remoteFalcon->EnableViewerControl(true)); _viewerControlEnabled = true; } @@ -477,7 +510,7 @@ void RemoteFalconFrame::Stop(bool suppressMessage) if (_options.IsEnableDisable()) { AddMessage(MESSAGE_LEVEL::ML_INFO, "Asking remote falcon to disable viewer control."); - AddMessage(MESSAGE_LEVEL::ML_INFO, " " + _remoteFalcon->EnableViewerControl(false)); + LogViewerControlResponse(_remoteFalcon->EnableViewerControl(false)); _viewerControlEnabled = false; } @@ -526,14 +559,11 @@ void RemoteFalconFrame::GetMode() try { nlohmann::json const val = nlohmann::json::parse(res); if (!val.is_null()) { - if (!val["viewerControlMode"].is_null()) { - _mode = val["viewerControlMode"].get(); - } - if (!val["remoteSubdomain"].is_null()) { - _subdomain = val["remoteSubdomain"].get(); - } - if (!val["message"].is_null()) { - AddMessage(MESSAGE_LEVEL::ML_ERROR, "ERROR: " + val["message"].get()); + _mode = JsonAsString(val, "viewerControlMode"); + _subdomain = JsonAsString(val, "remoteSubdomain"); + std::string message = JsonAsString(val, "message"); + if (!message.empty()) { + AddMessage(MESSAGE_LEVEL::ML_ERROR, "ERROR: " + message); } } @@ -591,14 +621,10 @@ void RemoteFalconFrame::GetAndPlaySong(const std::string& playing) std::string nextSong; if (!val.is_null()) { if (_mode == "voting") { - if (!val["winningPlaylist"].is_null()) { - nextSong = val["winningPlaylist"].get(); - } + nextSong = JsonAsString(val, "winningPlaylist"); } else { - if (!val["nextPlaylist"].is_null()) { - nextSong = val["nextPlaylist"].get(); - } + nextSong = JsonAsString(val, "nextPlaylist"); } } @@ -660,13 +686,9 @@ void RemoteFalconFrame::GetAndPlayEffect() { std::string nextSong; if (!val.is_null()) { if (_mode == "voting") { - if (!val["winningPlaylist"].is_null()) { - nextSong = val["winningPlaylist"].get(); - } + nextSong = JsonAsString(val, "winningPlaylist"); } else { - if (!val["nextPlaylist"].is_null()) { - nextSong = val["nextPlaylist"].get(); - } + nextSong = JsonAsString(val, "nextPlaylist"); } } @@ -706,7 +728,7 @@ void RemoteFalconFrame::DoNotifyStatus(const std::string& status) if (effects.size() > 0) playing = effects.front(); } else { - playing = val.value("step", std::string()); + playing = JsonAsString(val, "step"); } if (_lastPlaying != playing) { @@ -714,13 +736,13 @@ void RemoteFalconFrame::DoNotifyStatus(const std::string& status) _lastPlaying = playing; } - auto trigger = val.value("trigger", std::string()); + auto trigger = JsonAsString(val, "trigger"); // Only play songs if a schedule is playing if (trigger == "scheduled" || trigger == "queued") { - int queueLength = wxAtoi(val.value("queuelength", std::string())); - auto lefts = wxAtol(val.value("leftms", std::string())) / 1000; - _playingPlaylist = val.value("playlist", std::string()); + int queueLength = wxAtoi(JsonAsString(val, "queuelength")); + auto lefts = wxAtol(JsonAsString(val, "leftms")) / 1000; + _playingPlaylist = JsonAsString(val, "playlist"); // we can only play a song if the playlist playing allows if (_options.IsPlayDuring(_playingPlaylist) || _playingPlaylist == "Song Queue") { @@ -728,7 +750,7 @@ void RemoteFalconFrame::DoNotifyStatus(const std::string& status) if (!_viewerControlEnabled) { if (_options.IsEnableDisable()) { AddMessage(MESSAGE_LEVEL::ML_INFO, "Asking remote falcon to enable viewer control."); - AddMessage(MESSAGE_LEVEL::ML_INFO, " " + _remoteFalcon->EnableViewerControl(true)); + LogViewerControlResponse(_remoteFalcon->EnableViewerControl(true)); _viewerControlEnabled = true; } } @@ -749,7 +771,7 @@ void RemoteFalconFrame::DoNotifyStatus(const std::string& status) if (_viewerControlEnabled) { if (_options.IsEnableDisable()) { AddMessage(MESSAGE_LEVEL::ML_INFO, "Asking remote falcon to disable viewer control."); - AddMessage(MESSAGE_LEVEL::ML_INFO, " " + _remoteFalcon->EnableViewerControl(false)); + LogViewerControlResponse(_remoteFalcon->EnableViewerControl(false)); _viewerControlEnabled = false; } } @@ -760,7 +782,7 @@ void RemoteFalconFrame::DoNotifyStatus(const std::string& status) if (_viewerControlEnabled) { if (_options.IsEnableDisable()) { AddMessage(MESSAGE_LEVEL::ML_INFO, "Asking remote falcon to disable viewer control."); - AddMessage(MESSAGE_LEVEL::ML_INFO, " " + _remoteFalcon->EnableViewerControl(false)); + LogViewerControlResponse(_remoteFalcon->EnableViewerControl(false)); _viewerControlEnabled = false; } } @@ -841,12 +863,12 @@ bool RemoteFalconFrame::SendCommand(const std::string& command, const std::strin else if (command == "viewer_control") { if (parameters == "start") { AddMessage(MESSAGE_LEVEL::ML_INFO, "Asking remote falcon to enable viewer control."); - AddMessage(MESSAGE_LEVEL::ML_INFO, " " + _remoteFalcon->EnableViewerControl(true)); + LogViewerControlResponse(_remoteFalcon->EnableViewerControl(true)); _viewerControlEnabled = true; return true; } else if (parameters == "stop") { AddMessage(MESSAGE_LEVEL::ML_INFO, "Asking remote falcon to disable viewer control."); - AddMessage(MESSAGE_LEVEL::ML_INFO, " " + _remoteFalcon->EnableViewerControl(false)); + LogViewerControlResponse(_remoteFalcon->EnableViewerControl(false)); _viewerControlEnabled = false; return true; } else { diff --git a/xSchedule/RemoteFalcon/RemoteFalconMain.h b/xSchedule/RemoteFalcon/RemoteFalconMain.h index 195293f..88f5d0c 100644 --- a/xSchedule/RemoteFalcon/RemoteFalconMain.h +++ b/xSchedule/RemoteFalcon/RemoteFalconMain.h @@ -65,6 +65,7 @@ class RemoteFalconFrame : public wxFrame void ValidateWindow(); void AddMessage(MESSAGE_LEVEL lvl, const std::string& msg); void DoAddMessage(const std::string& msg); + void LogViewerControlResponse(const std::string& res); void DoNotifyStatus(const std::string& status); void DoSendPlayingSong(const std::string& playing); void SendPlayingSong(const std::string& playing); diff --git a/xSchedule/ScheduleManager.cpp b/xSchedule/ScheduleManager.cpp index 77622d1..7db6016 100644 --- a/xSchedule/ScheduleManager.cpp +++ b/xSchedule/ScheduleManager.cpp @@ -342,27 +342,34 @@ int ScheduleManager::DoSync(const std::string& filename, long ms) { } ScheduleManager::~ScheduleManager() { + spdlog::debug("ScheduleManager destructor starting."); AllOff(); + spdlog::debug("ScheduleManager destructor: stopping output."); _outputManager->StopOutput(); SetConfigBool("OutputActive", false); #ifdef __WXMSW__ ::SetPriorityClass(::GetCurrentProcess(), NORMAL_PRIORITY_CLASS); #endif + spdlog::debug("ScheduleManager destructor: stopping virtual matrices."); StopVirtualMatrices(); ManageBackground(); spdlog::info("Stopped outputting to lights."); if (IsDirty()) { + spdlog::debug("ScheduleManager destructor: schedule is dirty, prompting to save."); if (wxMessageBox("Unsaved changes to the schedule. Save now?", "Unsaved changes", wxYES_NO) == wxYES) { Save(); } } + spdlog::debug("ScheduleManager destructor: stopping sync manager."); _syncManager->Stop(GetForceLocalIP()); if (_listenerManager != nullptr) { + spdlog::debug("ScheduleManager destructor: stopping listener manager."); _listenerManager->Stop(); delete _listenerManager; + spdlog::debug("ScheduleManager destructor: listener manager stopped."); } while (_overlayData.size() > 0) { @@ -436,6 +443,7 @@ ScheduleManager::~ScheduleManager() { #endif spdlog::info("Closed schedule."); + spdlog::debug("ScheduleManager destructor complete."); } std::list ScheduleManager::GetPlayListIps() const { diff --git a/xSchedule/xSMSDaemon/xSMSDaemonMain.cpp b/xSchedule/xSMSDaemon/xSMSDaemonMain.cpp index e21a0e4..175495e 100644 --- a/xSchedule/xSMSDaemon/xSMSDaemonMain.cpp +++ b/xSchedule/xSMSDaemon/xSMSDaemonMain.cpp @@ -27,7 +27,7 @@ #include #include -#include "../../xlights/xLights/xLightsVersion.h" +#include "../xScheduleVersion.h" #include "xSMSDaemonMain.h" #include "../../xlights/include/xLights.xpm" @@ -197,7 +197,7 @@ xSMSDaemonFrame::xSMSDaemonFrame(wxWindow* parent, const std::string& showDir, c //Timer_Second.SetName("xSMSDaemon second timer"); //SendTimer.SetName("xSMSDaemon send timer"); - SetTitle("xLights SMS Daemon " + GetDisplayVersionString()); + SetTitle("xLights SMS Daemon " + GetXScheduleDisplayVersionString()); wxIconBundle icons; icons.AddIcon(wxIcon(xlights_16_xpm)); @@ -383,7 +383,7 @@ void xSMSDaemonFrame::OnQuit(wxCommandEvent& event) void xSMSDaemonFrame::OnAbout(wxCommandEvent& event) { - auto about = wxString::Format(wxT("xSMSDaemon v%s."), GetDisplayVersionString()); + auto about = wxString::Format(wxT("xSMSDaemon v%s."), GetXScheduleDisplayVersionString()); wxMessageBox(about, _("Welcome to...")); } diff --git a/xSchedule/xScheduleMain.cpp b/xSchedule/xScheduleMain.cpp index 06f6900..f772df4 100644 --- a/xSchedule/xScheduleMain.cpp +++ b/xSchedule/xScheduleMain.cpp @@ -916,17 +916,7 @@ xScheduleFrame::xScheduleFrame(wxWindow* parent, const std::string& showdir, con spdlog::debug("Loading plugins."); _pluginManager.Initialise(_showDir); - - for (auto it : _pluginManager.GetPlugins()) { - wxMenuItem* mi = Menu_Plugins->Append(_pluginManager.GetId(it), _pluginManager.GetMenuLabel(it), nullptr); - mi->SetCheckable(true); - Connect(_pluginManager.GetId(it), wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xScheduleFrame::OnPluginMenu); - if (config->ReadBool(_("Plugin") + it, false)) { - if (_pluginManager.StartPlugin(it, _showDir, __schedule->GetOptions()->GetOurURL())) { - mi->Check(true); - } - } - } + RebuildPluginsMenu(); spdlog::debug("Plugins loaded."); #if !defined(_DEBUG) @@ -986,11 +976,15 @@ void xScheduleFrame::LoadSchedule() if (__schedule != nullptr) { + spdlog::debug("LoadSchedule: deleting existing ScheduleManager."); delete __schedule; __schedule = nullptr; + spdlog::debug("LoadSchedule: existing ScheduleManager deleted."); } + spdlog::debug("LoadSchedule: constructing new ScheduleManager for '{}'.", _showDir); __schedule = new ScheduleManager(this, _showDir); + spdlog::debug("LoadSchedule: new ScheduleManager constructed."); _pinger = new Pinger(__schedule->GetListenerManager(), __schedule->GetOutputManager()); __schedule->SetPinger(_pinger); @@ -1050,6 +1044,29 @@ void xScheduleFrame::LoadSchedule() spdlog::debug("Schedule loaded."); } +void xScheduleFrame::RebuildPluginsMenu() +{ + wxConfigBase* config = wxConfigBase::Get(); + + // remove any menu items (and their event bindings) left over from a previous show folder + auto oldItems = Menu_Plugins->GetMenuItems(); + for (auto item : oldItems) { + Disconnect(item->GetId(), wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xScheduleFrame::OnPluginMenu); + Menu_Plugins->Delete(item); + } + + for (auto it : _pluginManager.GetPlugins()) { + wxMenuItem* mi = Menu_Plugins->Append(_pluginManager.GetId(it), _pluginManager.GetMenuLabel(it), nullptr); + mi->SetCheckable(true); + Connect(_pluginManager.GetId(it), wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xScheduleFrame::OnPluginMenu); + if (config->ReadBool(_("Plugin") + it, false)) { + if (_pluginManager.StartPlugin(it, _showDir, __schedule->GetOptions()->GetOurURL())) { + mi->Check(true); + } + } + } +} + void xScheduleFrame::AddIPs() { if (_pinger != nullptr) @@ -1548,19 +1565,30 @@ bool xScheduleFrame::SelectShowFolder() void xScheduleFrame::OnMenuItem_ShowFolderSelected(wxCommandEvent& event) { + spdlog::debug("ShowFolderSelected: menu item invoked."); if (SelectShowFolder()) { + spdlog::debug("ShowFolderSelected: new show folder '{}' selected. Stopping plugins.", _showDir); + _pluginManager.StopPlugins(); + spdlog::debug("ShowFolderSelected: plugins stopped. Uninitialising plugins."); _pluginManager.Uninitialise(); + spdlog::debug("ShowFolderSelected: plugins uninitialised. Stopping timers."); _timerSchedule.Stop(); _timer.Stop(); + spdlog::debug("ShowFolderSelected: timers stopped. Loading schedule."); LoadSchedule(); + spdlog::debug("ShowFolderSelected: schedule loaded. Restarting timers."); wxASSERT(__schedule != nullptr); _useHalfFrames = true; _timer.Start(50 / 2, false); _timerSchedule.Start(500, false); + spdlog::debug("ShowFolderSelected: timers restarted. Initialising plugins."); _pluginManager.Initialise(_showDir); + RebuildPluginsMenu(); + spdlog::debug("ShowFolderSelected: plugins initialised."); } ValidateWindow(); + spdlog::debug("ShowFolderSelected: complete."); } void xScheduleFrame::SaveShowDir() const @@ -1863,6 +1891,8 @@ void xScheduleFrame::UpdateSchedule() void xScheduleFrame::On_timerScheduleTrigger(wxTimerEvent& event) { + if (__schedule == nullptr) return; + if (__schedule->IsFPPRemoteOrMaster()) { SyncFPP::Ping(__schedule->IsSlave(), __schedule->GetForceLocalIP()); } diff --git a/xSchedule/xScheduleMain.h b/xSchedule/xScheduleMain.h index 340a55c..fac7634 100644 --- a/xSchedule/xScheduleMain.h +++ b/xSchedule/xScheduleMain.h @@ -87,6 +87,7 @@ class xScheduleFrame : public xlFrame { void UpdateSchedule(); std::string GetScheduleName(Schedule* schedule, const std::list& active) const; void LoadSchedule(); + void RebuildPluginsMenu(); bool HandleHotkeys(wxKeyEvent& event); bool HandleSpecialKeys(wxKeyEvent& event); void AddPlayList(bool forceadvanced = false);