Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ class GithubStoreApp : Application() {
// here at the earliest startup opportunity.
if (existing.isPendingInstall) {
resolveSelfPendingInstall(existing, repo)
} else {
// Backfill for #515: pre-fix releases left rows
// with `installedVersion` (tag) pinned to the
// pre-update tag even though the system already
// holds the new APK. Detect the drift on cold
// start and normalize so checkForUpdates stops
// re-flagging the row as updatable on every
// periodic sweep.
normalizeSelfInstalledVersion(existing, repo)
}
return@launch
}
Expand Down Expand Up @@ -214,6 +223,42 @@ class GithubStoreApp : Application() {
* and still has the flag set — the typical scenario after a
* successful self-update where the broadcast path missed.
*/
/**
* Self-heal stale `installedVersion` tags on the self-row carried
* over from before #515 was fixed. Only fires when:
* - The row exists and is not pending an install.
* - The system's package versionCode matches the row's
* `installedVersionCode` (so the user's *system* says the
* install is finished).
* - The row's tag (`installedVersion`) doesn't match
* `latestVersion` (which the pre-install update worker wrote
* to the intended new tag).
* Sets `installedVersion = latestVersion` and clears
* `isUpdateAvailable`. Cheap, idempotent, side-effect-free
* otherwise.
*/
private suspend fun normalizeSelfInstalledVersion(
existing: InstalledApp,
repo: InstalledAppsRepository,
) {
val latestTag = existing.latestVersion ?: return
if (existing.installedVersion == latestTag) return
try {
val packageMonitor = get<PackageMonitor>()
val systemInfo = packageMonitor.getInstalledPackageInfo(packageName) ?: return
if (systemInfo.versionCode != existing.installedVersionCode) return
repo.updateApp(
existing.copy(
installedVersion = latestTag,
isUpdateAvailable = false,
),
)
Logger.i { "Normalized stale self installedVersion tag to $latestTag" }
} catch (e: Exception) {
Logger.w(e) { "Failed to normalize self installedVersion tag" }
}
}

private suspend fun resolveSelfPendingInstall(
existing: InstalledApp,
repo: InstalledAppsRepository,
Expand All @@ -223,15 +268,22 @@ class GithubStoreApp : Application() {
val systemInfo = packageMonitor.getInstalledPackageInfo(packageName)
if (systemInfo != null) {
val latestVersionCode = existing.latestVersionCode ?: 0L
// Also pin `installedVersion` (the tag string) to the
// intended new release. Otherwise `checkForUpdates`
// compares the freshly-fetched matched-release tag to
// the *previous* tag still in the row and re-flags
// isUpdateAvailable on every periodic sweep (#515).
val resolvedTag = existing.latestVersion ?: systemInfo.versionName
repo.updateApp(
existing.copy(
isPendingInstall = false,
installedVersion = resolvedTag,
installedVersionName = systemInfo.versionName,
installedVersionCode = systemInfo.versionCode,
isUpdateAvailable = latestVersionCode > systemInfo.versionCode,
),
)
Logger.i { "Resolved self-update pending install: ${systemInfo.versionName} (code=${systemInfo.versionCode})" }
Logger.i { "Resolved self-update pending install: ${systemInfo.versionName} (code=${systemInfo.versionCode}, tag=$resolvedTag)" }
} else {
repo.updatePendingStatus(packageName, false)
Logger.i { "Resolved self-update pending install (no system info)" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,17 +371,37 @@ class InstalledAppsRepositoryImpl(

val (matchedRelease, primaryAsset, variantWasLost) = resolved

// Canary: when installedVersionCode and latestVersionCode are
// both known and equal, the user already has the matched
// release. The tag-string compare below can still flip true
// (#515) if `installedVersion` got out of sync — e.g. after
// a self-update where the resolver path missed updating the
// tag. Trust the version-code parity over the string compare.
val installedCode = app.installedVersionCode
val latestCode = app.latestVersionCode
val codesAlreadyMatch =
installedCode != null &&
installedCode > 0L &&
latestCode != null &&
latestCode > 0L &&
installedCode == latestCode

val isUpdateAvailable =
VersionMath.isVersionNewer(
candidate = matchedRelease.tagName,
current = app.installedVersion,
)
if (codesAlreadyMatch) {
false
} else {
VersionMath.isVersionNewer(
candidate = matchedRelease.tagName,
current = app.installedVersion,
)
}

Logger.d {
"Update check for ${app.appName}: " +
"installedTag=${app.installedVersion}, " +
"matchedTag=${matchedRelease.tagName}, " +
"matchedAsset=${primaryAsset.name}, " +
"codesMatch=$codesAlreadyMatch, " +
"isUpdate=$isUpdateAvailable, variantLost=$variantWasLost"
}

Expand All @@ -399,6 +419,25 @@ class InstalledAppsRepositoryImpl(
latestReleasePublishedAt = matchedRelease.publishedAt,
)

// Backfill: when codes already match but the row's
// `installedVersion` (tag) drifted from the matched release
// (typical for rows written before #515 was fixed — e.g.
// installedVersion="1.7.0" left over from before a
// self-update completed), pin the tag to the matched
// release so subsequent checks don't keep re-flagging.
if (codesAlreadyMatch &&
installedCode != null &&
app.installedVersion != matchedRelease.tagName
) {
installedAppsDao.updateInstalledVersion(
packageName = packageName,
installedVersion = matchedRelease.tagName,
installedVersionName = app.installedVersionName,
installedVersionCode = installedCode,
isUpdateAvailable = false,
)
}

// Sync the staleness flag with what the resolver actually
// observed: flip on when the user's pinned variant has
// disappeared from the latest matching release, flip off
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,24 @@ class SyncInstalledAppsUseCase(
val systemInfo = packageMonitor.getInstalledPackageInfo(app.packageName)
if (systemInfo != null) {
val latestVersionCode = app.latestVersionCode ?: 0L
// Also pin `installedVersion` (tag) to the
// intended new release. Skipping it leaves
// checkForUpdates re-flagging the row as
// updatable on every sweep because the
// tag-string compare keeps seeing the old
// version (#515).
val resolvedTag = app.latestVersion ?: systemInfo.versionName
installedAppsRepository.updateApp(
app.copy(
isPendingInstall = false,
installedVersion = resolvedTag,
installedVersionName = systemInfo.versionName,
installedVersionCode = systemInfo.versionCode,
isUpdateAvailable = latestVersionCode > systemInfo.versionCode,
),
)
logger.info(
"Resolved pending install: ${app.packageName} (v${systemInfo.versionName}, code=${systemInfo.versionCode})",
"Resolved pending install: ${app.packageName} (v${systemInfo.versionName}, code=${systemInfo.versionCode}, tag=$resolvedTag)",
)
} else {
installedAppsRepository.updatePendingStatus(app.packageName, false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"Self-update no longer leaves apps stuck on \"Preparing to install\".",
"Desktop now trusts OS-installed root certificates — Watt Toolkit, FastGithub, Fiddler, and corporate MITM proxies just work without keytool gymnastics.",
"Windows installer launch no longer fails on accounts with non-ASCII usernames or unusual filename characters.",
"Liquid Glass effect removed — icons in dark mode could become invisible against transparent backgrounds. Cleaner, higher-contrast UI everywhere."
"Liquid Glass effect removed — icons in dark mode could become invisible against transparent backgrounds. Cleaner, higher-contrast UI everywhere.",
"Store no longer claims an update is available for itself after you've already updated it."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"التحديث الذاتي لم يعد يترك التطبيقات معلّقة على «التحضير للتثبيت».",
"تطبيق سطح المكتب يثق الآن بشهادات الجذر المثبَّتة في النظام — Watt Toolkit و FastGithub و Fiddler ووكلاء MITM للشركات تعمل دون الحاجة إلى استخدام keytool.",
"تشغيل المثبّت على Windows لم يعد يفشل في الحسابات ذات أسماء المستخدمين غير ASCII أو الملفات بأسماء غير اعتيادية.",
"تمت إزالة تأثير الزجاج السائل — قد تختفي الأيقونات في الوضع الداكن على الخلفيات الشفافة. واجهة أنظف بتباين أعلى في كل مكان."
"تمت إزالة تأثير الزجاج السائل — قد تختفي الأيقونات في الوضع الداكن على الخلفيات الشفافة. واجهة أنظف بتباين أعلى في كل مكان.",
"المتجر لم يعد يدّعي توفر تحديث لنفسه بعد أن تكون قد حدّثته بالفعل."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"সেলফ-আপডেট আর অ্যাপগুলোকে 'ইনস্টলের জন্য প্রস্তুত হচ্ছে' অবস্থায় আটকে রাখে না।",
"ডেস্কটপ এখন OS-এ ইনস্টল করা রুট সার্টিফিকেটগুলোকে বিশ্বাস করে — Watt Toolkit, FastGithub, Fiddler আর কর্পোরেট MITM প্রক্সি keytool ছাড়াই কাজ করে।",
"Windows ইনস্টলার চালু করা আর non-ASCII ইউজারনেম বা অস্বাভাবিক ফাইলনেমে ব্যর্থ হয় না।",
"লিকুইড গ্লাস ইফেক্ট সরানো হয়েছে — ডার্ক মোডে স্বচ্ছ ব্যাকগ্রাউন্ডে আইকন অদৃশ্য হয়ে যেতে পারত। সর্বত্র পরিষ্কার, উচ্চ-কনট্রাস্ট UI।"
"লিকুইড গ্লাস ইফেক্ট সরানো হয়েছে — ডার্ক মোডে স্বচ্ছ ব্যাকগ্রাউন্ডে আইকন অদৃশ্য হয়ে যেতে পারত। সর্বত্র পরিষ্কার, উচ্চ-কনট্রাস্ট UI।",
"স্টোর আপনি ইতিমধ্যে আপডেট করার পরে আর নিজের জন্য আপডেট আছে দাবি করে না।"
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"La auto-actualización ya no deja apps atascadas en «Preparando para instalar».",
"El escritorio ahora confía en los certificados raíz instalados en el sistema — Watt Toolkit, FastGithub, Fiddler y proxies MITM corporativos funcionan sin tener que usar keytool.",
"El lanzamiento del instalador en Windows ya no falla con cuentas que tienen nombres de usuario no ASCII o nombres de archivo inusuales.",
"Efecto de cristal líquido eliminado — los iconos en modo oscuro podían volverse invisibles sobre fondos transparentes. Interfaz más limpia y con mayor contraste en todas partes."
"Efecto de cristal líquido eliminado — los iconos en modo oscuro podían volverse invisibles sobre fondos transparentes. Interfaz más limpia y con mayor contraste en todas partes.",
"La tienda ya no anuncia una actualización propia disponible después de que la hayas actualizado."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"L’auto-mise à jour ne laisse plus d’apps bloquées sur « Préparation de l’installation ».",
"Le bureau fait désormais confiance aux certificats racine installés par l’OS — Watt Toolkit, FastGithub, Fiddler et les proxys MITM d’entreprise fonctionnent sans manip keytool.",
"Le lancement de l’installateur sous Windows ne plante plus pour les comptes dont le nom d’utilisateur contient des caractères non-ASCII ou des noms de fichier inhabituels.",
"Effet verre liquide supprimé — les icônes en mode sombre pouvaient devenir invisibles sur des fonds transparents. Interface plus nette et plus contrastée partout."
"Effet verre liquide supprimé — les icônes en mode sombre pouvaient devenir invisibles sur des fonds transparents. Interface plus nette et plus contrastée partout.",
"Le store ne signale plus une mise à jour disponible pour lui-même après que vous l'avez déjà mis à jour."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"सेल्फ़-अपडेट अब ऐप्स को 'इंस्टॉल के लिए तैयार किया जा रहा है' पर अटका नहीं छोड़ता।",
"डेस्कटॉप अब OS में इंस्टॉल किए गए रूट सर्टिफिकेट पर भरोसा करता है — Watt Toolkit, FastGithub, Fiddler और कॉर्पोरेट MITM प्रॉक्सी keytool के बिना भी काम करते हैं।",
"Windows इंस्टॉलर लॉन्च अब non-ASCII यूज़रनेम या असामान्य फ़ाइलनेम वाले अकाउंट पर विफल नहीं होता।",
"लिक्विड ग्लास इफ़ेक्ट हटा दिया गया — डार्क मोड में पारदर्शी पृष्ठभूमि पर आइकन अदृश्य हो सकते थे। हर जगह साफ़, उच्च-कंट्रास्ट UI।"
"लिक्विड ग्लास इफ़ेक्ट हटा दिया गया — डार्क मोड में पारदर्शी पृष्ठभूमि पर आइकन अदृश्य हो सकते थे। हर जगह साफ़, उच्च-कंट्रास्ट UI।",
"स्टोर अब पहले से अपडेट होने के बाद अपने लिए अपडेट उपलब्ध होने का दावा नहीं करता।"
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"L’aggiornamento automatico non lascia più app bloccate su «Preparazione all’installazione».",
"Il desktop ora si fida dei certificati radice installati dal sistema — Watt Toolkit, FastGithub, Fiddler e i proxy MITM aziendali funzionano senza manovre con keytool.",
"L’avvio dell’installer su Windows non fallisce più per account con nomi utente non ASCII o nomi file insoliti.",
"Effetto vetro liquido rimosso — le icone in modalità scura potevano diventare invisibili su sfondi trasparenti. Interfaccia più pulita e con maggior contrasto ovunque."
"Effetto vetro liquido rimosso — le icone in modalità scura potevano diventare invisibili su sfondi trasparenti. Interfaccia più pulita e con maggior contrasto ovunque.",
"Lo store non segnala più un aggiornamento disponibile per sé stesso dopo che lo hai già aggiornato."
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"セルフアップデート後にアプリが「インストールの準備中」のまま固まる問題を修正。",
"デスクトップが OS にインストール済みのルート証明書を信頼するようになりました。Watt Toolkit、FastGithub、Fiddler、社内 MITM プロキシも keytool 不要で動作します。",
"Windows のインストーラー起動が、非 ASCII のユーザー名や特殊な文字を含むファイル名でも失敗しなくなりました。",
"リキッドグラス効果を削除しました — ダークモードでは透明な背景にアイコンが見えなくなることがありました。全体的にすっきりした、コントラストの高い UI になりました。"
"リキッドグラス効果を削除しました — ダークモードでは透明な背景にアイコンが見えなくなることがありました。全体的にすっきりした、コントラストの高い UI になりました。",
"ストアを更新したあとも自分自身に更新があると表示し続ける問題を修正しました。"
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"셀프 업데이트 후 앱이 ‘설치 준비 중’ 상태로 멈춰 있던 문제를 수정했습니다.",
"데스크톱이 OS에 설치된 루트 인증서를 신뢰합니다 — Watt Toolkit, FastGithub, Fiddler, 사내 MITM 프록시가 keytool 작업 없이 그대로 동작합니다.",
"Windows 설치 프로그램 실행이 비 ASCII 사용자 이름 또는 특수 문자가 포함된 파일 이름에서도 실패하지 않습니다.",
"리퀴드 글라스 효과 제거 — 다크 모드의 투명 배경 위에서 아이콘이 보이지 않을 수 있었습니다. 모든 곳에서 더 깔끔하고 대비가 높은 UI로 개선되었습니다."
"리퀴드 글라스 효과 제거 — 다크 모드의 투명 배경 위에서 아이콘이 보이지 않을 수 있었습니다. 모든 곳에서 더 깔끔하고 대비가 높은 UI로 개선되었습니다.",
"스토어를 업데이트한 뒤에도 자신에 대해 업데이트가 있다고 계속 알리던 문제를 수정했습니다."
]
}
]
Expand Down
Loading