Skip to content

Commit e77d26d

Browse files
Merge pull request #598 from appdevforall/feat/K2GO-430-dashnode-dependency
K2GO-430 feat(dependency): require a minimum dash-node version per module (Forgejo 1.3.7)
2 parents 54c5fb9 + 98ccce9 commit e77d26d

40 files changed

Lines changed: 440 additions & 11 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* ============================================================================
3+
* Name : DashNodeRequirement.java
4+
* Author : AppDevForAll
5+
* Copyright : Copyright (c) 2026 AppDevForAll
6+
* Description : K2GO-430. Per-module minimum dash-node version. Pure data + lookup; the version compare
7+
* and the box read live elsewhere (redesign.DashboardVersion), so this stays a plain,
8+
* unit-testable registry with no Android or network dependency.
9+
*
10+
* Forgejo needs the dashboard build that ships its repo status/refresh endpoints (1.3.7):
11+
* an older dash-node makes "Install repos"/"Update repos" and the seed break silently, so
12+
* installing Forgejo is HARD-blocked below that version. Other modules have no hard minimum
13+
* (a soft "update the dashboard" suggestion applies to all installs, handled in the gate).
14+
* ============================================================================
15+
*/
16+
package org.appdevforall.k2go.dependency.domain;
17+
18+
import androidx.annotation.Nullable;
19+
20+
public final class DashNodeRequirement {
21+
private DashNodeRequirement() {}
22+
23+
/** {major, minor, patch} minimum dash-node for {@code moduleKey}, or null when it has no hard minimum. */
24+
@Nullable
25+
public static int[] minFor(@Nullable String moduleKey) {
26+
if ("forgejo".equals(moduleKey)) return new int[]{1, 3, 7};
27+
return null;
28+
}
29+
30+
/** Human "x.y.z" label for the minimum, or null when there is none. */
31+
@Nullable
32+
public static String minLabel(@Nullable String moduleKey) {
33+
int[] m = minFor(moduleKey);
34+
return m == null ? null : m[0] + "." + m[1] + "." + m[2];
35+
}
36+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* ============================================================================
3+
* Name : DashNodeGate.java
4+
* Author : AppDevForAll
5+
* Copyright : Copyright (c) 2026 AppDevForAll
6+
* Description : K2GO-430. Gate a module install on the box dash-node version. Two mechanisms:
7+
* HARD block : a module that declares a minimum (DashNodeRequirement, e.g. Forgejo -> 1.3.7)
8+
* cannot install on a box below it; show a dialog and route to the dashboard update.
9+
* SOFT suggest : for a batch, when a dashboard update is available, recommend updating first
10+
* (Update / Install anyway). Reuses the existing version read (DashboardVersion, from the
11+
* rootfs, no network/proot) and the cached update-check (UpdateStatusCache), so the gate is
12+
* synchronous.
13+
* ============================================================================
14+
*/
15+
package org.appdevforall.k2go.dependency.presentation;
16+
17+
import android.app.Activity;
18+
import android.content.Context;
19+
import android.content.Intent;
20+
21+
import androidx.annotation.NonNull;
22+
import androidx.annotation.Nullable;
23+
24+
import org.appdevforall.k2go.R;
25+
import org.appdevforall.k2go.dependency.domain.DashNodeRequirement;
26+
import org.appdevforall.k2go.redesign.DashboardVersion;
27+
import org.appdevforall.k2go.redesign.OpReturnNavigator;
28+
import org.appdevforall.k2go.redesign.UpdateStatusCache;
29+
import org.appdevforall.k2go.ui.dialog.BrandDialog;
30+
31+
public final class DashNodeGate {
32+
private DashNodeGate() {}
33+
34+
/** True when the box dash-node is below {@code moduleKey}'s hard minimum (null key / no minimum = false).
35+
* A null (unreadable) installed version blocks a module that HAS a minimum: do not install it against
36+
* a dashboard we cannot verify. */
37+
public static boolean installBlocked(@NonNull Context ctx, @Nullable String moduleKey) {
38+
int[] min = DashNodeRequirement.minFor(moduleKey);
39+
return min != null && !DashboardVersion.atLeast(DashboardVersion.installed(ctx), min[0], min[1], min[2]);
40+
}
41+
42+
/** The first key below its dash-node minimum, or null. */
43+
@Nullable
44+
private static String firstBlocked(@NonNull Context ctx, @Nullable String[] keys) {
45+
if (keys == null) return null;
46+
for (String k : keys) if (installBlocked(ctx, k)) return k;
47+
return null;
48+
}
49+
50+
/**
51+
* Gate a module install batch before it runs. If any banked module needs a newer dash-node than the
52+
* box has, block and route to the dashboard update. Otherwise, if a dashboard update is available,
53+
* suggest it first (Update / Install anyway). Otherwise run {@code onProceed} straight away.
54+
*/
55+
public static void guardModuleBatch(@NonNull Activity act, @Nullable String[] bankedKeys, @NonNull Runnable onProceed) {
56+
String blocked = firstBlocked(act, bankedKeys);
57+
if (blocked != null) { showBlocked(act, blocked); return; }
58+
if (UpdateStatusCache.updateAvailable(act)) { showSuggest(act, onProceed); return; }
59+
onProceed.run();
60+
}
61+
62+
/**
63+
* Gate a single-module (re)install, e.g. a retry: returns false (and shows the block dialog) when the
64+
* module is below its dash-node minimum, true when it may proceed. Hard block only (a retry does not
65+
* carry the soft update suggestion). Covers the install paths that start the index directly, not via
66+
* the batch gate.
67+
*/
68+
public static boolean allowInstall(@NonNull Context ctx, @Nullable String moduleKey) {
69+
if (installBlocked(ctx, moduleKey)) { showBlocked(ctx, moduleKey); return false; }
70+
return true;
71+
}
72+
73+
private static void showBlocked(@NonNull Context ctx, @NonNull String moduleKey) {
74+
new BrandDialog(ctx)
75+
.setTitle(R.string.k2go_dep_required_title)
76+
.setMessage(ctx.getString(R.string.k2go_dep_required_msg_fmt, DashNodeRequirement.minLabel(moduleKey)))
77+
.setPositive(R.string.k2go_dep_update_dashboard, BrandDialog.Role.PRIMARY, () -> openDashboard(ctx))
78+
.setNegative(android.R.string.cancel, null)
79+
.show();
80+
}
81+
82+
private static void showSuggest(@NonNull Context ctx, @NonNull Runnable onProceed) {
83+
new BrandDialog(ctx)
84+
.setTitle(R.string.k2go_dep_suggest_title)
85+
.setMessage(R.string.k2go_dep_suggest_msg)
86+
.setPositive(R.string.k2go_dep_update_dashboard, BrandDialog.Role.PRIMARY, () -> openDashboard(ctx))
87+
.setNegative(R.string.k2go_dep_install_anyway, onProceed::run)
88+
// The suggestion is optional: dismissing it (back / tap-outside) proceeds with the install
89+
// the user already asked for, rather than silently swallowing it.
90+
.setOnCancel(onProceed::run)
91+
.show();
92+
}
93+
94+
/** Open the dashboard detail (the Rebuild/Update card) so the user can update the dash-node. Reuses the
95+
* canonical navigation intent instead of rebuilding it here. */
96+
private static void openDashboard(@NonNull Context ctx) {
97+
Intent i = OpReturnNavigator.dashboardRebuild(ctx);
98+
if (!(ctx instanceof Activity)) i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
99+
ctx.startActivity(i);
100+
}
101+
}

‎controller/app/src/main/java/org/appdevforall/k2go/redesign/ModuleRetry.java‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ public static boolean fire(View anchor, String moduleKey) {
5454
Snackbars.make(anchor, BusyMessage.resFor(ctx)).show();
5555
return false;
5656
}
57+
// K2GO-430: a retry starts the install directly (not via openModuleIndex), so gate the dash-node
58+
// dependency here too. If the box dash-node is below the module's minimum, the gate shows the
59+
// "update the dashboard first" dialog and the retry does not fire.
60+
if (!org.appdevforall.k2go.dependency.presentation.DashNodeGate.allowInstall(ctx, moduleKey)) {
61+
return false;
62+
}
5763
InstallService.retryModules(ctx, Collections.singletonList(moduleKey));
5864
return true;
5965
}

‎controller/app/src/main/java/org/appdevforall/k2go/redesign/SetupLibraryActivity.java‎

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -659,17 +659,23 @@ public void openDashboardDetail() {
659659
* banked in ModuleWishlist; the index drains them through the proot queue (ModuleProvisioner),
660660
* same mechanism as maps. */
661661
public void openModuleIndex() {
662-
// ADFA-5228: module app installs are proot (STOPPED) runroles — confirm before entering the
663-
// index that drains them. Class is read from the operation (the first banked module); a LIVE
664-
// op would pass straight through the gate.
665-
String[] banked = ModuleWishlist.keys(this);
666-
org.appdevforall.k2go.system.domain.Operation op =
667-
org.appdevforall.k2go.system.domain.Operation.appInstall(banked.length > 0 ? banked[0] : "");
668-
// K2GO-404: the runrole fetches over the network inside the proot, so gate on metered cost at
669-
// the START (like the rootfs/maps installs); a decline leaves the modules banked.
670-
InstallConfirm.gate(this, op, () ->
671-
org.appdevforall.k2go.networkpolicy.presentation.NetworkPolicyGate.guardHeavyStart(this,
672-
() -> startActivity(new Intent(this, SetupProgressActivity.class))));
662+
final String[] banked = ModuleWishlist.keys(this);
663+
// K2GO-430: a banked module may need a newer dash-node than the box has (Forgejo -> 1.3.7). Block
664+
// the batch and route to the dashboard update when any module is below its minimum; else, when a
665+
// dashboard update is available, suggest it first. This gates every install path (they all run
666+
// through here) before the proot queue starts.
667+
org.appdevforall.k2go.dependency.presentation.DashNodeGate.guardModuleBatch(this, banked, () -> {
668+
// ADFA-5228: module app installs are proot (STOPPED) runroles — confirm before entering the
669+
// index that drains them. Class is read from the operation (the first banked module); a LIVE
670+
// op would pass straight through the gate.
671+
org.appdevforall.k2go.system.domain.Operation op =
672+
org.appdevforall.k2go.system.domain.Operation.appInstall(banked.length > 0 ? banked[0] : "");
673+
// K2GO-404: the runrole fetches over the network inside the proot, so gate on metered cost at
674+
// the START (like the rootfs/maps installs); a decline leaves the modules banked.
675+
InstallConfirm.gate(this, op, () ->
676+
org.appdevforall.k2go.networkpolicy.presentation.NetworkPolicyGate.guardHeavyStart(this,
677+
() -> startActivity(new Intent(this, SetupProgressActivity.class))));
678+
});
673679
}
674680

675681
/** ADFA-4952: open the dedicated backup/restore job screen (mode = MODE_BACKUP / MODE_RESTORE). */

‎controller/app/src/main/res/values-ar/strings.xml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,4 +1370,10 @@
13701370
<string name="k2go_forgejo_update_failed">تعذّر تحديث المستودعات</string>
13711371
<string name="k2go_forgejo_update_cancelling">جارٍ الإلغاء...</string>
13721372
<string name="k2go_forgejo_update_cancelled">تم إلغاء التحديث</string>
1373+
<string name="k2go_dep_required_title">حدّث dashboard أولاً</string>
1374+
<string name="k2go_dep_required_msg_fmt">تحتاج هذه الوحدة إلى dashboard بالإصدار %1$s أو أحدث قبل تثبيتها.</string>
1375+
<string name="k2go_dep_update_dashboard">تحديث dashboard</string>
1376+
<string name="k2go_dep_suggest_title">يتوفر تحديث لـ dashboard</string>
1377+
<string name="k2go_dep_suggest_msg">يُوصى بتحديث dashboard أولاً حتى تعمل ميزات الوحدة الجديدة بشكل صحيح.</string>
1378+
<string name="k2go_dep_install_anyway">التثبيت على أي حال</string>
13731379
</resources>

‎controller/app/src/main/res/values-az/strings.xml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,4 +1390,10 @@
13901390
<string name="k2go_forgejo_update_failed">Anbarlar yenilene bilmedi</string>
13911391
<string name="k2go_forgejo_update_cancelling">Legv edilir...</string>
13921392
<string name="k2go_forgejo_update_cancelled">Yenileme legv edildi</string>
1393+
<string name="k2go_dep_required_title">Evvelce dashboard-u yenileyin</string>
1394+
<string name="k2go_dep_required_msg_fmt">Bu modul qurasdirila bilmesi ucun %1$s ve ya daha yeni dashboard versiyasina ehtiyac duyur.</string>
1395+
<string name="k2go_dep_update_dashboard">Dashboard-u yenile</string>
1396+
<string name="k2go_dep_suggest_title">Dashboard yenilemesi movcuddur</string>
1397+
<string name="k2go_dep_suggest_msg">Yeni modul funksiyalarinin duzgun islemesi ucun evvelce dashboard-u yenilemek tovsiye olunur.</string>
1398+
<string name="k2go_dep_install_anyway">Yene de qurasdir</string>
13931399
</resources>

‎controller/app/src/main/res/values-bg/strings.xml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,4 +1377,10 @@
13771377
<string name="k2go_forgejo_update_failed">Хранилищата не можаха да бъдат обновени</string>
13781378
<string name="k2go_forgejo_update_cancelling">Отказване...</string>
13791379
<string name="k2go_forgejo_update_cancelled">Обновяването е отказано</string>
1380+
<string name="k2go_dep_required_title">Първо обновете dashboard</string>
1381+
<string name="k2go_dep_required_msg_fmt">Този модул се нуждае от dashboard версия %1$s или по-нова, преди да може да се инсталира.</string>
1382+
<string name="k2go_dep_update_dashboard">Обнови dashboard</string>
1383+
<string name="k2go_dep_suggest_title">Налична е актуализация на dashboard</string>
1384+
<string name="k2go_dep_suggest_msg">Препоръчва се първо да обновите dashboard, за да работят правилно новите функции на модула.</string>
1385+
<string name="k2go_dep_install_anyway">Инсталирай въпреки това</string>
13801386
</resources>

‎controller/app/src/main/res/values-bn/strings.xml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,4 +1383,10 @@
13831383
<string name="k2go_forgejo_update_failed">রিপোজিটরি আপডেট করা যায়নি</string>
13841384
<string name="k2go_forgejo_update_cancelling">বাতিল করা হচ্ছে...</string>
13851385
<string name="k2go_forgejo_update_cancelled">আপডেট বাতিল করা হয়েছে</string>
1386+
<string name="k2go_dep_required_title">প্রথমে dashboard আপডেট করুন</string>
1387+
<string name="k2go_dep_required_msg_fmt">ইনস্টল করার আগে এই মডিউলের dashboard %1$s বা নতুন সংস্করণ প্রয়োজন।</string>
1388+
<string name="k2go_dep_update_dashboard">dashboard আপডেট করুন</string>
1389+
<string name="k2go_dep_suggest_title">dashboard আপডেট উপলব্ধ</string>
1390+
<string name="k2go_dep_suggest_msg">মডিউলের নতুন বৈশিষ্ট্য ঠিকমতো কাজ করার জন্য প্রথমে dashboard আপডেট করার পরামর্শ দেওয়া হয়।</string>
1391+
<string name="k2go_dep_install_anyway">তবুও ইনস্টল করুন</string>
13861392
</resources>

‎controller/app/src/main/res/values-cs/strings.xml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,4 +1377,10 @@
13771377
<string name="k2go_forgejo_update_failed">Repozitare se nepodarilo aktualizovat</string>
13781378
<string name="k2go_forgejo_update_cancelling">Ruseni...</string>
13791379
<string name="k2go_forgejo_update_cancelled">Aktualizace zrusena</string>
1380+
<string name="k2go_dep_required_title">Nejprve aktualizujte dashboard</string>
1381+
<string name="k2go_dep_required_msg_fmt">Tento modul potrebuje dashboard ve verzi %1$s nebo novejsi, nez jej bude mozne nainstalovat.</string>
1382+
<string name="k2go_dep_update_dashboard">Aktualizovat dashboard</string>
1383+
<string name="k2go_dep_suggest_title">Je dostupna aktualizace dashboardu</string>
1384+
<string name="k2go_dep_suggest_msg">Doporucujeme nejprve aktualizovat dashboard, aby nove funkce modulu fungovaly spravne.</string>
1385+
<string name="k2go_dep_install_anyway">Presto nainstalovat</string>
13801386
</resources>

‎controller/app/src/main/res/values-de/strings.xml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,4 +1370,10 @@
13701370
<string name="k2go_forgejo_update_failed">Repositorys konnten nicht aktualisiert werden</string>
13711371
<string name="k2go_forgejo_update_cancelling">Wird abgebrochen...</string>
13721372
<string name="k2go_forgejo_update_cancelled">Aktualisierung abgebrochen</string>
1373+
<string name="k2go_dep_required_title">Zuerst das Dashboard aktualisieren</string>
1374+
<string name="k2go_dep_required_msg_fmt">Dieses Modul benoetigt das Dashboard in Version %1$s oder neuer, bevor es installiert werden kann.</string>
1375+
<string name="k2go_dep_update_dashboard">Dashboard aktualisieren</string>
1376+
<string name="k2go_dep_suggest_title">Ein Dashboard-Update ist verfuegbar</string>
1377+
<string name="k2go_dep_suggest_msg">Es wird empfohlen, zuerst das Dashboard zu aktualisieren, damit neue Modulfunktionen richtig laufen.</string>
1378+
<string name="k2go_dep_install_anyway">Trotzdem installieren</string>
13731379
</resources>

0 commit comments

Comments
 (0)