boothifier/include/AppUpgrade.h
2025-08-25 23:38:53 -07:00

201 lines
6.0 KiB
C++

#pragma once
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <Update.h>
#include <FS.h>
#include <memory>
#include <ESPAsyncWebServer.h>
#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<uint8_t[]> 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);