#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 2048 // Reduced from 4096 to use less memory // 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 5 // Increased from 3 #define HTTP_RETRY_DELAY_MS 1000 // Increased from 500 /** * @brief Update mode enumeration */ enum class UpdateMode { UPDATE_FILES_ONLY, ///< Update files only, skip firmware UPDATE_FIRMWARE_ONLY, ///< Update firmware only, skip files UPDATE_BOTH ///< Update both files and firmware (default) }; // Allow external cancellation extern volatile bool g_UpdateCancelFlag; // Global update mode setting extern UpdateMode g_UpdateMode; 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 update mode (files only, firmware only, or both) */ void setUpdateMode(UpdateMode mode); /** * @brief Get current update mode */ UpdateMode getUpdateMode() const; /** * @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 Results from checkManifest */ enum class ManifestCheckResult { ERROR_FETCH_FAILED, ///< Failed to fetch manifest ERROR_TOO_LARGE, ///< Manifest too large ERROR_PARSE_FAILED, ///< Failed to parse manifest JSON ERROR_NO_FILES_SECTION, ///< No files section in manifest ERROR_NO_VERSION, ///< No version section in manifest VERSION_CURRENT, ///< Current version is same or newer UPDATE_AVAILABLE ///< New version available }; /** * @brief Get manifest content * @param manifestPath Path to manifest file * @return Result indicating success, failure, or version status */ ManifestCheckResult 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; size_t downloadBufferSize = 0; /**< Actual size allocated for downloadBuffer */ bool updateAvailable = false; UpdateMode updateMode = UpdateMode::UPDATE_BOTH; // Default to updating both files and firmware /** * @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* remotePath, 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); bool 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); // Convenience functions for setting update mode void setGlobalUpdateMode(UpdateMode mode); UpdateMode getGlobalUpdateMode(); void setUpdateModeFilesOnly(); void setUpdateModeFirmwareOnly(); void setUpdateModeBoth();