diff --git a/docs/DEVELOPPEMENT.md b/docs/DEVELOPPEMENT.md index dc80cd1..e0afee7 100644 --- a/docs/DEVELOPPEMENT.md +++ b/docs/DEVELOPPEMENT.md @@ -665,6 +665,10 @@ Historique complet — **à ne pas réintroduire** (utile pour diff/revert) : d'unicité dès la moindre donnée existante → import échouait. → mode ÉCRASEMENT (effacement ordonné enfants→parents puis restauration) + restauration du tConfig + reprogrammation des rappels. +37. **Upload d'asset Gitea instable** (v1.2.10) : le `?name=` du POST et le + PATCH de renommage peuvent être ignorés (asset au nom générique, APK + release disparu de la release). → `ensure_asset` dans le script : + upload + vérification nom/taille + retry PATCH + échec bruyant. 36. **Sens des boutons de zoom inversé** (v1.2.10, retour utilisateur) : le « + » dézoomait (fenêtre plus longue) et le « − » zoomait — contre la convention carte. → « + » = zoom avant (fenêtre courte), « − » = @@ -788,10 +792,11 @@ Le script : - crée la release si absente, sinon **met à jour le corps** (PATCH) ; - attache l'APK (remplace l'asset du même nom si présent) — appeler le script DEUX FOIS pour publier les deux APK (noms distincts) ; - ⚠️ renommage défensif : un asset peut se retrouver au nom générique après - une session d'upload interrompue (extinction machine — arrivé en v1.2.9) → - le script RENOMME l'asset via l'API d'édition (`PATCH …/assets/{id}`) - après chaque upload — ne pas retirer ; + ⚠️ vérification intégrée (`ensure_asset`) : le `?name=` de l'upload et/ou + le PATCH peuvent être ignorés par les instances (assets au nom générique / + APK disparu — v1.2.6, v1.2.10) → le script vérifie **nom ET taille** après + chaque upload, retente le PATCH une fois, et **échoue bruyamment** si + l'asset ne colle pas — ne pas retirer ; `HormoneTrack-vX.Y.Z-release.apk` (**recommandé**, R8) et `HormoneTrack-vX.Y.Z-debug.apk` ; - lit le token Gitea dans le trousseau macOS (`security find-internet-password`) ; diff --git a/scripts/gitea-release.py b/scripts/gitea-release.py index daa95a1..78910a6 100755 --- a/scripts/gitea-release.py +++ b/scripts/gitea-release.py @@ -20,6 +20,7 @@ Pour ajouter un token au trousseau : -w -U """ import json +import os import re import subprocess import sys @@ -83,6 +84,36 @@ def changelog_section(version: str) -> str: return m.group(1).strip() +def ensure_asset(base: str, token: str, release_id: int, name: str, path: str) -> dict: + """Upload + vérification (nom ET taille) + retry PATCH — échec bruyant. + Le ?name= de l'upload et/ou le PATCH peuvent être ignorés par certaines + instances (v1.2.6/v1.2.10 : asset au nom générique, APK release disparu).""" + import uuid + expected = os.path.getsize(path) + boundary = uuid.uuid4().hex + with open(path, "rb") as f: + data = (f"--{boundary}\r\nContent-Disposition: form-data; name=\"attachment\"; " + f"filename=\"{name}\"\r\n" + f"Content-Type: application/vnd.android.package-archive\r\n\r\n").encode() \ + + f.read() + f"\r\n--{boundary}--\r\n".encode() + req = urllib.request.Request(f"{base}/releases/{release_id}/assets?name={name}", + method="POST", + headers={"Authorization": f"token {token}", + "Content-Type": f"multipart/form-data; boundary={boundary}"}) + req.data = data + uploaded = json.loads(urllib.request.urlopen(req, timeout=300).read().decode()) + + for _ in range(2): + release = request(f"{base}/releases/{release_id}", token=token) + current = next((a for a in release["assets"] if a["id"] == uploaded["id"]), None) + if current and current["name"] == name and abs(current["size"] - expected) < 1000: + return current + request(f"{base}/releases/{release_id}/assets/{uploaded['id']}", + "PATCH", token=token, data={"name": name}) + sys.exit(f"✗ ÉCHEC : l'asset {name} n'a pas le bon nom/taille après retry — " + f"la release est incomplète, publier manuellement.") + + def main(): args = sys.argv[1:] # Premier argument = instance s'il n'est pas un tag (un tag commence par « v ») @@ -128,16 +159,11 @@ def main(): if asset["name"] == asset_name: request(f"{base}/releases/{release_id}/assets/{asset['id']}", "DELETE", token=token) - with open(apk_path, "rb") as f: - asset = request( - f"{base}/releases/{release_id}/assets?name={asset_name}", - "POST", token=token, - files={"name": asset_name, "data": f.read()}, - ) - # ⚠️ Renommage défensif : le nom d'asset peut se retrouver générique - # (ex. nom générique observé après une session d'upload INTERROMPUE — - # extinction machine en plein script) → renommage explicite via l'API - # d'édition d'asset, systématique après chaque upload. + # ⚠️ Renommage défensif + VÉRIFICATION : le ?name= de l'upload et/ou le + # PATCH peuvent être ignorés par certaines instances (nom générique + # observé en v1.2.6/10) → vérifier nom + taille après upload, retenter + # le PATCH une fois, et ÉCHOUER bruyamment si l'asset ne colle pas. + ensure_asset(base, token, release_id, asset_name, apk_path) request(f"{base}/releases/{release_id}/assets/{asset['id']}", "PATCH", token=token, data={"name": asset_name}) link = asset.get("browser_download_url", "?")