From 62a59e6a608755110ecf4432feb39b844a966418 Mon Sep 17 00:00:00 2001 From: Alexander Yee Date: Fri, 29 Nov 2024 00:33:39 -0800 Subject: [PATCH] Refactor error reporting. --- Common/Cpp/AbstractLogger.h | 6 + Common/Cpp/Json/JsonValue.cpp | 9 +- Common/Cpp/Json/JsonValue.h | 2 +- SerialPrograms/CMakeLists.txt | 12 +- SerialPrograms/SerialPrograms.pro | 11 +- .../Source/CommonFramework/CrashDump.cpp | 114 ------- .../Source/CommonFramework/CrashDump.h | 17 - .../ErrorReports/ErrorReports.cpp | 293 ++++++++++++++++++ .../ErrorReports/ErrorReports.h | 98 ++++++ .../ErrorReports/ProgramDumper.cpp | 20 ++ .../ErrorReports/ProgramDumper.h | 21 ++ .../ErrorReports/ProgramDumper_Windows.tpp | 133 ++++++++ .../Exceptions/FatalProgramException.cpp | 14 +- .../Exceptions/OperationFailedException.cpp | 14 +- .../Exceptions/UnexpectedBattleException.cpp | 14 +- .../CommonFramework/GlobalSettingsPanel.cpp | 5 +- .../CommonFramework/GlobalSettingsPanel.h | 5 +- .../ImageMatch/ExactImageMatcher.h | 4 +- .../CommonFramework/ImageTypes/ImageRGB32.h | 2 +- .../Logging/FileWindowLogger.cpp | 17 + .../Logging/FileWindowLogger.h | 18 +- .../Source/CommonFramework/Main.cpp | 3 +- .../Notifications/ProgramNotifications.cpp | 16 - .../Notifications/ProgramNotifications.h | 10 - .../CommonFramework/Tools/ErrorDumper.cpp | 16 +- .../CommonFramework/Tools/ErrorDumper.h | 8 +- .../CommonFramework/Windows/MainWindow.cpp | 2 +- .../Source/Integrations/DiscordWebhook.cpp | 176 ++++++----- .../Source/Integrations/DiscordWebhook.h | 26 +- .../DevPrograms/TestProgramComputer.cpp | 29 ++ .../DevPrograms/TestProgramSwitch.cpp | 18 +- .../PokemonSwSh_MaxLair_Run_Adventure.cpp | 10 +- 32 files changed, 863 insertions(+), 280 deletions(-) delete mode 100644 SerialPrograms/Source/CommonFramework/CrashDump.cpp delete mode 100644 SerialPrograms/Source/CommonFramework/CrashDump.h create mode 100644 SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.cpp create mode 100644 SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.h create mode 100644 SerialPrograms/Source/CommonFramework/ErrorReports/ProgramDumper.cpp create mode 100644 SerialPrograms/Source/CommonFramework/ErrorReports/ProgramDumper.h create mode 100644 SerialPrograms/Source/CommonFramework/ErrorReports/ProgramDumper_Windows.tpp diff --git a/Common/Cpp/AbstractLogger.h b/Common/Cpp/AbstractLogger.h index 1c7fee3ab..77db0dd74 100644 --- a/Common/Cpp/AbstractLogger.h +++ b/Common/Cpp/AbstractLogger.h @@ -8,7 +8,9 @@ #define PokemonAutomation_AbstractLogger_H #include +#include #include "Color.h" +#include "Time.h" namespace PokemonAutomation{ @@ -22,6 +24,10 @@ class Logger{ virtual void log(const char* msg, Color color = Color()){ log(std::string(msg), color); } + + virtual std::vector get_last() const{ + return {}; + } }; diff --git a/Common/Cpp/Json/JsonValue.cpp b/Common/Cpp/Json/JsonValue.cpp index 633deec39..712425709 100644 --- a/Common/Cpp/Json/JsonValue.cpp +++ b/Common/Cpp/Json/JsonValue.cpp @@ -10,6 +10,10 @@ #include "JsonObject.h" #include "JsonTools.h" +//#include +//using std::cout; +//using std::endl; + namespace PokemonAutomation{ @@ -365,8 +369,9 @@ bool JsonValue::read_string(std::string& value) const{ JsonValue parse_json(const std::string& str){ return from_nlohmann(nlohmann::json::parse(str, nullptr, false)); } -JsonValue load_json_file(const std::string& str){ - return parse_json(file_to_string(str)); +JsonValue load_json_file(const std::string& filename){ + std::string str = file_to_string(filename); + return parse_json(str); } std::string JsonValue::dump(int indent) const{ return to_nlohmann(*this).dump(indent); diff --git a/Common/Cpp/Json/JsonValue.h b/Common/Cpp/Json/JsonValue.h index 5eaedaf51..5817fbe34 100644 --- a/Common/Cpp/Json/JsonValue.h +++ b/Common/Cpp/Json/JsonValue.h @@ -161,7 +161,7 @@ class JsonValue{ }; JsonValue parse_json(const std::string& str); -JsonValue load_json_file(const std::string& str); +JsonValue load_json_file(const std::string& filename); template diff --git a/SerialPrograms/CMakeLists.txt b/SerialPrograms/CMakeLists.txt index 2272b3388..a72a2eace 100644 --- a/SerialPrograms/CMakeLists.txt +++ b/SerialPrograms/CMakeLists.txt @@ -287,8 +287,6 @@ file(GLOB MAIN_SOURCES Source/CommonFramework/ControllerDevices/SerialPortSession.h Source/CommonFramework/ControllerDevices/SerialPortWidget.cpp Source/CommonFramework/ControllerDevices/SerialPortWidget.h - Source/CommonFramework/CrashDump.cpp - Source/CommonFramework/CrashDump.h Source/CommonFramework/Environment/Environment.cpp Source/CommonFramework/Environment/Environment.h Source/CommonFramework/Environment/Environment_Linux.h @@ -307,6 +305,11 @@ file(GLOB MAIN_SOURCES Source/CommonFramework/Environment/SystemSleep.h Source/CommonFramework/Environment/SystemSleep_Apple.tpp Source/CommonFramework/Environment/SystemSleep_Windows.tpp + Source/CommonFramework/ErrorReports/ErrorReports.cpp + Source/CommonFramework/ErrorReports/ErrorReports.h + Source/CommonFramework/ErrorReports/ProgramDumper.cpp + Source/CommonFramework/ErrorReports/ProgramDumper.h + Source/CommonFramework/ErrorReports/ProgramDumper_Windows.tpp Source/CommonFramework/Exceptions/FatalProgramException.cpp Source/CommonFramework/Exceptions/FatalProgramException.h Source/CommonFramework/Exceptions/OliveActionFailedException.cpp @@ -2080,11 +2083,12 @@ target_link_libraries(SerialPrograms PRIVATE Threads::Threads) #add defines target_compile_definitions(SerialPrograms PRIVATE NOMINMAX) -if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../Internal/SerialPrograms/TelemetryURLs.h") +if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../../Internal/SerialPrograms/Internal0.cpp") target_compile_definitions(SerialPrograms PRIVATE PA_OFFICIAL) - target_sources(SerialPrograms PRIVATE ../../Internal/SerialPrograms/TelemetryURLs.h) target_sources(SerialPrograms PRIVATE ../../Internal/SerialPrograms/NintendoSwitch_TestPrograms.cpp) target_sources(SerialPrograms PRIVATE ../../Internal/SerialPrograms/NintendoSwitch_TestPrograms.h) + target_sources(SerialPrograms PRIVATE ../../Internal/SerialPrograms/Internal0.cpp) + target_sources(SerialPrograms PRIVATE ../../Internal/SerialPrograms/Internal1.cpp) endif() #extract opencv_world460d.dll from archive on Windows Debug builds diff --git a/SerialPrograms/SerialPrograms.pro b/SerialPrograms/SerialPrograms.pro index 67483cfbf..69ff3b9aa 100644 --- a/SerialPrograms/SerialPrograms.pro +++ b/SerialPrograms/SerialPrograms.pro @@ -174,10 +174,11 @@ SOURCES += \ Source/CommonFramework/ControllerDevices/SerialPortOption.cpp \ Source/CommonFramework/ControllerDevices/SerialPortSession.cpp \ Source/CommonFramework/ControllerDevices/SerialPortWidget.cpp \ - Source/CommonFramework/CrashDump.cpp \ Source/CommonFramework/Environment/Environment.cpp \ Source/CommonFramework/Environment/HardwareValidation.cpp \ Source/CommonFramework/Environment/SystemSleep.cpp \ + Source/CommonFramework/ErrorReports/ErrorReports.cpp \ + Source/CommonFramework/ErrorReports/ProgramDumper.cpp \ Source/CommonFramework/Exceptions/FatalProgramException.cpp \ Source/CommonFramework/Exceptions/OliveActionFailedException.cpp \ Source/CommonFramework/Exceptions/OperationFailedException.cpp \ @@ -1234,7 +1235,6 @@ HEADERS += \ Source/CommonFramework/ControllerDevices/SerialPortOption.h \ Source/CommonFramework/ControllerDevices/SerialPortSession.h \ Source/CommonFramework/ControllerDevices/SerialPortWidget.h \ - Source/CommonFramework/CrashDump.h \ Source/CommonFramework/Environment/Environment.h \ Source/CommonFramework/Environment/Environment_Linux.h \ Source/CommonFramework/Environment/Environment_Linux.tpp \ @@ -1250,6 +1250,9 @@ HEADERS += \ Source/CommonFramework/Environment/SystemSleep.h \ Source/CommonFramework/Environment/SystemSleep_Apple.tpp \ Source/CommonFramework/Environment/SystemSleep_Windows.tpp \ + Source/CommonFramework/ErrorReports/ErrorReports.h \ + Source/CommonFramework/ErrorReports/ProgramDumper.h \ + Source/CommonFramework/ErrorReports/ProgramDumper_Windows.tpp \ Source/CommonFramework/Exceptions/FatalProgramException.h \ Source/CommonFramework/Exceptions/OliveActionFailedException.h \ Source/CommonFramework/Exceptions/OperationFailedException.h \ @@ -2137,10 +2140,12 @@ HEADERS += \ -exists(../../Internal/SerialPrograms/TelemetryURLs.h){ +exists(../../Internal/SerialPrograms/Internal0.cpp){ DEFINES += PA_OFFICIAL SOURCES += ../../Internal/SerialPrograms/NintendoSwitch_TestPrograms.cpp HEADERS += ../../Internal/SerialPrograms/NintendoSwitch_TestPrograms.h + SOURCES += ../../Internal/SerialPrograms/Internal0.cpp + SOURCES += ../../Internal/SerialPrograms/Internal1.cpp } diff --git a/SerialPrograms/Source/CommonFramework/CrashDump.cpp b/SerialPrograms/Source/CommonFramework/CrashDump.cpp deleted file mode 100644 index 1a2c3081b..000000000 --- a/SerialPrograms/Source/CommonFramework/CrashDump.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* Crash Dump - * - * From: https://github.com/PokemonAutomation/Arduino-Source - * - */ - -#include -#include -#include "Common/Cpp/Unicode.h" -#include "Common/Cpp/PrettyPrint.h" -//#include "ClientSource/Libraries/Logging.h" -#include "CrashDump.h" - -#include -using std::cout; -using std::endl; - - -// TODO: let this file use ERROR_PATH() declared in commonFrames/Globals.h. - - -#if _WIN32 && _MSC_VER -#pragma comment (lib, "Dbghelp.lib") -#include -#include -#include -namespace PokemonAutomation{ - - -long WINAPI crash_handler(EXCEPTION_POINTERS* e){ - static bool handled = false; - if (handled){ - return EXCEPTION_CONTINUE_SEARCH; - } - handled = true; - -// cout << "Oops... Program has crashed." << endl; -// cout << "Creating mini-dump file..." << endl; - - _wmkdir(L"ErrorDumps"); - - std::string filename = "ErrorDumps/SerialPrograms-"; - filename += now_to_filestring(); - - std::ofstream log; - log.open(filename + ".log"); - - log << "Oops... Program has crashed." << endl; - log << "Creating mini-dump file..." << endl; - - HANDLE handle = CreateFileW( - utf8_to_wstr(filename + ".dmp").c_str(), - FILE_WRITE_ACCESS, - FILE_SHARE_READ, - nullptr, - OPEN_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - 0 - ); - if (handle == INVALID_HANDLE_VALUE){ - DWORD error = GetLastError(); -// cout << "Unable to create dump file: " << error << endl; - log << "Unable to create dump file: " << error << endl; - return EXCEPTION_EXECUTE_HANDLER; - } - - MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; - exceptionInfo.ThreadId = GetCurrentThreadId(); - exceptionInfo.ExceptionPointers = e; - exceptionInfo.ClientPointers = FALSE; - - int ret = MiniDumpWriteDump( - GetCurrentProcess(), - GetCurrentProcessId(), - handle, - MiniDumpNormal, - e != nullptr ? &exceptionInfo : nullptr, - nullptr, - nullptr - ); - CloseHandle(handle); - - if (!ret){ - DWORD error = GetLastError(); -// cout << "Unable to create minidump: " << error << endl; - log << "Unable to create minidump: " << error << endl; - }else{ -// cout << "Minidump created!" << endl; - log << "Minidump created!" << endl; - } - - Sleep(1000); - - return EXCEPTION_CONTINUE_SEARCH; -} - - - -void setup_crash_handler(){ -// AddVectoredExceptionHandler(0, crash_handler); - SetUnhandledExceptionFilter(crash_handler); -} - - -} -#else -namespace PokemonAutomation{ - -void setup_crash_handler(){ - // Not supported -} - -} -#endif diff --git a/SerialPrograms/Source/CommonFramework/CrashDump.h b/SerialPrograms/Source/CommonFramework/CrashDump.h deleted file mode 100644 index 191871701..000000000 --- a/SerialPrograms/Source/CommonFramework/CrashDump.h +++ /dev/null @@ -1,17 +0,0 @@ -/* Crash Dump - * - * From: https://github.com/PokemonAutomation/Arduino-Source - * - */ - -#ifndef PokemonAutomation_CrashDump_H -#define PokemonAutomation_CrashDump_H - -namespace PokemonAutomation{ - - -void setup_crash_handler(); - - -} -#endif diff --git a/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.cpp b/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.cpp new file mode 100644 index 000000000..e52397d02 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.cpp @@ -0,0 +1,293 @@ +/* Error Reports + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#include +#include +#include "Common/Cpp/PrettyPrint.h" +#include "Common/Cpp/Json/JsonArray.h" +#include "Common/Cpp/Json/JsonObject.h" +#include "CommonFramework/Globals.h" +#include "CommonFramework/GlobalSettingsPanel.h" +#include "CommonFramework/Logging/Logger.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "ProgramDumper.h" +#include "ErrorReports.h" + +// REMOVE +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ + + + +const std::string& ERROR_LOGS_NAME = "Logs.log"; +const std::string& ERROR_DUMP_NAME = "Minidump.dmp"; +const std::string& ERROR_PATH_UNSENT = "ErrorReportsLocal"; +const std::string& ERROR_PATH_SENT = "ErrorReportsSent"; + + + +ErrorReportOption::ErrorReportOption() + : GroupOption("Error Reports", LockMode::UNLOCK_WHILE_RUNNING, true) + , DESCRIPTION( + "Send error reports to the " + PROGRAM_NAME + " server to help them resolve issues and improve the program." + ) + , SCREENSHOT( + "Include Screenshots:", + LockMode::UNLOCK_WHILE_RUNNING, + true + ) + , LOGS( + "Include Logs:
Include the recent log leading up to the error.", + LockMode::UNLOCK_WHILE_RUNNING, + true + ) + , DUMPS( + "Include Dumps:
This saves stack-trace and related information.", + LockMode::UNLOCK_WHILE_RUNNING, + true + ) +#if 0 + , FILES( + "Include Other Files:
Include other files that may be helpful for the developers.", + LockMode::UNLOCK_WHILE_RUNNING, + true + ) +#endif +{ + PA_ADD_STATIC(DESCRIPTION); + PA_ADD_OPTION(SCREENSHOT); + PA_ADD_OPTION(LOGS); + PA_ADD_OPTION(DUMPS); +// PA_ADD_OPTION(FILES); +} + + +SendableErrorReport::SendableErrorReport() + : m_timestamp(now_to_filestring()) + , m_directory(ERROR_PATH_UNSENT + "/" + m_timestamp + "/") + , m_program(PreloadSettings::instance().DEVELOPER_MODE + ? PROGRAM_NAME + " (" + PROGRAM_VERSION + "-dev)" + : PROGRAM_NAME + " (" + PROGRAM_VERSION + ")" + ) +{ + QDir().mkdir(QString::fromStdString(m_directory)); +} +SendableErrorReport::SendableErrorReport( + Logger* logger, + const ProgramInfo& info, + std::string title, + std::vector> messages, + const ImageViewRGB32& image +) + : SendableErrorReport() +{ + if (logger){ + logger->log("Compiling Error Report..."); + }else{ + std::cout << "Compiling Error Report..." << std::endl; + } + m_program_id = info.program_id; + m_program_runtime_millis = std::chrono::duration_cast(current_time() - info.start_time).count(); + m_title = std::move(title); + m_messages = std::move(messages); + m_image = image; + { + std::string log; + for (const std::string& line : global_logger_raw().get_last()){ + log += line; + log += "\r\n"; + } + QFile file(QString::fromStdString(m_directory + ERROR_LOGS_NAME)); + bool exists = file.exists(); + file.open(QIODevice::WriteOnly); + if (!exists){ + std::string bom = "\xef\xbb\xbf"; + file.write(bom.c_str(), bom.size()); + } + file.write(log.c_str()); + file.flush(); + m_files.emplace_back(ERROR_LOGS_NAME); + } + if (program_dump(logger, m_directory + ERROR_DUMP_NAME)){ + m_files.emplace_back(ERROR_DUMP_NAME); + } +} + +SendableErrorReport::SendableErrorReport(std::string directory) + : m_directory(std::move(directory)) +{ + if (m_directory.back() != '/'){ + m_directory += '/'; + } + + try{ + m_image_owner = ImageRGB32(m_directory + "Image.png"); + m_image = m_image_owner; + }catch (FileException&){} + + JsonValue json = load_json_file(m_directory + "Report.json"); + const JsonObject& obj = json.to_object_throw(); + m_timestamp = obj.get_string_throw("Timestamp"); + m_program = obj.get_string_throw("Program"); + m_program_id = obj.get_string_throw("ProgramID"); + m_program_runtime_millis = obj.get_integer_throw("ElapsedTimeMillis"); + m_title = obj.get_string_throw("Title"); + { + const JsonArray& messages = obj.get_array_throw("Messages"); + for (const JsonValue& message : messages){ + const JsonArray& item = message.to_array_throw(); + if (item.size() != 2){ + throw ParseException("Expected 2 values for message."); + } + m_messages.emplace_back( + item[0].to_string_throw(), + item[1].to_string_throw() + ); + } + } + { + const JsonArray& files = obj.get_array_throw("Files"); + for (const JsonValue& file : files){ + m_files.emplace_back(file.to_string_throw()); + } + } +} +void SendableErrorReport::add_file(std::string filename){ + m_files.emplace_back(std::move(filename)); +} + +void SendableErrorReport::save(Logger* logger) const{ + if (logger){ + logger->log("Saving Error Report..."); + }else{ + std::cout << "Saving Error Report..." << std::endl; + } + + JsonObject report; + + report["Timestamp"] = m_timestamp; + report["Program"] = m_program; + report["ProgramID"] = m_program_id; + report["ElapsedTimeMillis"] = m_program_runtime_millis; + report["Title"] = m_title; + { + JsonArray messages; + for (const std::pair& item : m_messages){ + JsonArray array; + array.push_back(item.first); + array.push_back(item.second); + messages.push_back(std::move(array)); + } + report["Messages"] = std::move(messages); + } + + m_image.save(m_directory + "Image.png"); + + JsonArray array; + for (const std::string& file : m_files){ + array.push_back(file); + } + report["Files"] = std::move(array); + + report.dump(m_directory + "Report.json"); +} + +#ifndef PA_OFFICIAL +bool SendableErrorReport::send(Logger& logger){ + return false; +} +#endif + +void SendableErrorReport::move_to_sent(){ +// cout << "move_to_sent()" << endl; + QDir().mkdir(QString::fromStdString(ERROR_PATH_SENT)); + + std::string new_directory = ERROR_PATH_SENT + "/" + m_timestamp + "/"; +// cout << "old: " << m_directory << endl; +// cout << "new: " << new_directory << endl; + QDir().rename( + QString::fromStdString(m_directory), + QString::fromStdString(new_directory) + ); + m_directory = std::move(new_directory); +} + + +std::vector SendableErrorReport::get_pending_reports(){ + std::vector ret; + QDir dir(QString::fromStdString(ERROR_PATH_UNSENT)); + dir.setFilter(QDir::Filter::AllDirs); + QFileInfoList list = dir.entryInfoList(); + for (const auto& item : list){ + std::string path = item.filePath().toStdString(); + if (path.back() == '.'){ + continue; + } + ret.emplace_back(std::move(path)); + } + return ret; +} +void SendableErrorReport::send_reports(Logger& logger, const std::vector& reports){ + for (const std::string& path : reports){ + try{ + SendableErrorReport report(path); + report.send(logger); + }catch (Exception& e){ + logger.log("Unable to send report: " + path + ", Message: " + e.to_str(), COLOR_RED); + }catch (...){ + logger.log("Unable to send report: " + path, COLOR_RED); + } + } +} +void SendableErrorReport::send_all_unsent_reports(Logger& logger){ +#ifdef PA_OFFICIAL + if (GlobalSettings::instance().ERROR_REPORTS.enabled()){ + std::vector reports = SendableErrorReport::get_pending_reports(); + global_logger_tagged().log("Found " + std::to_string(reports.size()) + " unsent error reports. Attempting to send...", COLOR_PURPLE); + SendableErrorReport::send_reports(global_logger_tagged(), reports); + } +#endif +} + +void report_error( + Logger* logger, + const ProgramInfo& info, + std::string title, + std::vector> messages, + const ImageViewRGB32& image, + const std::vector& files +){ + if (logger == nullptr){ + logger = &global_logger_tagged(); + } + + { + SendableErrorReport report( + logger, + info, + std::move(title), + std::move(messages), + image + ); + + std::vector full_file_paths; + for (const std::string& file: files){ + full_file_paths.emplace_back(report.directory() + file); + } + + report.save(logger); + } + + SendableErrorReport::send_all_unsent_reports(*logger); +} + + + + +} diff --git a/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.h b/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.h new file mode 100644 index 000000000..136024bf0 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ErrorReports/ErrorReports.h @@ -0,0 +1,98 @@ +/* Error Reports + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#ifndef PokemonAutomation_ErrorReports_H +#define PokemonAutomation_ErrorReports_H + +#include "Common/Cpp/AbstractLogger.h" +#include "Common/Cpp/Options/GroupOption.h" +#include "Common/Cpp/Options/StaticTextOption.h" +#include "Common/Cpp/Options/BooleanCheckBoxOption.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/Notifications/ProgramInfo.h" + +namespace PokemonAutomation{ + + +extern const std::string& ERROR_LOGS_NAME; +extern const std::string& ERROR_DUMP_NAME; +extern const std::string& ERROR_PATH_UNSENT; +extern const std::string& ERROR_PATH_SENT; + + + +class ErrorReportOption : public GroupOption{ +public: + ErrorReportOption(); + + StaticTextOption DESCRIPTION; + BooleanCheckBoxOption SCREENSHOT; + BooleanCheckBoxOption LOGS; + BooleanCheckBoxOption DUMPS; +// BooleanCheckBoxOption FILES; +}; + + + +class SendableErrorReport{ +public: + SendableErrorReport(); + + // Create new report from current stack. + SendableErrorReport( + Logger* logger, + const ProgramInfo& info = ProgramInfo(), + std::string title = "", + std::vector> messages = {}, + const ImageViewRGB32& image = ImageViewRGB32() + ); + + // Deserialize from existing report. + SendableErrorReport(std::string directory); + + const std::string& directory() const{ + return m_directory; + } + + void add_file(std::string filename); + + void save(Logger* logger) const; + bool send(Logger& logger); + void move_to_sent(); + + static std::vector get_pending_reports(); + static void send_reports(Logger& logger, const std::vector& reports); + static void send_all_unsent_reports(Logger& logger); + +private: + std::string m_timestamp; + std::string m_directory; + std::string m_program; + std::string m_program_id; + uint64_t m_program_runtime_millis; + std::string m_title; + std::vector> m_messages; + ImageRGB32 m_image_owner; + ImageViewRGB32 m_image; + std::vector m_files; +}; + + + +void report_error( + Logger* logger = nullptr, + const ProgramInfo& info = ProgramInfo(), + std::string title = "", + std::vector> messages = {}, + const ImageViewRGB32& image = ImageViewRGB32(), + const std::vector& files = {} +); + + +} + +#endif diff --git a/SerialPrograms/Source/CommonFramework/ErrorReports/ProgramDumper.cpp b/SerialPrograms/Source/CommonFramework/ErrorReports/ProgramDumper.cpp new file mode 100644 index 000000000..473fc9d25 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ErrorReports/ProgramDumper.cpp @@ -0,0 +1,20 @@ +/* Program Dumper + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#include "ProgramDumper.h" + + +#if _WIN32 && _MSC_VER +#include "ProgramDumper_Windows.tpp" + +#else +void setup_crash_handler(){ + // Not supported +} +bool program_dump(Logger* logger, const std::string& filename){ + +} +#endif diff --git a/SerialPrograms/Source/CommonFramework/ErrorReports/ProgramDumper.h b/SerialPrograms/Source/CommonFramework/ErrorReports/ProgramDumper.h new file mode 100644 index 000000000..418511178 --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ErrorReports/ProgramDumper.h @@ -0,0 +1,21 @@ +/* Program Dumper + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#ifndef PokemonAutomation_ProgramDumper_H +#define PokemonAutomation_ProgramDumper_H + +#include +#include "Common/Cpp/AbstractLogger.h" + +namespace PokemonAutomation{ + + +void setup_crash_handler(); +bool program_dump(Logger* logger, const std::string& filename); + + +} +#endif diff --git a/SerialPrograms/Source/CommonFramework/ErrorReports/ProgramDumper_Windows.tpp b/SerialPrograms/Source/CommonFramework/ErrorReports/ProgramDumper_Windows.tpp new file mode 100644 index 000000000..4cbd6c37f --- /dev/null +++ b/SerialPrograms/Source/CommonFramework/ErrorReports/ProgramDumper_Windows.tpp @@ -0,0 +1,133 @@ +/* Program Dumper (Windows) + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#pragma comment (lib, "Dbghelp.lib") +#include +#include +#include +#include +#include "Common/Cpp/Unicode.h" +#include "Common/Cpp/PrettyPrint.h" +#include "ErrorReports.h" +#include "ProgramDumper.h" + +namespace PokemonAutomation{ + + +class HandleHolder{ +public: + HandleHolder(const HandleHolder&) = delete; + void operator=(const HandleHolder&) = delete; + ~HandleHolder(){ + CloseHandle(m_handle); + } + HandleHolder(HANDLE handle) + : m_handle(handle) + {} + operator bool() const{ + return m_handle != INVALID_HANDLE_VALUE; + } + operator HANDLE() const{ + return m_handle; + } + +private: + HANDLE m_handle; +}; + + +bool program_dump(Logger* logger, const std::string& filename, EXCEPTION_POINTERS* e){ + HandleHolder handle = CreateFileW( + utf8_to_wstr(filename).c_str(), + FILE_WRITE_ACCESS, + FILE_SHARE_READ, + nullptr, + OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + 0 + ); + if (!handle){ + DWORD error = GetLastError(); + if (logger){ + logger->log("Unable to create dump file: " + std::to_string(error), COLOR_RED); + }else{ + std::cout << "Unable to create dump file: " << error << std::endl; + } + return false; + } + + int ret; + if (e != nullptr){ + MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; + exceptionInfo.ThreadId = GetCurrentThreadId(); + exceptionInfo.ExceptionPointers = e; + exceptionInfo.ClientPointers = FALSE; + ret = MiniDumpWriteDump( + GetCurrentProcess(), + GetCurrentProcessId(), + handle, + MiniDumpNormal, + &exceptionInfo, + nullptr, + nullptr + ); + }else{ + ret = MiniDumpWriteDump( + GetCurrentProcess(), + GetCurrentProcessId(), + handle, + MiniDumpNormal, + nullptr, + nullptr, + nullptr + ); + } + if (!ret){ + DWORD error = GetLastError(); + if (logger){ + logger->log("Unable to create dump file: " + std::to_string(error), COLOR_RED); + }else{ + std::cout << "Unable to create dump file: " << error << std::endl; + } + return false; + } + + return true; +} +bool program_dump(Logger* logger, const std::string& filename){ + return program_dump(logger, filename, nullptr); +} + + + +long WINAPI crash_handler(EXCEPTION_POINTERS* e){ + static bool handled = false; + if (handled){ + return EXCEPTION_CONTINUE_SEARCH; + } + handled = true; + + SendableErrorReport report; + +// _wmkdir(utf8_to_wstr(ERROR_PATH_UNSENT).c_str()); + program_dump(nullptr, report.directory() + ERROR_DUMP_NAME, e); + report.add_file(ERROR_DUMP_NAME); + report.save(nullptr); + + Sleep(1000); + + return EXCEPTION_CONTINUE_SEARCH; +} + + +void setup_crash_handler(){ +// AddVectoredExceptionHandler(0, crash_handler); + SetUnhandledExceptionFilter(crash_handler); +} + + + +} diff --git a/SerialPrograms/Source/CommonFramework/Exceptions/FatalProgramException.cpp b/SerialPrograms/Source/CommonFramework/Exceptions/FatalProgramException.cpp index 2b872a0fa..e89849ebd 100644 --- a/SerialPrograms/Source/CommonFramework/Exceptions/FatalProgramException.cpp +++ b/SerialPrograms/Source/CommonFramework/Exceptions/FatalProgramException.cpp @@ -6,7 +6,8 @@ #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/Notifications/ProgramNotifications.h" -#include "CommonFramework/Tools/ErrorDumper.h" +#include "CommonFramework/ErrorReports/ErrorReports.h" +//#include "CommonFramework/Tools/ErrorDumper.h" #include "CommonFramework/Tools/ProgramEnvironment.h" #include "CommonFramework/Tools/ConsoleHandle.h" #include "FatalProgramException.h" @@ -39,7 +40,15 @@ void FatalProgramException::send_notification(ProgramEnvironment& env, EventNoti if (!m_message.empty()){ embeds.emplace_back(std::pair("Message:", m_message)); } - if (m_send_error_report == ErrorReport::SEND_ERROR_REPORT && m_screenshot){ + if (m_send_error_report == ErrorReport::SEND_ERROR_REPORT){ + report_error( + &env.logger(), + env.program_info(), + name(), + embeds, + screenshot() + ); +#if 0 std::string label = name(); std::string filename = dump_image_alone(env.logger(), env.program_info(), label, *m_screenshot); send_program_telemetry( @@ -49,6 +58,7 @@ void FatalProgramException::send_notification(ProgramEnvironment& env, EventNoti embeds, filename ); +#endif } send_program_notification( env, notification, diff --git a/SerialPrograms/Source/CommonFramework/Exceptions/OperationFailedException.cpp b/SerialPrograms/Source/CommonFramework/Exceptions/OperationFailedException.cpp index 4dcc4be56..23882ebb7 100644 --- a/SerialPrograms/Source/CommonFramework/Exceptions/OperationFailedException.cpp +++ b/SerialPrograms/Source/CommonFramework/Exceptions/OperationFailedException.cpp @@ -6,7 +6,8 @@ #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/Notifications/ProgramNotifications.h" -#include "CommonFramework/Tools/ErrorDumper.h" +#include "CommonFramework/ErrorReports/ErrorReports.h" +//#include "CommonFramework/Tools/ErrorDumper.h" #include "CommonFramework/Tools/ProgramEnvironment.h" #include "CommonFramework/Tools/ConsoleHandle.h" #include "OperationFailedException.h" @@ -36,7 +37,15 @@ void OperationFailedException::send_notification(ProgramEnvironment& env, EventN if (!m_message.empty()){ embeds.emplace_back(std::pair("Message:", m_message)); } - if (m_send_error_report == ErrorReport::SEND_ERROR_REPORT && m_screenshot){ + if (m_send_error_report == ErrorReport::SEND_ERROR_REPORT){ + report_error( + &env.logger(), + env.program_info(), + name(), + embeds, + screenshot() + ); +#if 0 std::string label = name(); std::string filename = dump_image_alone(env.logger(), env.program_info(), label, *m_screenshot); send_program_telemetry( @@ -46,6 +55,7 @@ void OperationFailedException::send_notification(ProgramEnvironment& env, EventN embeds, filename ); +#endif } send_program_notification( env, notification, diff --git a/SerialPrograms/Source/CommonFramework/Exceptions/UnexpectedBattleException.cpp b/SerialPrograms/Source/CommonFramework/Exceptions/UnexpectedBattleException.cpp index 18aa05a60..3664b184d 100644 --- a/SerialPrograms/Source/CommonFramework/Exceptions/UnexpectedBattleException.cpp +++ b/SerialPrograms/Source/CommonFramework/Exceptions/UnexpectedBattleException.cpp @@ -6,7 +6,8 @@ #include "CommonFramework/ImageTypes/ImageRGB32.h" #include "CommonFramework/Notifications/ProgramNotifications.h" -#include "CommonFramework/Tools/ErrorDumper.h" +#include "CommonFramework/ErrorReports/ErrorReports.h" +//#include "CommonFramework/Tools/ErrorDumper.h" #include "CommonFramework/Tools/ProgramEnvironment.h" #include "CommonFramework/Tools/ConsoleHandle.h" #include "UnexpectedBattleException.h" @@ -36,7 +37,15 @@ void UnexpectedBattleException::send_notification(ProgramEnvironment& env, Event if (!m_message.empty()){ embeds.emplace_back(std::pair("Message:", m_message)); } - if (m_send_error_report == ErrorReport::SEND_ERROR_REPORT && m_screenshot){ + if (m_send_error_report == ErrorReport::SEND_ERROR_REPORT){ + report_error( + &env.logger(), + env.program_info(), + name(), + embeds, + screenshot() + ); +#if 0 std::string label = name(); std::string filename = dump_image_alone(env.logger(), env.program_info(), label, *m_screenshot); send_program_telemetry( @@ -46,6 +55,7 @@ void UnexpectedBattleException::send_notification(ProgramEnvironment& env, Event embeds, filename ); +#endif } send_program_notification( env, notification, diff --git a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp index 0730c51ab..82be0fa23 100644 --- a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp +++ b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.cpp @@ -218,12 +218,14 @@ GlobalSettings::GlobalSettings() LockMode::UNLOCK_WHILE_RUNNING, IS_BETA_VERSION ) +#if 0 , SEND_ERROR_REPORTS0( "Send Error Reports:
" "Send error reports to the " + PROGRAM_NAME + " server to help them resolve issues and improve the program.", LockMode::LOCK_WHILE_RUNNING, true ) +#endif , DEVELOPER_TOKEN( true, "Developer Token:
Restart application to take full effect after changing this.", @@ -272,7 +274,8 @@ GlobalSettings::GlobalSettings() PA_ADD_OPTION(PROCESSOR_LEVEL0); #ifdef PA_OFFICIAL - PA_ADD_OPTION(SEND_ERROR_REPORTS0); +// PA_ADD_OPTION(SEND_ERROR_REPORTS0); + PA_ADD_OPTION(ERROR_REPORTS); #endif PA_ADD_OPTION(DEVELOPER_TOKEN); diff --git a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h index b4f5a5a60..e1162396c 100644 --- a/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h +++ b/SerialPrograms/Source/CommonFramework/GlobalSettingsPanel.h @@ -17,6 +17,7 @@ #include "CommonFramework/Options/Environment/ProcessorLevelOption.h" #include "CommonFramework/Options/Environment/ThemeSelectorOption.h" #include "CommonFramework/Options/Environment/SleepSuppressOption.h" +#include "CommonFramework/ErrorReports/ErrorReports.h" #include "CommonFramework/VideoPipeline/Backends/CameraImplementations.h" #include "CommonFramework/Panels/SettingsPanel.h" #include "CommonFramework/Panels/PanelTools.h" @@ -44,7 +45,6 @@ class ResolutionOption : public GroupOption{ }; - struct DebugSettings{ bool COLOR_CHECK = false; bool IMAGE_TEMPLATE_MATCHING = false; @@ -121,7 +121,8 @@ class GlobalSettings : public BatchOption, private ConfigOption::Listener{ BooleanCheckBoxOption ENABLE_LIFETIME_SANITIZER; - BooleanCheckBoxOption SEND_ERROR_REPORTS0; +// BooleanCheckBoxOption SEND_ERROR_REPORTS0; + ErrorReportOption ERROR_REPORTS; ProcessorLevelOption PROCESSOR_LEVEL0; diff --git a/SerialPrograms/Source/CommonFramework/ImageMatch/ExactImageMatcher.h b/SerialPrograms/Source/CommonFramework/ImageMatch/ExactImageMatcher.h index 11666ac8b..ee2ac369c 100644 --- a/SerialPrograms/Source/CommonFramework/ImageMatch/ExactImageMatcher.h +++ b/SerialPrograms/Source/CommonFramework/ImageMatch/ExactImageMatcher.h @@ -8,7 +8,6 @@ #define PokemonAutomation_ExactImageMatcher_H #include "CommonFramework/ImageTypes/ImageRGB32.h" -#include "CommonFramework/ImageTools/FloatPixel.h" #include "CommonFramework/ImageTools/ImageStats.h" namespace PokemonAutomation{ @@ -26,6 +25,9 @@ class ExactImageMatcher{ void operator=(const ExactImageMatcher&) = delete; public: + ExactImageMatcher(const std::string& image_template) + : ExactImageMatcher(ImageRGB32(image_template)) + {} ExactImageMatcher(ImageRGB32 image_template); const ImageStats& stats() const{ return m_stats; } diff --git a/SerialPrograms/Source/CommonFramework/ImageTypes/ImageRGB32.h b/SerialPrograms/Source/CommonFramework/ImageTypes/ImageRGB32.h index 059d904e8..1afc16967 100644 --- a/SerialPrograms/Source/CommonFramework/ImageTypes/ImageRGB32.h +++ b/SerialPrograms/Source/CommonFramework/ImageTypes/ImageRGB32.h @@ -28,7 +28,7 @@ class ImageRGB32 : public ImageViewRGB32{ public: ImageRGB32(); ImageRGB32(size_t width, size_t height); - ImageRGB32(const std::string& filename); + explicit ImageRGB32(const std::string& filename); // Fill the entire image with the specified pixel. using ImageViewPlanar32::fill; diff --git a/SerialPrograms/Source/CommonFramework/Logging/FileWindowLogger.cpp b/SerialPrograms/Source/CommonFramework/Logging/FileWindowLogger.cpp index 200952a97..0c0b37fff 100644 --- a/SerialPrograms/Source/CommonFramework/Logging/FileWindowLogger.cpp +++ b/SerialPrograms/Source/CommonFramework/Logging/FileWindowLogger.cpp @@ -25,6 +25,17 @@ Logger& global_logger_raw(){ } +void LastLogTracker::operator+=(std::string line){ + m_lines.emplace_back(std::move(line)); + while (m_lines.size() > m_max_lines){ + m_lines.pop_front(); + } +} +std::vector LastLogTracker::snapshot() const{ + return std::vector(m_lines.begin(), m_lines.end()); +} + + FileWindowLogger::~FileWindowLogger(){ { std::lock_guard lg(m_lock); @@ -57,16 +68,22 @@ void FileWindowLogger::operator-=(FileWindowLoggerWindow& widget){ void FileWindowLogger::log(const std::string& msg, Color color){ std::unique_lock lg(m_lock); + m_last_log_tracker += msg; m_cv.wait(lg, [this]{ return m_queue.size() < m_max_queue_size; }); m_queue.emplace_back(msg, color); m_cv.notify_all(); } void FileWindowLogger::log(std::string&& msg, Color color){ std::unique_lock lg(m_lock); + m_last_log_tracker += msg; m_cv.wait(lg, [this]{ return m_queue.size() < m_max_queue_size; }); m_queue.emplace_back(std::move(msg), color); m_cv.notify_all(); } +std::vector FileWindowLogger::get_last() const{ + std::unique_lock lg(m_lock); + return m_last_log_tracker.snapshot(); +} std::string FileWindowLogger::normalize_newlines(const std::string& msg){ diff --git a/SerialPrograms/Source/CommonFramework/Logging/FileWindowLogger.h b/SerialPrograms/Source/CommonFramework/Logging/FileWindowLogger.h index 98a3957dd..57c795bf9 100644 --- a/SerialPrograms/Source/CommonFramework/Logging/FileWindowLogger.h +++ b/SerialPrograms/Source/CommonFramework/Logging/FileWindowLogger.h @@ -22,6 +22,20 @@ namespace PokemonAutomation{ class FileWindowLoggerWindow; +class LastLogTracker{ +public: + LastLogTracker(size_t max_lines = 10000) + : m_max_lines(max_lines) + {} + void operator+=(std::string line); + std::vector snapshot() const; + +private: + size_t m_max_lines; + std::deque m_lines; +}; + + class FileWindowLogger : public Logger{ public: ~FileWindowLogger(); @@ -32,6 +46,7 @@ class FileWindowLogger : public Logger{ virtual void log(const std::string& msg, Color color = Color()) override; virtual void log(std::string&& msg, Color color = Color()) override; + virtual std::vector get_last() const override; private: static std::string normalize_newlines(const std::string& msg); @@ -44,8 +59,9 @@ class FileWindowLogger : public Logger{ private: QFile m_file; size_t m_max_queue_size; - std::mutex m_lock; + mutable std::mutex m_lock; std::condition_variable m_cv; + LastLogTracker m_last_log_tracker; bool m_stopping; std::deque> m_queue; std::set m_windows; diff --git a/SerialPrograms/Source/CommonFramework/Main.cpp b/SerialPrograms/Source/CommonFramework/Main.cpp index 6bc582620..1987bc3e8 100644 --- a/SerialPrograms/Source/CommonFramework/Main.cpp +++ b/SerialPrograms/Source/CommonFramework/Main.cpp @@ -10,7 +10,7 @@ #include "Common/Cpp/ImageResolution.h" #include "PersistentSettings.h" #include "Tests/CommandLineTests.h" -#include "CrashDump.h" +#include "ErrorReports/ProgramDumper.h" #include "Environment/HardwareValidation.h" #include "Logging/Logger.h" #include "Logging/OutputRedirector.h" @@ -109,6 +109,7 @@ int main(int argc, char *argv[]){ } set_working_directory(); + SendableErrorReport::send_all_unsent_reports(global_logger_tagged()); int ret = 0; { diff --git a/SerialPrograms/Source/CommonFramework/Notifications/ProgramNotifications.cpp b/SerialPrograms/Source/CommonFramework/Notifications/ProgramNotifications.cpp index a13329529..a42715457 100644 --- a/SerialPrograms/Source/CommonFramework/Notifications/ProgramNotifications.cpp +++ b/SerialPrograms/Source/CommonFramework/Notifications/ProgramNotifications.cpp @@ -397,20 +397,4 @@ void send_program_fatal_error_notification( - - - - -#ifndef PA_OFFICIAL -void send_program_telemetry( - Logger& logger, bool is_error, Color color, - const ProgramInfo& info, - const std::string& title, - const std::vector>& messages, - const std::string& file -){} -#endif - - - } diff --git a/SerialPrograms/Source/CommonFramework/Notifications/ProgramNotifications.h b/SerialPrograms/Source/CommonFramework/Notifications/ProgramNotifications.h index 07d90ae19..9fba222b2 100644 --- a/SerialPrograms/Source/CommonFramework/Notifications/ProgramNotifications.h +++ b/SerialPrograms/Source/CommonFramework/Notifications/ProgramNotifications.h @@ -90,15 +90,5 @@ void send_program_fatal_error_notification( - -void send_program_telemetry( - Logger& logger, bool is_error, Color color, - const ProgramInfo& info, - const std::string& title, - const std::vector>& messages, - const std::string& file -); - - } #endif diff --git a/SerialPrograms/Source/CommonFramework/Tools/ErrorDumper.cpp b/SerialPrograms/Source/CommonFramework/Tools/ErrorDumper.cpp index 9b9e4610c..bce3052dd 100644 --- a/SerialPrograms/Source/CommonFramework/Tools/ErrorDumper.cpp +++ b/SerialPrograms/Source/CommonFramework/Tools/ErrorDumper.cpp @@ -13,6 +13,7 @@ //#include "CommonFramework/Notifications/EventNotificationOption.h" #include "CommonFramework/Notifications/ProgramInfo.h" #include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonFramework/ErrorReports/ErrorReports.h" #include "CommonFramework/VideoPipeline/VideoFeed.h" //#include "CommonFramework/VideoPipeline/VideoOverlay.h" #include "ConsoleHandle.h" @@ -39,11 +40,19 @@ std::string dump_image_alone( image.save(name); return name; } -std::string dump_image( +void dump_image( Logger& logger, const ProgramInfo& program_info, const std::string& label, const ImageViewRGB32& image ){ + report_error( + &logger, + program_info, + label, + {}, + image + ); +#if 0 std::string name = dump_image_alone(logger, program_info, label, image); send_program_telemetry( logger, true, COLOR_RED, @@ -53,14 +62,15 @@ std::string dump_image( name ); return name; +#endif } -std::string dump_image( +void dump_image( const ProgramInfo& program_info, ConsoleHandle& console, const std::string& label ){ auto snapshot = console.video().snapshot(); - return dump_image(console, program_info, label, snapshot); + dump_image(console, program_info, label, snapshot); } #if 0 diff --git a/SerialPrograms/Source/CommonFramework/Tools/ErrorDumper.h b/SerialPrograms/Source/CommonFramework/Tools/ErrorDumper.h index fb2a39395..939e60421 100644 --- a/SerialPrograms/Source/CommonFramework/Tools/ErrorDumper.h +++ b/SerialPrograms/Source/CommonFramework/Tools/ErrorDumper.h @@ -20,19 +20,21 @@ struct VideoSnapshot; class ProgramEnvironment; struct ProgramInfo; +#if 0 std::string dump_image_alone( Logger& logger, const ProgramInfo& program_info, const std::string& label, const ImageViewRGB32& image ); +#endif + // Dump error image to ./ErrorDumps/ folder. Also send image as telemetry if user allows. -// Return image path. -std::string dump_image( +void dump_image( Logger& logger, const ProgramInfo& program_info, const std::string& label, const ImageViewRGB32& image ); -std::string dump_image( +void dump_image( const ProgramInfo& program_info, ConsoleHandle& console, const std::string& label diff --git a/SerialPrograms/Source/CommonFramework/Windows/MainWindow.cpp b/SerialPrograms/Source/CommonFramework/Windows/MainWindow.cpp index 05cb1f0e7..2f3cea4f5 100644 --- a/SerialPrograms/Source/CommonFramework/Windows/MainWindow.cpp +++ b/SerialPrograms/Source/CommonFramework/Windows/MainWindow.cpp @@ -124,7 +124,7 @@ MainWindow::MainWindow(QWidget* parent) str += "Copyright: 2020 - 2025
"; str += "Version: " + PROGRAM_VERSION + "
"; str += "
"; - str += "Framwork: Qt " + std::to_string(QT_VERSION_MAJOR); + str += "Framework: Qt " + std::to_string(QT_VERSION_MAJOR); str += "." + std::to_string(QT_VERSION_MINOR); str += "." + std::to_string(QT_VERSION_PATCH); str += "
"; diff --git a/SerialPrograms/Source/Integrations/DiscordWebhook.cpp b/SerialPrograms/Source/Integrations/DiscordWebhook.cpp index 3451a49fb..5336d6c9b 100644 --- a/SerialPrograms/Source/Integrations/DiscordWebhook.cpp +++ b/SerialPrograms/Source/Integrations/DiscordWebhook.cpp @@ -52,41 +52,44 @@ DiscordWebhookSender& DiscordWebhookSender::instance(){ return sender; } -void DiscordWebhookSender::send_json( +void DiscordWebhookSender::send( Logger& logger, const QUrl& url, std::chrono::milliseconds delay, const JsonObject& obj, std::shared_ptr file ){ cleanup_stuck_requests(); + std::shared_ptr json(new JsonValue(obj.clone())); m_queue.add_event( delay, - [this, url, data = QByteArray::fromStdString(obj.dump()), file = std::move(file)]{ + [this, url, json = std::move(json), file = std::move(file)]{ throttle(); - if (!file && !data.isEmpty()){ - internal_send_json(url, data); - }else if (file && !data.isEmpty()){ - internal_send_image_embed(url, data, file->filepath(), file->filename()); - }else{ - internal_send_file(url, file->filepath()); + std::vector attachments; + if (file){ + attachments.emplace_back(file->filename(), file->filepath()); } + internal_send(url, *json, attachments); } ); logger.log("Scheduling Webhook Message... (queue = " + tostr_u_commas(m_queue.size()) + ")", COLOR_PURPLE); } - -void DiscordWebhookSender::send_file( +void DiscordWebhookSender::send( Logger& logger, const QUrl& url, std::chrono::milliseconds delay, - std::shared_ptr file + const JsonObject& obj, + std::vector> files ){ cleanup_stuck_requests(); -// m_queue.emplace_back(url, file); + std::shared_ptr json(new JsonValue(obj.clone())); m_queue.add_event( delay, - [this, url, file = std::move(file)]{ + [this, url, json = std::move(json), files = std::move(files)]{ throttle(); - internal_send_file(url, file->filepath()); + std::vector attachments; + for (auto& file : files){ + attachments.emplace_back(file->filename(), file->filepath()); + } + internal_send(url, *json, attachments); } ); logger.log("Scheduling Webhook Message... (queue = " + tostr_u_commas(m_queue.size()) + ")", COLOR_PURPLE); @@ -148,49 +151,48 @@ void DiscordWebhookSender::process_reply(QNetworkReply* reply){ } } -void DiscordWebhookSender::internal_send_json(const QUrl& url, const QByteArray& data){ +void DiscordWebhookSender::internal_send( + const QUrl& url, const JsonValue& json, + const std::vector& files +){ QEventLoop event_loop; connect( this, &DiscordWebhookSender::stop_event_loop, &event_loop, &QEventLoop::quit ); - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - - QNetworkAccessManager manager; - event_loop.connect(&manager, SIGNAL(finished(QNetworkReply*)), SLOT(quit())); - m_logger.log("Sending Webhook Message...", COLOR_BLUE); - std::unique_ptr reply(manager.post(request, data)); - event_loop.exec(); - process_reply(reply.get()); -} - -void DiscordWebhookSender::internal_send_file(const QUrl& url, const std::string& filename){ - QEventLoop event_loop; - connect( - this, &DiscordWebhookSender::stop_event_loop, - &event_loop, &QEventLoop::quit - ); + QHttpMultiPart multiPart(QHttpMultiPart::FormDataType); + if (!json.is_null()){ + QHttpPart json_part; + json_part.setHeader( + QNetworkRequest::ContentDispositionHeader, + QVariant("form-data; name=payload_json") + ); + json_part.setBody(QByteArray::fromStdString(json.dump())); + multiPart.append(json_part); + } - QFile file(QString::fromStdString(filename)); - if (!file.open(QIODevice::ReadOnly)){ - m_logger.log("File doesn't exist: " + filename, COLOR_RED); - return; + std::vector file_parts; + std::deque file_readers; + file_parts.reserve(files.size()); + size_t c = 0; + for (const auto& file : files){ + QFile& reader = file_readers.emplace_back(QString::fromStdString(file.filepath)); + if (!reader.open(QIODevice::ReadOnly)){ + m_logger.log("File doesn't exist: " + file.filepath, COLOR_RED); + continue; + } + QHttpPart& part = file_parts.emplace_back(); + part.setHeader( + QNetworkRequest::ContentDispositionHeader, + QVariant(QString::fromStdString("application/octet-stream; name=file" + std::to_string(c) + "; filename=" + file.name)) + ); + part.setBodyDevice(&reader); + multiPart.append(part); + c++; } QNetworkRequest request(url); - - QHttpPart imagePart; - imagePart.setHeader( - QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=\"file1\"; filename=\"" + file.fileName() + "\"") - ); - imagePart.setBodyDevice(&file); - - QHttpMultiPart multiPart(QHttpMultiPart::FormDataType); - multiPart.append(imagePart); - QNetworkAccessManager manager; event_loop.connect(&manager, SIGNAL(finished(QNetworkReply*)), SLOT(quit())); m_logger.log("Sending Webhook Message...", COLOR_BLUE); @@ -199,56 +201,61 @@ void DiscordWebhookSender::internal_send_file(const QUrl& url, const std::string process_reply(reply.get()); } -void DiscordWebhookSender::internal_send_image_embed(const QUrl& url, const QByteArray& data, const std::string& filepath, const std::string& filename){ - QEventLoop event_loop; - connect( - this, &DiscordWebhookSender::stop_event_loop, - &event_loop, &QEventLoop::quit - ); - QFile file(QString::fromStdString(filepath)); - if (!file.open(QIODevice::ReadOnly)){ - m_logger.log("File doesn't exist: " + filepath, COLOR_RED); + +void send_embed( + Logger& logger, + bool should_ping, + const std::vector& tags, + const JsonArray& embeds, + std::shared_ptr file +){ + DiscordSettingsOption& settings = GlobalSettings::instance().DISCORD; + if (!settings.webhooks.enabled()){ return; } - QNetworkRequest request(url); + MessageBuilder builder(tags); - QHttpPart imagePart; - imagePart.setHeader( - QNetworkRequest::ContentDispositionHeader, - QVariant("application/octet-stream; name=file0; filename=" + QString::fromStdString(filename)) - ); - imagePart.setBodyDevice(&file); + std::vector> list = settings.webhooks.urls.copy_snapshot(); + for (size_t c = 0; c < list.size(); c++){ + const DiscordWebhookUrl& url = *list[c]; + if (!url.enabled || ((std::string)url.url).empty()){ + continue; + } + if (!builder.should_send(EventNotificationOption::parse_tags(url.tags_text))){ + continue; + } - QHttpPart jsonPart; - jsonPart.setHeader( - QNetworkRequest::ContentDispositionHeader, - QVariant("form-data; name=payload_json") - ); - jsonPart.setBody(data); + std::chrono::seconds delay(url.delay); - QHttpMultiPart multiPart(QHttpMultiPart::FormDataType); - multiPart.append(imagePart); - multiPart.append(jsonPart); + JsonObject json; + json["content"] = builder.build_message( + delay, + should_ping && url.ping, + settings.message.user_id, + settings.message.message + ); + if (!embeds.empty()){ + json["embeds"] = embeds.clone(); + } - QNetworkAccessManager manager; - event_loop.connect(&manager, SIGNAL(finished(QNetworkReply*)), SLOT(quit())); - m_logger.log("Sending Webhook Message...", COLOR_BLUE); - std::unique_ptr reply(manager.post(request, &multiPart)); - event_loop.exec(); - process_reply(reply.get()); + DiscordWebhookSender::instance().send( + logger, + QString::fromStdString(url.url), + delay, + std::move(json), + file + ); + } } - - - void send_embed( Logger& logger, bool should_ping, const std::vector& tags, const JsonArray& embeds, - std::shared_ptr file + const std::vector>& files ){ DiscordSettingsOption& settings = GlobalSettings::instance().DISCORD; if (!settings.webhooks.enabled()){ @@ -280,19 +287,18 @@ void send_embed( json["embeds"] = embeds.clone(); } - DiscordWebhookSender::instance().send_json( + DiscordWebhookSender::instance().send( logger, QString::fromStdString(url.url), delay, std::move(json), - file + files ); } } - } } } diff --git a/SerialPrograms/Source/Integrations/DiscordWebhook.h b/SerialPrograms/Source/Integrations/DiscordWebhook.h index 8d9b005a2..97d7ad581 100644 --- a/SerialPrograms/Source/Integrations/DiscordWebhook.h +++ b/SerialPrograms/Source/Integrations/DiscordWebhook.h @@ -25,6 +25,11 @@ namespace Integration{ namespace DiscordWebhook{ +struct DiscordFileAttachment{ + std::string name; + std::string filepath; +}; + class DiscordWebhookSender : public QObject{ Q_OBJECT @@ -38,16 +43,17 @@ class DiscordWebhookSender : public QObject{ public: - void send_json( + void send( Logger& logger, const QUrl& url, std::chrono::milliseconds delay, const JsonObject& obj, std::shared_ptr file ); - void send_file( + void send( Logger& logger, const QUrl& url, std::chrono::milliseconds delay, - std::shared_ptr file + const JsonObject& obj, + std::vector> files ); static DiscordWebhookSender& instance(); @@ -59,9 +65,10 @@ class DiscordWebhookSender : public QObject{ void throttle(); void process_reply(QNetworkReply* reply); - void internal_send_json(const QUrl& url, const QByteArray& data); - void internal_send_file(const QUrl& url, const std::string& filename); - void internal_send_image_embed(const QUrl& url, const QByteArray& data, const std::string& filepath, const std::string& filename); + void internal_send( + const QUrl& url, const JsonValue& json, + const std::vector& files + ); signals: void stop_event_loop(); @@ -87,6 +94,13 @@ void send_embed( const JsonArray& embeds, std::shared_ptr file ); +void send_embed( + Logger& logger, + bool should_ping, + const std::vector& tags, + const JsonArray& embeds, + const std::vector>& files +); diff --git a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramComputer.cpp b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramComputer.cpp index b78ea0e10..530fda769 100644 --- a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramComputer.cpp +++ b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramComputer.cpp @@ -243,7 +243,35 @@ void TestProgramComputer::program(ProgramEnvironment& env, CancellableScope& sco using namespace Pokemon; using namespace NintendoSwitch::PokemonSwSh::MaxLairInternal; +#if 0 + send_program_notification_with_file( + env, NOTIFICATION_PROGRAM_FINISH, COLOR_BLUE, + "test notification", + {}, + "", + "test.txt" + ); +#endif + + +// ImageRGB32 image("Screenshots/20241128-152424608121.png"); +#if 0 + send_error_report( + env.logger(), + env.program_info(), + "testtest", + {{"title", "message"}}, + image, +// ImageRGB32(), + {"test.txt", "test2.txt"} + ); +#endif + + int* ptr = nullptr; + cout << ptr[0] << endl; + +#if 0 std::random_device rndsource; std::minstd_rand rndgen(rndsource()); std::uniform_real_distribution dist(0, 1.0); @@ -252,6 +280,7 @@ void TestProgramComputer::program(ProgramEnvironment& env, CancellableScope& sco cout << dist(rndgen) << endl; cout << dist(rndgen) << endl; cout << dist(rndgen) << endl; +#endif #if 0 PokemonSV::DateSeed data = PokemonSV::ItemPrinter::calculate_seed_prizes(2346161588); diff --git a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp index a430182b1..62b9e1287 100644 --- a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp +++ b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp @@ -121,6 +121,7 @@ #include "PokemonSV/Inference/Overworld/PokemonSV_DirectionDetector.h" #include "PokemonSwSh/Inference/PokemonSwSh_IvJudgeReader.h" //#include "CommonFramework/Environment/SystemSleep.h" +#include "CommonFramework/ErrorReports/ErrorReports.h" @@ -275,6 +276,21 @@ void TestProgram::program(MultiSwitchProgramEnvironment& env, CancellableScope& // SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED); + + +#if 0 + VideoSnapshot image = feed.snapshot(); + report_error( + &env.logger(), + env.program_info(), + "testtest", + {{"title", "message"}}, + image, + {"test.txt"} + ); +#endif + +#if 1 VideoSnapshot image = feed.snapshot(); // ImageRGB32 image("screenshot-20241124-135028529403.png"); @@ -288,7 +304,7 @@ void TestProgram::program(MultiSwitchProgramEnvironment& env, CancellableScope& cout << "hour = " << (int)date.second.hour << endl; cout << "min = " << (int)date.second.minute << endl; cout << "secs = " << (int)date.second.second << endl; - +#endif #if 0 diff --git a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Adventure.cpp b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Adventure.cpp index 1e57f4f0a..63cffda2d 100644 --- a/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Adventure.cpp +++ b/SerialPrograms/Source/PokemonSwSh/MaxLair/Program/PokemonSwSh_MaxLair_Run_Adventure.cpp @@ -5,7 +5,7 @@ */ #include "CommonFramework/Exceptions/OperationFailedException.h" -#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonFramework/ErrorReports/ErrorReports.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/NintendoSwitch_Settings.h" #include "PokemonSwSh/PokemonSwSh_Settings.h" @@ -162,12 +162,20 @@ void loop_adventures( case AdventureResult::START_ERROR: restart_count++; if (restart_count == 3){ + report_error( + &env.logger(), + env.program_info(), + "Error", + {{"Message:", "Failed to start adventure 3 times in the row."}} + ); +#if 0 send_program_telemetry( env.logger(), true, COLOR_RED, env.program_info(), "Error", {{"Message:", "Failed to start adventure 3 times in the row."}}, "" ); +#endif throw OperationFailedException( ErrorReport::SEND_ERROR_REPORT, env.logger(), "Failed to start adventure 3 times in the row."