From a142ee6ec26b2fc7e5a2923ca444cfa0c3cd24f1 Mon Sep 17 00:00:00 2001 From: derwin12 <4643499+derwin12@users.noreply.github.com> Date: Sat, 9 May 2026 17:38:36 -0400 Subject: [PATCH] spdlog corrections, check for updates and downloads --- xSchedule/Pinger.cpp | 2 +- xSchedule/RemoteFalcon/RemoteFalconMain.cpp | 4 +- xSchedule/events/ListenerMQTT.cpp | 2 +- xSchedule/wxsmith/xScheduleframe.wxs | 14 ++ xSchedule/xScheduleApp.cpp | 4 +- xSchedule/xScheduleMain.cpp | 160 +++++++++++++++++++- xSchedule/xScheduleMain.h | 10 ++ 7 files changed, 188 insertions(+), 8 deletions(-) diff --git a/xSchedule/Pinger.cpp b/xSchedule/Pinger.cpp index 8de72927..8c124320 100644 --- a/xSchedule/Pinger.cpp +++ b/xSchedule/Pinger.cpp @@ -42,7 +42,7 @@ class PingThread : public wxThread { } void Stop() { - spdlog::debug("Asking pinging thread %s to stop", (const char*)_pinger->GetName().c_str()); + spdlog::debug("Asking pinging thread {} to stop", (const char*)_pinger->GetName().c_str()); _stop = true; } diff --git a/xSchedule/RemoteFalcon/RemoteFalconMain.cpp b/xSchedule/RemoteFalcon/RemoteFalconMain.cpp index 87ae4bbb..c7ad475e 100644 --- a/xSchedule/RemoteFalcon/RemoteFalconMain.cpp +++ b/xSchedule/RemoteFalcon/RemoteFalconMain.cpp @@ -417,14 +417,14 @@ void RemoteFalconFrame::OnMenuItem_ViewLogSelected(wxCommandEvent& event) wxString command = ft->GetOpenCommand("foo.txt"); command.Replace("foo.txt", fn); - spdlog::debug("Viewing log file %s.", (const char*)fn.c_str()); + spdlog::debug("Viewing log file {}.", (const char*)fn.c_str()); wxExecute(command); delete ft; } else { - spdlog::warn("Unable to view log file %s.", (const char*)fn.c_str()); + spdlog::warn("Unable to view log file {}.", (const char*)fn.c_str()); wxMessageBox(_("Unable to show log file."), _("Error")); } } diff --git a/xSchedule/events/ListenerMQTT.cpp b/xSchedule/events/ListenerMQTT.cpp index 5e1dbed3..095f6a7d 100644 --- a/xSchedule/events/ListenerMQTT.cpp +++ b/xSchedule/events/ListenerMQTT.cpp @@ -156,7 +156,7 @@ bool ListenerMQTT::Subscribe(const std::string& topic) index += PlayListItemMQTT::EncodeString(&buffer[index], topic); buffer[index++] = 0x00; - spdlog::info("MQTT subscribing to topic %s.", topic); + spdlog::info("MQTT subscribing to topic {}.", topic); _client.Write(buffer, index); wxASSERT((int)_client.LastWriteCount() == index); diff --git a/xSchedule/wxsmith/xScheduleframe.wxs b/xSchedule/wxsmith/xScheduleframe.wxs index 535128c9..41f4557b 100644 --- a/xSchedule/wxsmith/xScheduleframe.wxs +++ b/xSchedule/wxsmith/xScheduleframe.wxs @@ -587,6 +587,20 @@ Show info about this application + + + + + + + Donate to the xLights project. + + + + + Check for newer xSchedule updates + + diff --git a/xSchedule/xScheduleApp.cpp b/xSchedule/xScheduleApp.cpp index 41a5cc08..6b3bb446 100644 --- a/xSchedule/xScheduleApp.cpp +++ b/xSchedule/xScheduleApp.cpp @@ -337,11 +337,11 @@ bool xScheduleApp::OnInit() } if (parser.Found("s", &showDir)) { parmfound = true; - spdlog::info("-s: Show directory set to %s.", (const char*)showDir.c_str()); + spdlog::info("-s: Show directory set to {}.", (const char*)showDir.c_str()); } if (parser.Found("p", &playlist)) { parmfound = true; - spdlog::info("-p: Playlist to play %s.", (const char*)playlist.c_str()); + spdlog::info("-p: Playlist to play {}.", (const char*)playlist.c_str()); } if (!parmfound && parser.GetParamCount() > 0) { spdlog::info("Unrecognised command line parameter found."); diff --git a/xSchedule/xScheduleMain.cpp b/xSchedule/xScheduleMain.cpp index dde6ad59..9c3e87cd 100644 --- a/xSchedule/xScheduleMain.cpp +++ b/xSchedule/xScheduleMain.cpp @@ -71,6 +71,8 @@ #include "../xlights/xLights/outputs/ControllerSerial.h" #include "RemoteModeConfigDialog.h" #include "utils/ExternalHooks.h" +#include "utils/CurlManager.h" +#include #include "../xlights/include/xs_save.xpm" #include "../xlights/include/xs_otlon.xpm" @@ -224,6 +226,9 @@ const wxWindowID xScheduleFrame::ID_MNU_OSCOPTION = wxNewId(); const wxWindowID xScheduleFrame::MNU_CONFIGUREMIDITIMECODE = wxNewId(); const wxWindowID xScheduleFrame::ID_MNU_CONFIGURE_TEST = wxNewId(); const wxWindowID xScheduleFrame::idMenuAbout = wxNewId(); +const wxWindowID xScheduleFrame::ID_MNU_UPDATE = wxNewId(); +const wxWindowID xScheduleFrame::ID_MNU_DONATE = wxNewId(); +const wxWindowID xScheduleFrame::ID_MNU_DOWNLOAD = wxNewId(); const wxWindowID xScheduleFrame::ID_STATUSBAR1 = wxNewId(); const wxWindowID xScheduleFrame::ID_TIMER1 = wxNewId(); const wxWindowID xScheduleFrame::ID_TIMER2 = wxNewId(); @@ -613,6 +618,12 @@ xScheduleFrame::xScheduleFrame(wxWindow* parent, const std::string& showdir, con Menu2 = new wxMenu(); MenuItem2 = new wxMenuItem(Menu2, idMenuAbout, _("About\tF1"), _("Show info about this application"), wxITEM_NORMAL); Menu2->Append(MenuItem2); + MenuItem_Download = new wxMenuItem(Menu2, ID_MNU_DOWNLOAD, _("Download"), wxEmptyString, wxITEM_NORMAL); + Menu2->Append(MenuItem_Download); + MenuItem_Donate = new wxMenuItem(Menu2, ID_MNU_DONATE, _("Donate"), _("Donate to the xLights project."), wxITEM_NORMAL); + Menu2->Append(MenuItem_Donate); + MenuItem_Update = new wxMenuItem(Menu2, ID_MNU_UPDATE, _("Check for Updates"), _("Check for newer xSchedule updates"), wxITEM_NORMAL); + Menu2->Append(MenuItem_Update); MenuBar1->Append(Menu2, _("Help")); SetMenuBar(MenuBar1); StatusBar1 = new wxStatusBar(this, ID_STATUSBAR1, 0, _T("ID_STATUSBAR1")); @@ -691,6 +702,9 @@ xScheduleFrame::xScheduleFrame(wxWindow* parent, const std::string& showdir, con Connect(MNU_CONFIGUREMIDITIMECODE, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xScheduleFrame::OnMenuItem5MenuItem_ConfigureMIDITimecodeSelected); Connect(ID_MNU_CONFIGURE_TEST, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xScheduleFrame::OnMenuItem_ConfigureTestSelected); Connect(idMenuAbout, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xScheduleFrame::OnAbout); + Connect(ID_MNU_DOWNLOAD, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xScheduleFrame::OnMenuItem_DownloadSelected); + Connect(ID_MNU_UPDATE, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xScheduleFrame::OnMenuItem_UpdateSelected); + Connect(ID_MNU_DONATE, wxEVT_COMMAND_MENU_SELECTED, (wxObjectEventFunction)&xScheduleFrame::OnMenuItem_DonateSelected); Connect(ID_TIMER1, wxEVT_TIMER, (wxObjectEventFunction)&xScheduleFrame::On_timerTrigger); Connect(ID_TIMER2, wxEVT_TIMER, (wxObjectEventFunction)&xScheduleFrame::On_timerScheduleTrigger); Connect(wxID_ANY, wxEVT_CLOSE_WINDOW, (wxObjectEventFunction)&xScheduleFrame::OnClose); @@ -913,6 +927,10 @@ xScheduleFrame::xScheduleFrame(wxWindow* parent, const std::string& showdir, con } } spdlog::debug("Plugins loaded."); + +#if !defined(_DEBUG) + CheckForUpdate(false); +#endif } void xScheduleFrame::LoadSchedule() @@ -922,6 +940,7 @@ void xScheduleFrame::LoadSchedule() // reset our special options SpecialOptions::StashShowDir(_showDir); SpecialOptions::GetOption("", ""); + spdlog::info("Special options file: {}", _showDir + GetPathSeparator() + "special.options"); if (_f != nullptr) { @@ -942,6 +961,13 @@ void xScheduleFrame::LoadSchedule() wxLog::SetActiveTarget(new wxLogStderr()); } + // Re-apply spdlog levels from special.options now that show dir is known + spdlog::default_logger()->set_level(spdlog::level::from_str(SpecialOptions::GetOption("xschedule_logger", "info"))); + auto curl_log = spdlog::get("curl"); + if (curl_log) curl_log->set_level(spdlog::level::from_str(SpecialOptions::GetOption("curl_logger", "info"))); + auto frame_log = spdlog::get("frame"); + if (frame_log) frame_log->set_level(spdlog::level::from_str(SpecialOptions::GetOption("frame_logger", "info"))); + spdlog::debug("Loading schedule."); if (_pinger != nullptr) @@ -1148,6 +1174,136 @@ void xScheduleFrame::OnAbout(wxCommandEvent& event) wxMessageBox(about, _("Welcome to...")); } +void xScheduleFrame::OnMenuItem_DownloadSelected(wxCommandEvent& event) +{ + ::wxLaunchDefaultBrowser("https://github.com/xLightsSequencer/xSchedule"); +} + +void xScheduleFrame::OnMenuItem_DonateSelected(wxCommandEvent& event) +{ + ::wxLaunchDefaultBrowser("https://www.paypal.com/donate/?hosted_button_id=BB6366BT755H6"); +} + +void xScheduleFrame::OnMenuItem_UpdateSelected(wxCommandEvent& event) +{ + bool update_found = CheckForUpdate(true); + if (!update_found) { + DisplayInfo("Update check complete: No update found", this); + } +} + +bool xScheduleFrame::CheckForUpdate(bool showMessageBoxes) +{ + bool found_update = false; + std::string githubTagURL = "https://api.github.com/repos/xLightsSequencer/xSchedule/releases?per_page=6"; + int rc = 0; + spdlog::info("Checking for xSchedule updates at {}", githubTagURL); + + bool didConnect = false; + std::string resp; + nlohmann::json val; + int maxRetries = showMessageBoxes ? 3 : 1; + for (int retry = 0; retry < maxRetries && !didConnect; retry++) { + resp = CurlManager::INSTANCE.doGet(githubTagURL, rc); + spdlog::info("xSchedule update check: HTTP {} response length {}", rc, resp.size()); + if (rc == 200 && !resp.empty()) { + try { + val = nlohmann::json::parse(resp, nullptr, false); + if (!val.is_discarded()) { + didConnect = true; + spdlog::info("xSchedule update check: {} releases found", (int)val.size()); + } + } catch (...) { + spdlog::info("xSchedule update check: JSON parse exception"); + } + } else { + wxSleep(1); + } + } + if (!didConnect) { + spdlog::info("xSchedule version update check failed. Unable to connect."); + if (showMessageBoxes) { + wxMessageBox("Unable to connect.", "xSchedule version update check failed"); + } + return false; + } + + wxString skipver; + wxConfigBase* config = wxConfigBase::Get(); + if (!showMessageBoxes && config != nullptr) { + config->Read("xsSkipVersion", &skipver); + } + +#ifdef LINUX + const std::string ASSET_EXT = "AppImage"; +#else + const std::string ASSET_EXT = "exe"; +#endif + + std::string downloadURL; + std::string urlVersion; + for (int x = 0; x < (int)val.size() && downloadURL.empty(); x++) { + if (val[x].contains("name")) { + std::string verName = val[x].contains("tag_name") ? val[x]["tag_name"].get() : val[x]["name"].get(); + if (verName != "nightly" && val[x].contains("assets")) { + for (int a = 0; a < (int)val[x]["assets"].size(); a++) { + std::string url = val[x]["assets"][a]["browser_download_url"].get(); + if (url.ends_with(ASSET_EXT)) { + downloadURL = url; + urlVersion = verName; + } + } + } + } + } + + std::string currentVersion = xlights_version_string; + spdlog::info("xSchedule current version: '{}'. Latest available: '{}'. Skip version: '{}'.", + currentVersion, urlVersion, skipver.ToStdString()); + + if (!downloadURL.empty()) { + if ((urlVersion != skipver) && (urlVersion != currentVersion) && IsVersionOlder(urlVersion, currentVersion)) { + found_update = true; + wxDialog dlg(this, wxID_ANY, "xSchedule Update Available"); + wxBoxSizer* vs = new wxBoxSizer(wxVERTICAL); + wxStaticText* lbl = new wxStaticText(&dlg, wxID_ANY, + "You are currently running xSchedule " + currentVersion + "\n" + + "Whereas the current release is " + urlVersion, + wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE_HORIZONTAL); + vs->Add(lbl, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 10); + wxBoxSizer* hs = new wxBoxSizer(wxHORIZONTAL); + wxButton* btnDownload = new wxButton(&dlg, wxID_OK, "Download new release"); + wxButton* btnIgnore = new wxButton(&dlg, wxID_NO, "Ignore this version"); + wxButton* btnSkip = new wxButton(&dlg, wxID_CANCEL, "Skip this time"); + btnSkip->SetDefault(); + hs->Add(btnDownload, 1, wxALL, 5); + hs->Add(btnIgnore, 1, wxALL, 5); + hs->Add(btnSkip, 1, wxALL, 5); + vs->Add(hs, 0, wxALL | wxALIGN_CENTER_HORIZONTAL, 5); + dlg.SetSizerAndFit(vs); + dlg.Centre(); + int result = dlg.ShowModal(); + if (result == wxID_OK) { + spdlog::info("User downloading xSchedule update to version {}.", urlVersion); + wxLaunchDefaultBrowser(downloadURL); + if (config) config->Write("xsSkipVersion", ""); + } else if (result == wxID_NO) { + spdlog::info("User ignoring xSchedule update to version {}.", urlVersion); + if (config) config->Write("xsSkipVersion", wxString(urlVersion)); + } else { + spdlog::info("User skipping xSchedule update to version {}.", urlVersion); + if (config) config->Write("xsSkipVersion", ""); + } + } + } else { + spdlog::info("xSchedule version update check failed. Unable to read available versions."); + if (showMessageBoxes) { + wxMessageBox("Unable to read available versions.", "xSchedule version update check failed"); + } + } + return found_update; +} + bool xScheduleFrame::IsPlayList(wxTreeItemId id) const { if (!id.IsOk()) return false; @@ -1534,7 +1690,7 @@ void xScheduleFrame::On_timerTrigger(wxTimerEvent& event) auto logger_frame = spdlog::get("frame"); - logger_frame->info("Timer: Start frame elapsed {} now {} last {}", elapsed, now, lastms); + logger_frame->trace("Timer: Start frame elapsed {} now {} last {}", elapsed, now, lastms); if (elapsed < _timer.GetInterval() / 2) { @@ -1607,7 +1763,7 @@ void xScheduleFrame::On_timerTrigger(wxTimerEvent& event) wxCommandEvent event3(EVT_SLOWFRAMEPROCESSING); wxPostEvent(this, event3); - logger_frame->info("Timer: End Frame: Time {}", ms); + logger_frame->trace("Timer: End Frame: Time {}", ms); } void xScheduleFrame::UpdateSchedule() diff --git a/xSchedule/xScheduleMain.h b/xSchedule/xScheduleMain.h index 6dd82235..340a55c1 100644 --- a/xSchedule/xScheduleMain.h +++ b/xSchedule/xScheduleMain.h @@ -216,9 +216,13 @@ class xScheduleFrame : public xlFrame { void OnMenuItem_ResetWindowLocationsSelected(wxCommandEvent& event); void OnBitmapButton_BrightnessDownClick(wxCommandEvent& event); void OnBitmapButton_BrightnessUpClick(wxCommandEvent& event); + void OnMenuItem_UpdateSelected(wxCommandEvent& event); + void OnMenuItem_DonateSelected(wxCommandEvent& event); + void OnMenuItem_DownloadSelected(wxCommandEvent& event); //*) void SlowFrameProcessing(wxCommandEvent& event); + bool CheckForUpdate(bool showMessageBoxes); void OnCustom_BrightnessLeftDown(wxMouseEvent& event); void OnListView_PingMouseMove(wxMouseEvent& event); void OnListView_RunningItemSelected(wxListEvent& event); @@ -321,6 +325,9 @@ class xScheduleFrame : public xlFrame { static const wxWindowID MNU_CONFIGUREMIDITIMECODE; static const wxWindowID ID_MNU_CONFIGURE_TEST; static const wxWindowID idMenuAbout; + static const wxWindowID ID_MNU_UPDATE; + static const wxWindowID ID_MNU_DONATE; + static const wxWindowID ID_MNU_DOWNLOAD; static const wxWindowID ID_STATUSBAR1; static const wxWindowID ID_TIMER1; static const wxWindowID ID_TIMER2; @@ -400,6 +407,9 @@ class xScheduleFrame : public xlFrame { wxMenuItem* MenuItem_Save; wxMenuItem* MenuItem_ShowFolder; wxMenuItem* MenuItem_UsexLightsFolder; + wxMenuItem* MenuItem_Update; + wxMenuItem* MenuItem_Donate; + wxMenuItem* MenuItem_Download; wxMenuItem* MenuItem_ViewLog; wxMenuItem* MenuItem_VirtualMatrices; wxMenuItem* MenuItem_WebInterface;