diff --git a/GitHubSync.cpp b/GitHubSync.cpp index ba5e5b1..1208370 100644 --- a/GitHubSync.cpp +++ b/GitHubSync.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #define GH_PREFS_NS "github_sync" #define GH_KEY_USER "username" @@ -44,15 +45,15 @@ bool GitHubSync::isConfigured() { const char* GitHubSync::resultMessage(GitHubSyncResult r) { switch (r) { - case GitHubSyncResult::OK: return "GitHub sync complete"; - case GitHubSyncResult::NOT_CONFIGURED: return "GitHub sync: not configured"; - case GitHubSyncResult::NO_WIFI: return "GitHub sync: no WiFi"; - case GitHubSyncResult::AUTH_ERROR: return "GitHub sync: auth failed (check PAT)"; - case GitHubSyncResult::REPO_NOT_FOUND: return "GitHub sync: repo not found"; - case GitHubSyncResult::API_ERROR: return "GitHub sync: API error"; - case GitHubSyncResult::SD_ERROR: return "GitHub sync: SD card error"; - case GitHubSyncResult::PARSE_ERROR: return "GitHub sync: bad API response"; - default: return "GitHub sync: unknown error"; + case GitHubSyncResult::OK: return "Sync complete"; + case GitHubSyncResult::NOT_CONFIGURED: return "Not configured"; + case GitHubSyncResult::NO_WIFI: return "No WiFi"; + case GitHubSyncResult::AUTH_ERROR: return "Auth failed (check PAT)"; + case GitHubSyncResult::REPO_NOT_FOUND: return "Repo not found"; + case GitHubSyncResult::API_ERROR: return "API error"; + case GitHubSyncResult::SD_ERROR: return "SD card error"; + case GitHubSyncResult::PARSE_ERROR: return "Bad API response"; + default: return "Unknown error"; } } @@ -92,6 +93,7 @@ bool GitHubSync::fetchFileList(const GitHubSyncConfig &cfg, std::string &outJson http.addHeader("User-Agent", "CrossPoint-X4"); int code = http.GET(); + LOG_INF("SYNC", "fetchFileList HTTP code: %d", code); if (code == 401 || code == 403) { http.end(); err = GitHubSyncResult::AUTH_ERROR; return false; } if (code == 404) { http.end(); err = GitHubSyncResult::REPO_NOT_FOUND; return false; } if (code != 200) { http.end(); err = GitHubSyncResult::API_ERROR; return false; } @@ -101,21 +103,19 @@ bool GitHubSync::fetchFileList(const GitHubSyncConfig &cfg, std::string &outJson return true; } -bool GitHubSync::downloadFile(const GitHubSyncConfig &cfg, const std::string &path, const std::string &sha, GitHubSyncResult &err) { - std::string url = std::string(GH_API_BASE) + "/repos/" + cfg.username + "/" + - cfg.repo + "/contents/" + path + "?ref=" + cfg.branch; - +bool GitHubSync::downloadFile(const GitHubSyncConfig &cfg, const std::string &downloadUrl, const std::string &name, const std::string &sha, GitHubSyncResult &err) { HTTPClient http; - http.begin(url.c_str()); + http.begin(downloadUrl.c_str()); http.addHeader("Authorization", ("token " + cfg.pat).c_str()); - http.addHeader("Accept", "application/vnd.github.v3.raw"); http.addHeader("User-Agent", "CrossPoint-X4"); int code = http.GET(); + LOG_INF("SYNC", "downloadFile '%s' HTTP code: %d", name.c_str(), code); if (code == 401 || code == 403) { http.end(); err = GitHubSyncResult::AUTH_ERROR; return false; } if (code != 200) { http.end(); err = GitHubSyncResult::API_ERROR; return false; } - std::string destPath = (path == GH_SLEEP_BMP) ? GH_SLEEP_PATH : std::string(GH_BOOKS_DIR) + path; + std::string destPath = (name == GH_SLEEP_BMP) ? GH_SLEEP_PATH : std::string(GH_BOOKS_DIR) + name; + LOG_INF("SYNC", "Writing to SD path: '%s'", destPath.c_str()); File f = SD.open(destPath.c_str(), FILE_WRITE); if (!f) { http.end(); err = GitHubSyncResult::SD_ERROR; return false; } @@ -137,7 +137,7 @@ bool GitHubSync::downloadFile(const GitHubSyncConfig &cfg, const std::string &pa f.close(); http.end(); - saveLocalSha(path, sha); + saveLocalSha(name, sha); return true; } @@ -156,9 +156,10 @@ GitHubSyncResult GitHubSync::sync() { JsonArray files = doc.as(); for (JsonObject file : files) { - std::string type = file["type"].as(); - std::string name = file["name"].as(); - std::string sha = file["sha"].as(); + std::string type = file["type"].as(); + std::string name = file["name"].as(); + std::string sha = file["sha"].as(); + std::string downloadUrl = file["download_url"].as(); if (type != "file") continue; @@ -166,11 +167,13 @@ GitHubSyncResult GitHubSync::sync() { bool isSleep = (name == GH_SLEEP_BMP); if (!isEpub && !isSleep) continue; + if (downloadUrl.empty()) continue; std::string localSha = loadLocalSha(name); if (localSha == sha) continue; - if (!downloadFile(cfg, name, sha, err)) return err; + LOG_INF("SYNC", "Downloading '%s'", name.c_str()); + if (!downloadFile(cfg, downloadUrl, name, sha, err)) return err; } return GitHubSyncResult::OK; diff --git a/GitHubSync.h b/GitHubSync.h index 361f18a..5a48a0b 100644 --- a/GitHubSync.h +++ b/GitHubSync.h @@ -31,7 +31,7 @@ public: private: static bool fetchFileList(const GitHubSyncConfig &cfg, std::string &outJson, GitHubSyncResult &err); - static bool downloadFile(const GitHubSyncConfig &cfg, const std::string &path, const std::string &sha, GitHubSyncResult &err); + static bool downloadFile(const GitHubSyncConfig &cfg, const std::string &downloadUrl, const std::string &name, const std::string &sha, GitHubSyncResult &err); static std::string loadLocalSha(const std::string &filename); static void saveLocalSha(const std::string &filename, const std::string &sha); static std::string shaFilePath(const std::string &filename); diff --git a/GitHubSyncSettingsActivity.cpp b/GitHubSyncSettingsActivity.cpp index 1b79ec5..86dc636 100644 --- a/GitHubSyncSettingsActivity.cpp +++ b/GitHubSyncSettingsActivity.cpp @@ -2,19 +2,26 @@ #include #include +#include #include "MappedInputManager.h" #include "activities/util/KeyboardEntryActivity.h" +#include "activities/network/WifiSelectionActivity.h" #include "components/UITheme.h" #include "fontIds.h" namespace { -constexpr int MENU_ITEMS = 5; +constexpr int MENU_ITEMS = 7; +constexpr int WIFI_STATUS_INDEX = 4; +constexpr int SYNC_NOW_INDEX = 5; +constexpr int SAVE_INDEX = 6; const char* menuNames[MENU_ITEMS] = { "Username", "Token (PAT)", "Repo", "Branch", + "WiFi", + "Sync Now", "Save" }; } @@ -33,6 +40,19 @@ std::string GitHubSyncSettingsActivity::getMasked(const std::string &s) const { return s.empty() ? "" : "••••••••"; } +void GitHubSyncSettingsActivity::doSync() { + GitHubSyncConfig cfg; + GitHubSync::loadConfig(cfg); + LOG_INF("SYNC", "user='%s' repo='%s' branch='%s' pat_len=%d", + cfg.username.c_str(), cfg.repo.c_str(), cfg.branch.c_str(), (int)cfg.pat.size()); + syncStatus = "Syncing..."; + requestUpdate(); + GitHubSyncResult result = GitHubSync::sync(); + syncStatus = GitHubSync::resultMessage(result); + LOG_INF("SYNC", "result: %s", syncStatus.c_str()); + requestUpdate(); +} + void GitHubSyncSettingsActivity::handleSelection() { GitHubSyncConfig cfg; GitHubSync::loadConfig(cfg); @@ -88,7 +108,25 @@ void GitHubSyncSettingsActivity::handleSelection() { GitHubSync::saveConfig(c); } }); - } else if (selectedIndex == 4) { + } else if (selectedIndex == SYNC_NOW_INDEX) { + if (WiFi.status() != WL_CONNECTED) { + syncStatus = "Connecting to WiFi..."; + requestUpdate(); + startActivityForResult( + std::make_unique(renderer, mappedInput), + [this](const ActivityResult &result) { + if (WiFi.status() != WL_CONNECTED) { + syncStatus = "No WiFi — sync cancelled"; + requestUpdate(); + return; + } + doSync(); + }); + } else { + doSync(); + } + requestUpdate(); + } else if (selectedIndex == SAVE_INDEX) { finish(); } } @@ -106,11 +144,15 @@ void GitHubSyncSettingsActivity::loop() { buttonNavigator.onNext([this] { selectedIndex = (selectedIndex + 1) % MENU_ITEMS; + if (selectedIndex == WIFI_STATUS_INDEX) + selectedIndex = (selectedIndex + 1) % MENU_ITEMS; requestUpdate(); }); buttonNavigator.onPrevious([this] { selectedIndex = (selectedIndex + MENU_ITEMS - 1) % MENU_ITEMS; + if (selectedIndex == WIFI_STATUS_INDEX) + selectedIndex = (selectedIndex + MENU_ITEMS - 1) % MENU_ITEMS; requestUpdate(); }); } @@ -140,6 +182,9 @@ void GitHubSyncSettingsActivity::render(RenderLock &&) { if (index == 1) return cfg.pat.empty() ? "(not set)" : getMasked(cfg.pat); if (index == 2) return cfg.repo.empty() ? "xteink" : cfg.repo; if (index == 3) return cfg.branch.empty() ? "main" : cfg.branch; + if (index == WIFI_STATUS_INDEX) + return WiFi.status() == WL_CONNECTED ? "Connected" : "Not connected"; + if (index == SYNC_NOW_INDEX) return syncStatus; return ""; }, true); diff --git a/GitHubSyncSettingsActivity.h b/GitHubSyncSettingsActivity.h index 071c574..fe59e15 100644 --- a/GitHubSyncSettingsActivity.h +++ b/GitHubSyncSettingsActivity.h @@ -17,9 +17,11 @@ public: private: ButtonNavigator buttonNavigator; int selectedIndex = 0; + std::string syncStatus; - static constexpr int MENU_ITEMS = 5; + static constexpr int MENU_ITEMS = 7; void handleSelection(); + void doSync(); std::string getMasked(const std::string& s) const; }; diff --git a/patch.py b/patch.py index 8dd7f4b..baedf78 100644 --- a/patch.py +++ b/patch.py @@ -588,7 +588,7 @@ def prompt_for_upload_port(existing_port: str | None) -> str | None: current_ports = list_likely_serial_ports() if current_ports: - entered = input(" Upload port (recommended, e.g. /dev/cu.usbmodemXXXX; Enter for auto-detect): ").strip() + entered = input(" Upload port (recommended, e.g. /dev/cu.usbmodemXXXX; Press ENTER for auto-detect): ").strip() if entered: return entered warn("Proceeding with auto-detect. This may choose a non-USB port (like Bluetooth).")