diff --git a/.github/workflows/release.yml b/.github/workflows/build.yml similarity index 59% rename from .github/workflows/release.yml rename to .github/workflows/build.yml index 0e73024..49ebaeb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/build.yml @@ -1,9 +1,18 @@ -name: Release +name: Build +# Packages are published to the `builds` branch instead of to a GitHub +# Release, so everything the installers and the in-app updater fetch comes +# straight from the repository and always matches what is on main. on: push: - tags: - - "v*" + branches: + - main + # Nothing to rebuild when only documentation changed. + paths-ignore: + - "README.md" + - "LICENSE" + - ".gitignore" + workflow_dispatch: permissions: contents: write @@ -26,7 +35,7 @@ jobs: - name: Build AppImage env: - VERSION: ${{ github.ref_name }} + COMMIT: ${{ github.sha }} run: | chmod +x scripts/bundle-linux.sh ./scripts/bundle-linux.sh @@ -60,7 +69,7 @@ jobs: - name: Build and bundle env: - VERSION: ${{ github.ref_name }} + COMMIT: ${{ github.sha }} run: | make bundle cd dist @@ -87,7 +96,7 @@ jobs: - name: Build .app zip env: - VERSION: ${{ github.ref_name }} + COMMIT: ${{ github.sha }} run: | chmod +x scripts/bundle-macos.sh ./scripts/bundle-macos.sh @@ -99,7 +108,7 @@ jobs: path: dist/maturita-macos-*.zip publish: - name: Publish GitHub Release + name: Publish to builds branch needs: [linux, windows, macos] runs-on: ubuntu-latest steps: @@ -108,34 +117,34 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v4 with: - path: release-assets + path: staged merge-multiple: true - # The installers carry no application: each downloads the newest release - # when the user runs it. The macOS one is zipped because a release asset - # loses its executable bit, which a .command needs to be double-clicked. - name: Collect installers run: | - cp installers/maturita-installer-linux.sh release-assets/ - cp installers/maturita-installer-windows.cmd release-assets/ + cp installers/maturita-installer-linux.sh staged/ + cp installers/maturita-installer-windows.cmd staged/ chmod +x installers/maturita-installer-macos.command - (cd installers && zip -q ../release-assets/maturita-installer-macos.zip \ + (cd installers && zip -q ../staged/maturita-installer-macos.zip \ maturita-installer-macos.command) + # The updater downloads this to see whether it is behind. + echo "${{ github.sha }}" > staged/VERSION - - name: List assets - run: find release-assets -type f -print - - - name: Create release - uses: softprops/action-gh-release@v2 - with: - files: | - release-assets/maturita-linux-*.AppImage - release-assets/maturita-windows-x64.zip - release-assets/maturita-macos-*.zip - release-assets/maturita-installer-macos.zip - release-assets/maturita-installer-windows.cmd - release-assets/maturita-installer-linux.sh - generate_release_notes: true - fail_on_unmatched_files: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # The branch is rewritten from scratch every time rather than added to, + # so the repository keeps one copy of the packages instead of a new + # ~70 MB set per commit. + - name: Push to builds branch + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + rm -rf .git/index.lock + git checkout --orphan builds + git rm -rf --cached . >/dev/null + find . -maxdepth 1 ! -name . ! -name .git ! -name staged \ + -exec rm -rf {} + + mv staged/* . + rmdir staged + ls -la + git add -A + git commit -q -m "Build from ${{ github.sha }}" + git push -f origin builds diff --git a/CMakeLists.txt b/CMakeLists.txt index 943bfde..06fb532 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,9 +52,9 @@ else() add_executable(maturita ${MATURITA_SOURCES}) endif() -# Mirrors VERSION in the Makefile: bakes the release tag in for the updater. -if(DEFINED APP_VERSION AND NOT APP_VERSION STREQUAL "") - target_compile_definitions(maturita PRIVATE APP_VERSION="${APP_VERSION}") +# Mirrors COMMIT in the Makefile: bakes the source commit in for the updater. +if(DEFINED APP_COMMIT AND NOT APP_COMMIT STREQUAL "") + target_compile_definitions(maturita PRIVATE APP_COMMIT="${APP_COMMIT}") endif() if(MSVC) diff --git a/Makefile b/Makefile index 64e2fb6..5785932 100644 --- a/Makefile +++ b/Makefile @@ -17,12 +17,12 @@ GTK_LIBS := $(shell $(PKG_CONFIG) --libs gtk4) CFLAGS = -Wall -Wextra -Wno-deprecated-declarations $(GTK_CFLAGS) LIBS = $(GTK_LIBS) -lm -# Release builds pass the git tag (the CI workflow exports it), which bakes the -# version into the binary for the updater to compare against. Without it the -# build keeps the "dev" default from maturita.h and never offers an update. -VERSION ?= -ifneq ($(VERSION),) -CFLAGS += -DAPP_VERSION='"$(VERSION)"' +# Published builds pass the commit they were made from (the CI workflow +# exports it), which the updater compares against the build in the repository. +# Without it the build keeps the "dev" default and never offers an update. +COMMIT ?= +ifneq ($(COMMIT),) +CFLAGS += -DAPP_COMMIT='"$(COMMIT)"' endif # Windows (MSYS2 / MinGW): .exe suffix, no console window, embed .ico. diff --git a/README.md b/README.md index 351fd3e..58dd513 100644 --- a/README.md +++ b/README.md @@ -125,44 +125,35 @@ GTK 4 interface. ### Downloads (prebuilt) -Precompiled, self-contained builds are published on the -**GitHub Releases** page of this project (enable Actions on a GitHub remote) -when a version tag (`v*`) is pushed. No compiler or GTK development packages -are required: +There are no releases and no version numbers. CI rebuilds all three platforms +whenever `main` changes and commits the packages to the +[`builds`](../../tree/builds) branch, so what you download is always the +current state of the repository. No compiler or GTK development packages are +required. -| Platform | Asset | How to run | -| -------- | ----- | ---------- | -| Linux (x86_64) | `maturita-linux-x86_64.AppImage` | `chmod +x` the file, then double-click or run it | -| macOS (Apple Silicon) | `maturita-macos-arm64.zip` | Unzip and open `Maturita.app` | -| Windows (x64) | `maturita-windows-x64.zip` | Unzip and run `maturita.exe` | +Start with the installer for your platform. It contains no application of its +own — it fetches the current build when you run it, so the file never goes out +of date and you only ever need this one download: -Progress and settings are saved next to the AppImage / `.app` / unzipped -folder in a `progress/` directory. +| Platform | File | How to run | +| -------- | ---- | ---------- | +| macOS (Apple Silicon) | [`maturita-installer-macos.zip`](../../raw/builds/maturita-installer-macos.zip) | Unzip, then double-click the `.command` file | +| Windows (x64) | [`maturita-installer-windows.cmd`](../../raw/builds/maturita-installer-windows.cmd) | Double-click the file | +| Linux (x86_64) | [`maturita-installer-linux.sh`](../../raw/builds/maturita-installer-linux.sh) | `sh maturita-installer-linux.sh` | -#### Installers - -Each release also ships a small installer that contains no application of its -own: it downloads the newest build when you run it, so the file never goes out -of date. Once installed, the app keeps itself up to date on its own — it checks -shortly after startup and offers the update in a banner, and there is a manual -check under **Settings → Updates**. - -| Platform | Asset | How to run | -| -------- | ----- | ---------- | -| macOS (Apple Silicon) | `maturita-installer-macos.zip` | Unzip, then double-click the `.command` file | -| Windows (x64) | `maturita-installer-windows.cmd` | Double-click the file | -| Linux (x86_64) | `maturita-installer-linux.sh` | `sh maturita-installer-linux.sh` | +Once installed, the app keeps itself up to date: it compares the commit it was +built from against `VERSION` on the `builds` branch shortly after startup and +offers the update in a banner. There is a manual check under +**Settings → Updates**. The builds are not code-signed, so the first launch needs one extra step: on macOS right-click the installer and pick **Open**, and on Windows choose **More info → Run anyway** if SmartScreen appears. -To cut a release yourself (needs a GitHub remote with Actions enabled): - -```bash -git tag v1.0.0 -git push origin v1.0.0 -``` +The packages themselves are on the same branch if you would rather skip the +installer: `maturita-linux-x86_64.AppImage`, `maturita-macos-arm64.zip` and +`maturita-windows-x64.zip`. Progress and settings are saved next to the +AppImage / `.app` / unzipped folder in a `progress/` directory. Local packaging (same scripts the CI uses): @@ -605,43 +596,37 @@ moderním GTK 4 rozhraním stylovaným přes CSS. ### Stažení (předkompilované) -Hotové self-contained buildy vycházejí na stránce -**GitHub Releases** tohoto projektu (Actions na GitHub remote) -při pushi tagu `v*`. Kompilátor ani vývojové balíčky GTK nejsou potřeba: +Žádné releasy ani čísla verzí nejsou. CI přestaví všechny tři platformy +pokaždé, když se změní `main`, a balíčky commitne do větve +[`builds`](../../tree/builds) — stahujete tedy vždycky aktuální stav repa. +Kompilátor ani vývojové balíčky GTK nejsou potřeba. + +Začněte instalátorem pro svou platformu. Samotnou aplikaci neobsahuje, stáhne +si aktuální build až při spuštění, takže soubor nikdy nezestárne a stačí vám +tohle jedno stažení: | Platforma | Soubor | Spuštění | | --------- | ------ | -------- | -| Linux (x86_64) | `maturita-linux-x86_64.AppImage` | `chmod +x` a spustit / dvojklik | -| macOS (Apple Silicon) | `maturita-macos-arm64.zip` | Rozbalit a otevřít `Maturita.app` | -| Windows (x64) | `maturita-windows-x64.zip` | Rozbalit a spustit `maturita.exe` | +| macOS (Apple Silicon) | [`maturita-installer-macos.zip`](../../raw/builds/maturita-installer-macos.zip) | Rozbalit a dvojklik na soubor `.command` | +| Windows (x64) | [`maturita-installer-windows.cmd`](../../raw/builds/maturita-installer-windows.cmd) | Dvojklik na soubor | +| Linux (x86_64) | [`maturita-installer-linux.sh`](../../raw/builds/maturita-installer-linux.sh) | `sh maturita-installer-linux.sh` | -Postup a nastavení se ukládají vedle AppImage / `.app` / rozbalené složky -do adresáře `progress/`. - -#### Instalátory - -Ke každému releasu vychází i malý instalátor, který samotnou aplikaci -neobsahuje: nejnovější build si stáhne až při spuštění, takže soubor nikdy -nezestárne. Nainstalovaná aplikace se pak udržuje aktuální sama — chvíli po -startu zkontroluje nové verze a nabídne je v pruhu nahoře, ruční kontrola je +Nainstalovaná aplikace se pak udržuje aktuální sama: chvíli po startu porovná +commit, ze kterého je postavená, se souborem `VERSION` ve větvi `builds` +a případnou aktualizaci nabídne v pruhu nahoře. Ruční kontrola je v **Nastavení → Aktualizace**. -| Platforma | Soubor | Spuštění | -| --------- | ------ | -------- | -| macOS (Apple Silicon) | `maturita-installer-macos.zip` | Rozbalit a dvojklik na soubor `.command` | -| Windows (x64) | `maturita-installer-windows.cmd` | Dvojklik na soubor | -| Linux (x86_64) | `maturita-installer-linux.sh` | `sh maturita-installer-linux.sh` | - Buildy nejsou podepsané, takže první spuštění chce jeden krok navíc: na macOS klikněte na instalátor pravým tlačítkem a zvolte **Otevřít**, na Windows při hlášce SmartScreenu **Další informace → Přesto spustit**. -Vytvoření releasu (GitHub remote s Actions): +Kdo chce instalátor přeskočit, najde na stejné větvi i samotné balíčky: +`maturita-linux-x86_64.AppImage`, `maturita-macos-arm64.zip` a +`maturita-windows-x64.zip`. Postup a nastavení se ukládají vedle AppImage / +`.app` / rozbalené složky do adresáře `progress/`. -```bash -git tag v1.0.0 -git push origin v1.0.0 -``` +Vydání nové verze je prostý push na `main` — build i publikování do větve +`builds` obstará CI. Lokální balíček (stejné skripty jako CI): diff --git a/i18n.c b/i18n.c index 5ca67af..07e45a5 100644 --- a/i18n.c +++ b/i18n.c @@ -20,11 +20,12 @@ const TrEntry tr_ui[] = { {"theme", "TÉMA", "THEME"}, {"language", "JAZYK", "LANGUAGE"}, {"updates", "AKTUALIZACE", "UPDATES"}, - {"update_current", "Verze %s", "Version %s"}, + {"update_current", "Build %s", "Build %s"}, {"update_checking", "Hledám aktualizace…", "Checking for updates…"}, - {"update_uptodate", "Máte nejnovější verzi (%s).", + {"update_uptodate", "Máte nejnovější build (%s).", "You are up to date (%s)."}, - {"update_available", "Je dostupná verze %s.", "Version %s is available."}, + {"update_available", "Je dostupný novější build (%s).", + "A newer build is available (%s)."}, {"update_downloading", "Stahuji aktualizaci…", "Downloading the update…"}, {"update_staged", "Aktualizace je připravená — restartujte aplikaci.", "The update is ready — restart the app to finish."}, @@ -38,16 +39,16 @@ const TrEntry tr_ui[] = { {"update_err_download", "Stahování se nepovedlo.", "The download failed."}, {"update_err_curl", "V systému chybí curl, aktualizace nejsou dostupné.", "curl is missing, so updates are unavailable."}, - {"update_err_unsupported", "Pro tuto platformu se aktualizace nevydává.", - "No update is published for this platform."}, + {"update_err_unsupported", "Pro tuto platformu se build nepublikuje.", + "No build is published for this platform."}, {"update_err_readonly", "Do umístění aplikace nelze zapisovat. Přesuňte ji třeba do Aplikací.", "The app's location is not writable. Move it somewhere else, for " "example to Applications."}, {"update_err_stage", "Aktualizaci nešlo připravit.", "Could not prepare the update."}, - {"update_err_devbuild", "Tento build nemá číslo verze (dev).", - "This build carries no version number (dev)."}, + {"update_err_devbuild", "Tento build nepochází z repozitáře (dev).", + "This build did not come from the repository (dev)."}, {"welcome_body", "maturita.C je vzdělávací program pro studenty středních škol a " "gymnázií na přípravu k maturitě.\n\n" diff --git a/installers/maturita-installer-linux.sh b/installers/maturita-installer-linux.sh index a72c6e3..24308b0 100755 --- a/installers/maturita-installer-linux.sh +++ b/installers/maturita-installer-linux.sh @@ -1,9 +1,9 @@ #!/bin/sh # maturita.c installer for Linux. # -# Carries no application of its own: it downloads whatever the newest release -# is at the moment you run it, so this file never goes stale. GitHub resolves -# the /releases/latest/download/ path to the current tag on every request. +# Carries no application of its own: it downloads the current build straight +# from the repository's `builds` branch, which CI rewrites whenever main +# changes. This file therefore never goes stale. # # Run it with: sh maturita-installer-linux.sh # The app updates itself from then on. @@ -11,8 +11,9 @@ set -eu REPO="pepaskopec-blip/maturita.c" +BRANCH="builds" ASSET="maturita-linux-x86_64.AppImage" -URL="https://github.com/$REPO/releases/latest/download/$ASSET" +URL="https://raw.githubusercontent.com/$REPO/$BRANCH/$ASSET" # ~/Applications is where desktop environments look for AppImages, and the app # keeps its progress/ directory next to its own file. diff --git a/installers/maturita-installer-macos.command b/installers/maturita-installer-macos.command index b5f79d1..3bf8b23 100755 --- a/installers/maturita-installer-macos.command +++ b/installers/maturita-installer-macos.command @@ -1,17 +1,18 @@ #!/bin/sh # maturita.c installer for macOS. # -# Carries no application of its own: it downloads whatever the newest release -# is at the moment you run it, so this file never goes stale. GitHub resolves -# the /releases/latest/download/ path to the current tag on every request. +# Carries no application of its own: it downloads the current build straight +# from the repository's `builds` branch, which CI rewrites whenever main +# changes. This file therefore never goes stale. # # Double-click the file to run it. The app updates itself from then on. set -eu REPO="pepaskopec-blip/maturita.c" +BRANCH="builds" ASSET="maturita-macos-arm64.zip" -URL="https://github.com/$REPO/releases/latest/download/$ASSET" +URL="https://raw.githubusercontent.com/$REPO/$BRANCH/$ASSET" say() { printf '%s\n' "$*"; } die() { printf '\nError: %s\n' "$*" >&2; printf 'Press Return to close. '; read -r _; exit 1; } diff --git a/installers/maturita-installer-windows.cmd b/installers/maturita-installer-windows.cmd index 661e286..4c0f5d4 100644 --- a/installers/maturita-installer-windows.cmd +++ b/installers/maturita-installer-windows.cmd @@ -1,16 +1,17 @@ @echo off rem maturita.c installer for Windows. rem -rem Carries no application of its own: it downloads whatever the newest release -rem is at the moment you run it, so this file never goes stale. GitHub resolves -rem the /releases/latest/download/ path to the current tag on every request. +rem Carries no application of its own: it downloads the current build straight +rem from the repository's `builds` branch, which CI rewrites whenever main +rem changes. This file therefore never goes stale. rem rem Double-click the file to run it. The app updates itself from then on. setlocal set "REPO=pepaskopec-blip/maturita.c" +set "BRANCH=builds" set "ASSET=maturita-windows-x64.zip" -set "URL=https://github.com/%REPO%/releases/latest/download/%ASSET%" +set "URL=https://raw.githubusercontent.com/%REPO%/%BRANCH%/%ASSET%" set "DEST=%LOCALAPPDATA%\Programs\Maturita" echo Installing maturita.C @@ -51,7 +52,7 @@ exit /b 0 echo. echo Error: this installer needs curl and tar, which come with Windows 10 echo version 1803 and later. Please update Windows, or download the zip -echo manually from https://github.com/%REPO%/releases/latest +echo manually from https://github.com/%REPO%/tree/%BRANCH% pause exit /b 1 diff --git a/maturita.h b/maturita.h index d6e10b0..405f6e6 100644 --- a/maturita.h +++ b/maturita.h @@ -21,13 +21,16 @@ #define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */ #define CSS_FILE "style.css" #define APP_ID "org.maturita.Maturita" -/* Release builds bake the git tag in (see VERSION in the Makefile), so the - * updater can tell what it is running. Plain builds stay at "dev" and never - * report an update as available. */ -#ifndef APP_VERSION -#define APP_VERSION "dev" +/* Published builds bake in the commit they were made from (see COMMIT in the + * Makefile), which is what the updater compares against the build published + * in the repository. Local builds stay at "dev" and never offer an update. */ +#ifndef APP_COMMIT +#define APP_COMMIT "dev" #endif #define UPDATE_REPO "pepaskopec-blip/maturita.c" +/* Packages live on their own branch rather than in releases, so the update + * always matches whatever is on main. */ +#define UPDATE_BRANCH "builds" #define ICON_NAME "maturita" #define ICON_FILE "assets/app-icon.png" #define ICON_THEME_DIR "assets/icons" /* cwd when run from source tree */ diff --git a/update.c b/update.c index 9eecf0b..2d4c216 100644 --- a/update.c +++ b/update.c @@ -19,9 +19,13 @@ * Every call is asynchronous, so a slow or unreachable network never blocks * the interface. * - * The newest version is read from the redirect GitHub serves for - * /releases/latest, which points at /releases/tag/. That avoids the REST - * API together with its unauthenticated rate limit, and needs no JSON parser. + * Packages are not published as releases. They live on the `builds` branch of + * the repository, which CI rewrites on every change to main, so an update is + * simply whatever that branch currently holds. The branch also carries a + * VERSION file naming the commit its packages were built from; comparing that + * against the commit baked into this binary is the whole check. Serving it + * from raw.githubusercontent.com avoids the REST API and its unauthenticated + * rate limit, and needs no JSON parser. * * An application cannot reliably replace its own files while it is running, so * all three platforms follow the same shape: unpack the new version into a @@ -30,9 +34,9 @@ * script detached and quit. Staging beside the install keeps the final move on * one filesystem and proves up front that the location is writable. */ -#define UPDATE_LATEST_URL "https://github.com/" UPDATE_REPO "/releases/latest" -#define UPDATE_ASSET_URL "https://github.com/" UPDATE_REPO \ - "/releases/latest/download/" +#define UPDATE_BASE_URL "https://raw.githubusercontent.com/" UPDATE_REPO \ + "/" UPDATE_BRANCH "/" +#define UPDATE_VERSION_URL UPDATE_BASE_URL "VERSION" #define UPDATE_STAGING_DIR ".maturita-update" #ifdef _WIN32 @@ -64,7 +68,7 @@ typedef enum { static struct { UpdateState state; gboolean interactive; /* user asked, so also report "up to date" */ - char *latest; /* newest release version, without any "v" */ + char *latest; /* commit the published packages were built from */ char *staging; /* scratch directory beside the install */ char *archive; /* asset downloaded into the staging directory */ char *source; /* unpacked version the swap script moves in */ @@ -81,39 +85,33 @@ static struct { } up; /* ------------------------------------------------------------------ */ -/* Versions */ +/* Build identity */ /* ------------------------------------------------------------------ */ -static const char *strip_v(const char *v) { - return (v && (v[0] == 'v' || v[0] == 'V')) ? v + 1 : v; +/* Commits are not ordered, so there is nothing to compare: the branch only + * ever holds the current build, and anything else means this copy is behind. + * Short and full forms count as the same commit, so the interface can show a + * short one without confusing the check. */ +static gboolean same_commit(const char *a, const char *b) { + size_t la, lb, n; + + if (!a || !b || !*a || !*b) + return FALSE; + la = strlen(a); + lb = strlen(b); + n = MIN(la, lb); + return n >= 7 && g_ascii_strncasecmp(a, b, n) == 0; } -/* Component-wise numeric comparison, so 1.0.10 sorts above 1.0.9. Missing - * components count as zero, which makes 1.1 and 1.1.0 equal. */ -static int version_cmp(const char *a, const char *b) { - char **pa = g_strsplit(strip_v(a), ".", -1); - char **pb = g_strsplit(strip_v(b), ".", -1); - guint na = g_strv_length(pa); - guint nb = g_strv_length(pb); - guint n = MAX(na, nb); - int result = 0; - - for (guint i = 0; i < n && result == 0; i++) { - gint64 va = i < na ? g_ascii_strtoll(pa[i], NULL, 10) : 0; - gint64 vb = i < nb ? g_ascii_strtoll(pb[i], NULL, 10) : 0; - - if (va != vb) - result = va < vb ? -1 : 1; - } - - g_strfreev(pa); - g_strfreev(pb); - return result; +/* Only builds published by CI carry a commit; see APP_COMMIT in maturita.h. */ +static gboolean is_published_build(void) { + return g_ascii_isxdigit(APP_COMMIT[0]) && strlen(APP_COMMIT) >= 7; } -/* Only tagged builds know their version; see APP_VERSION in maturita.h. */ -static gboolean is_release_build(void) { - return g_ascii_isdigit(strip_v(APP_VERSION)[0]); +/* Commit hashes are unreadable at full length; seven characters is what git + * itself shows. */ +static char *short_commit(const char *sha) { + return g_strndup(sha ? sha : "", 7); } /* ------------------------------------------------------------------ */ @@ -272,22 +270,28 @@ static void update_refresh_ui(void) { switch (up.state) { case UPDATE_IDLE: - owned = g_strdup_printf(tr("update_current"), APP_VERSION); + case UPDATE_UP_TO_DATE: { + char *mine = short_commit(APP_COMMIT); + + owned = g_strdup_printf(tr(up.state == UPDATE_IDLE + ? "update_current" + : "update_uptodate"), mine); status = owned; + g_free(mine); break; + } case UPDATE_CHECKING: status = tr("update_checking"); break; - case UPDATE_UP_TO_DATE: - owned = g_strdup_printf(tr("update_uptodate"), APP_VERSION); - status = owned; - break; - case UPDATE_AVAILABLE: - owned = g_strdup_printf(tr("update_available"), - up.latest ? up.latest : ""); + case UPDATE_AVAILABLE: { + char *newest = short_commit(up.latest); + + owned = g_strdup_printf(tr("update_available"), newest); status = owned; action = "update_install"; + g_free(newest); break; + } case UPDATE_DOWNLOADING: status = tr("update_downloading"); action = "update_install"; @@ -581,7 +585,7 @@ static void size_probe_done(GObject *src, GAsyncResult *res, gpointer data) { if (up.state != UPDATE_DOWNLOADING) return; - url = g_strconcat(UPDATE_ASSET_URL, asset_name(), NULL); + url = g_strconcat(UPDATE_BASE_URL, asset_name(), NULL); argv[0] = "curl"; argv[1] = "-fsSL"; argv[2] = "--max-time"; @@ -630,7 +634,7 @@ static void update_start_download(void) { update_set_state(UPDATE_DOWNLOADING); /* HEAD the asset with its headers on stdout, to size the progress bar. */ - url = g_strconcat(UPDATE_ASSET_URL, asset_name(), NULL); + url = g_strconcat(UPDATE_BASE_URL, asset_name(), NULL); argv[0] = "curl"; argv[1] = "-fsSLI"; argv[2] = "--max-time"; @@ -651,18 +655,6 @@ static void update_start_download(void) { /* Check */ /* ------------------------------------------------------------------ */ -/* The effective URL ends in /releases/tag/. */ -static char *tag_from_url(const char *url) { - const char *slash; - - if (!url || !*url) - return NULL; - slash = g_strrstr(url, "/"); - if (!slash || !slash[1]) - return NULL; - return g_strdup(slash + 1); -} - /* A failed background check stays silent; only an explicit one reports. */ static void check_giveup(void) { if (up.interactive) @@ -674,7 +666,6 @@ static void check_giveup(void) { static void check_done(GObject *src, GAsyncResult *res, gpointer data) { GSubprocess *proc = G_SUBPROCESS(src); char *out = NULL; - char *tag; gboolean ok; (void)data; @@ -682,30 +673,30 @@ static void check_done(GObject *src, GAsyncResult *res, gpointer data) { ok = g_subprocess_get_successful(proc); g_object_unref(proc); - tag = ok ? tag_from_url(g_strstrip(out ? out : "")) : NULL; - g_free(out); - if (!tag) { + if (out) + g_strstrip(out); + if (!ok || !out || !*out) { + g_free(out); check_giveup(); return; } g_free(up.latest); - up.latest = g_strdup(strip_v(tag)); - g_free(tag); + up.latest = out; - if (version_cmp(APP_VERSION, up.latest) >= 0) + if (same_commit(APP_COMMIT, up.latest)) update_set_state(up.interactive ? UPDATE_UP_TO_DATE : UPDATE_IDLE); else if (update_supported()) update_set_state(UPDATE_AVAILABLE); else if (up.interactive) - /* Newer release exists, but this copy cannot swap itself out. */ + /* A newer build exists, but this copy cannot swap itself out. */ update_fail(tr("update_err_unsupported")); else update_set_state(UPDATE_IDLE); } void update_check_async(gboolean interactive) { - const char *argv[10]; + const char *argv[8]; if (up.state == UPDATE_CHECKING || up.state == UPDATE_DOWNLOADING || up.state == UPDATE_STAGED) @@ -713,8 +704,8 @@ void update_check_async(gboolean interactive) { up.interactive = interactive; - /* Nothing to compare against without a baked-in version. */ - if (!is_release_build()) { + /* Nothing to compare against without a baked-in commit. */ + if (!is_published_build()) { if (interactive) update_fail(tr("update_err_devbuild")); return; @@ -722,16 +713,16 @@ void update_check_async(gboolean interactive) { update_set_state(UPDATE_CHECKING); + /* The CDN in front of raw.githubusercontent.com caches for a few minutes, + * so ask it not to hand back a stale commit. */ argv[0] = "curl"; - argv[1] = "-fsSLI"; + argv[1] = "-fsSL"; argv[2] = "--max-time"; argv[3] = "20"; - argv[4] = "-o"; - argv[5] = NULL_DEVICE; - argv[6] = "-w"; - argv[7] = "%{url_effective}"; - argv[8] = UPDATE_LATEST_URL; - argv[9] = NULL; + argv[4] = "-H"; + argv[5] = "Cache-Control: no-cache"; + argv[6] = UPDATE_VERSION_URL; + argv[7] = NULL; if (!curl_async(argv, check_done)) { if (interactive)