#pragma once #include #include #include #include #include #include #include "AppVersion.h" //#define DEFAULT_MANIFEST_URL "https://storage.googleapis.com/boothifier/latest/" #define DEFAULT_MANIFEST_URL "https://minio.boothwizard.com/boothifier/latest/" #define BUFFER_SIZE 4096 // Maximum allowed manifest size (bytes) to protect memory #define MAX_MANIFEST_SIZE (64 * 1024) // Number of HTTP retry attempts for transient failures #define HTTP_RETRY_COUNT 3 #define HTTP_RETRY_DELAY_MS 500 // Allow external cancellation extern volatile bool g_UpdateCancelFlag; extern TaskHandle_t Update_Task_Handle; /** * @brief Version information structure */ //bool versionCompare(Version& remoteVersion, Version& localVersion); extern Version otaVersion; /** * @brief File information structure */ struct FileInfo { String remotePath; ///< Path on remote server String localPath; ///< Path in local filesystem String md5; ///< MD5 hash for verification //size_t size; ///< File size in bytes }; /** * @class AppUpdater * @brief Handles firmware and filesystem updates */ class AppUpdater { public: Version localVersion; Version otaVersion; // Base URL (bucket) for update resources. Supports either Google Cloud Storage or MinIO (or any HTTP host) String baseUrl; const char* appName; const char* manifestName; JsonDocument jsonManifest; JsonArray jsonFilesArray; /** * @brief Update status enumeration */ enum class UpdateStatus { IDLE, ///< No update in progress MESSAGE, ///< Update message DOWNLOADING, ///< Downloading files VERIFYING, ///< Verifying file integrity FILE_SKIPPED, ///< File already up to date FILE_SAVED, ///< Saving to filesystem MD5_FAILED, ///< MD5 verification failed COMPLETE, ///< Update complete ERROR ///< Error occurred }; /** * @brief Constructor * @param version Current firmware version * @param bucket Base URL for updates * @param fs Filesystem reference */ AppUpdater(fs::FS& fs, Version localVersion, const char* bucket, const char* manifestName ="update.json", const char* appBin = "firmware.bin" ); /** * @brief Change the base URL after construction (e.g. switch between MinIO and GCS) */ void setBaseUrl(const String& newBaseUrl) { baseUrl = newBaseUrl; } /** * @brief Get the currently configured base URL */ const String& getBaseUrl() const { return baseUrl; } /** * @brief Set progress callback function * @param callback Function to call with progress updates */ void setProgressCallback(void (*callback)( UpdateStatus status, int percentage, const char* message)); /** * @brief Check for and apply updates * @return true if update successful */ bool IsUpdateAvailable(void); /** * @brief Update files from manifest * @param manifestPath Path to manifest file * @return true if all files updated successfully */ //bool updateFilesFromManifest(const char* manifestPath = DEFAULT_MANIFEST_URL); bool updateFilesArray(void); /** * @brief Update single file * @param remotePath Remote file path * @param localPath Local file path * @param expectedMd5 Expected MD5 hash * @return true if file updated successfully */ bool updateFile(const char* remotePath, const char* localPath, const char* expectedMd5); /** * @brief Get manifest content * @param manifestPath Path to manifest file * @return Manifest content as a json document */ bool checkManifest(void); bool updateApp(void); String getVersion(void); private: typedef void (*ProgressCallback)(UpdateStatus status, int percentage, const char* message); ProgressCallback progressCb; fs::FS& fileSystem; UpdateStatus status; std::unique_ptr downloadBuffer; bool updateAvailable = false; /** * @brief Verify and save file * @param stream Input stream * @param contentLength Expected content length * @param localPath Local file path * @param expectedMd5 Expected MD5 hash * @return true if successful */ bool verifyAndSaveFile(WiFiClient* stream, size_t contentLength, const char* localPath, const char* expectedMd5); /** * @brief Update progress callback * @param percentage Progress percentage * @param newStatus Current status */ void updateProgress(UpdateStatus newStatus, int percentage, const char* message = nullptr); /** * @brief Build a full URL by combining baseUrl and a relative path. Absolute (http/https) paths pass through. * Ensures exactly one slash joins base and path. Leading slash on path is stripped. */ String buildUrl(const char* path) const; String getLocalMD5(const char* filePath); }; // Queue handle for firmware update messages extern QueueHandle_t updateMsgQueue; // Message structure for update progress struct UpdateMessage { String message; bool complete; int progress; }; /** * @brief Firmware update task * @param param Task parameters */ void firmwareUpdateTask(void* param); void startFirmwareUpdateTask(AsyncEventSource* eventSrc); void loadUpdateJson(void); void updateProgress(AppUpdater::UpdateStatus status, int percentage, const char* message); void sendUpdateMessage(const char* message, bool complete, int progress); void handleUpdateProgress(AsyncWebServerRequest *request); void startVersionCheckTask(); void versionCheckTask(void* parameter);