mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 03:28:28 -04:00
feat(dms updater): add support for ignoring specific packages during system updates
- Added UI component & popout settings to manage ignored packages - Added CLI support available in danklinux docs Fixes: #2827 Closes: #2344, #1741 Port 1.5
This commit is contained in:
@@ -959,6 +959,7 @@ Singleton {
|
||||
property int updaterIntervalSeconds: 1800
|
||||
property bool updaterIncludeFlatpak: true
|
||||
property bool updaterAllowAUR: true
|
||||
property var updaterIgnoredPackages: []
|
||||
|
||||
property string displayNameMode: "system"
|
||||
property var screenPreferences: ({})
|
||||
|
||||
@@ -514,6 +514,7 @@ var SPEC = {
|
||||
updaterIntervalSeconds: { def: 1800 },
|
||||
updaterIncludeFlatpak: { def: true },
|
||||
updaterAllowAUR: { def: true },
|
||||
updaterIgnoredPackages: { def: [] },
|
||||
|
||||
displayNameMode: { def: "system" },
|
||||
screenPreferences: { def: {} },
|
||||
|
||||
@@ -76,6 +76,47 @@ DankPopout {
|
||||
|
||||
readonly property bool hasTerminalBackend: (SystemUpdateService.backends || []).some(b => b.runsInTerminal === true)
|
||||
|
||||
property int nowUnix: Math.floor(Date.now() / 1000)
|
||||
|
||||
Connections {
|
||||
target: systemUpdatePopout
|
||||
function onShouldBeVisibleChanged() {
|
||||
if (systemUpdatePopout.shouldBeVisible) {
|
||||
updaterPanel.nowUnix = Math.floor(Date.now() / 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function distroLabel() {
|
||||
const pretty = (SystemUpdateService.distributionPretty || "").trim();
|
||||
if (pretty) {
|
||||
return pretty.split(/\s+/)[0];
|
||||
}
|
||||
const id = (SystemUpdateService.distribution || "").trim();
|
||||
if (id) {
|
||||
return id.charAt(0).toUpperCase() + id.slice(1);
|
||||
}
|
||||
return I18n.tr("System");
|
||||
}
|
||||
|
||||
function lastCheckedText() {
|
||||
const last = SystemUpdateService.lastCheckUnix;
|
||||
if (!last) {
|
||||
return "";
|
||||
}
|
||||
const delta = Math.max(0, nowUnix - last);
|
||||
if (delta < 90) {
|
||||
return I18n.tr("checked just now");
|
||||
}
|
||||
if (delta < 3600) {
|
||||
return I18n.tr("checked %1m ago").arg(Math.round(delta / 60));
|
||||
}
|
||||
if (delta < 86400) {
|
||||
return I18n.tr("checked %1h ago").arg(Math.round(delta / 3600));
|
||||
}
|
||||
return I18n.tr("checked %1d ago").arg(Math.round(delta / 86400));
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
systemUpdatePopout.close();
|
||||
@@ -171,8 +212,17 @@ DankPopout {
|
||||
anchors.topMargin: Theme.spacingS
|
||||
visible: SystemUpdateService.backends.length > 0 && !SystemUpdateService.isUpgrading
|
||||
text: {
|
||||
const names = (SystemUpdateService.backends || []).map(b => b.displayName).join(", ");
|
||||
return I18n.tr("Backends: %1").arg(names);
|
||||
const kinds = [];
|
||||
for (const b of SystemUpdateService.backends || []) {
|
||||
const label = b.repo === "flatpak" ? I18n.tr("Flatpak") : I18n.tr("System");
|
||||
if (!kinds.includes(label)) {
|
||||
kinds.push(label);
|
||||
}
|
||||
}
|
||||
const distro = updaterPanel.distroLabel();
|
||||
const checked = updaterPanel.lastCheckedText();
|
||||
const base = `${distro}: ${kinds.join(", ")}`;
|
||||
return checked ? `${base} · ${checked}` : base;
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
@@ -180,6 +230,21 @@ DankPopout {
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: hiddenRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: backendsRow.visible ? backendsRow.bottom : header.bottom
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
visible: SystemUpdateService.hiddenUpdateCount > 0 && !SystemUpdateService.isUpgrading
|
||||
text: I18n.tr("%1 hidden (AUR disabled or ignored)").arg(SystemUpdateService.hiddenUpdateCount)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonsRow
|
||||
anchors.left: parent.left
|
||||
@@ -275,7 +340,7 @@ DankPopout {
|
||||
id: bodyArea
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: backendsRow.visible ? backendsRow.bottom : header.bottom
|
||||
anchors.top: hiddenRow.visible ? hiddenRow.bottom : (backendsRow.visible ? backendsRow.bottom : header.bottom)
|
||||
anchors.bottom: buttonsRow.top
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
@@ -294,7 +359,8 @@ DankPopout {
|
||||
text: {
|
||||
switch (true) {
|
||||
case SystemUpdateService.hasError:
|
||||
return I18n.tr("Failed: %1").arg(SystemUpdateService.errorMessage);
|
||||
const msg = I18n.tr("Failed: %1").arg(SystemUpdateService.errorMessage);
|
||||
return SystemUpdateService.errorHint ? `${msg}\n\n${SystemUpdateService.errorHint}` : msg;
|
||||
case !SystemUpdateService.helperAvailable:
|
||||
return I18n.tr("No supported package manager found.");
|
||||
case SystemUpdateService.isChecking:
|
||||
@@ -318,13 +384,18 @@ DankPopout {
|
||||
model: SystemUpdateService.availableUpdates
|
||||
|
||||
delegate: Rectangle {
|
||||
id: packageRow
|
||||
width: ListView.view.width
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: packageMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryHoverLight, 0)
|
||||
color: rowHoverHandler.hovered ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryHoverLight, 0)
|
||||
|
||||
required property var modelData
|
||||
|
||||
HoverHandler {
|
||||
id: rowHoverHandler
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
@@ -350,7 +421,7 @@ DankPopout {
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 64 - Theme.spacingS
|
||||
width: parent.width - 64 - Theme.spacingS * 2 - 28
|
||||
spacing: Theme.spacingXXS
|
||||
|
||||
StyledText {
|
||||
@@ -395,13 +466,26 @@ DankPopout {
|
||||
id: packageMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: modelData.changelogUrl ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
cursorShape: packageRow.modelData.changelogUrl ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
if (modelData.changelogUrl) {
|
||||
Qt.openUrlExternally(modelData.changelogUrl);
|
||||
if (packageRow.modelData.changelogUrl) {
|
||||
Qt.openUrlExternally(packageRow.modelData.changelogUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
anchors.right: packageRow.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: packageRow.verticalCenter
|
||||
buttonSize: 24
|
||||
iconName: "visibility_off"
|
||||
iconSize: 16
|
||||
iconColor: Theme.surfaceVariantText
|
||||
visible: rowHoverHandler.hovered
|
||||
tooltipText: I18n.tr("Ignore this package")
|
||||
onClicked: SystemUpdateService.ignorePackage(packageRow.modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,13 +30,20 @@ Item {
|
||||
}
|
||||
]
|
||||
|
||||
readonly property string customIntervalLabel: I18n.tr("Custom")
|
||||
property bool customIntervalSelected: false
|
||||
|
||||
Component.onCompleted: {
|
||||
customIntervalSelected = !intervalOptions.some(o => o.seconds === SettingsData.updaterIntervalSeconds);
|
||||
}
|
||||
|
||||
function intervalLabelFor(seconds) {
|
||||
for (const opt of intervalOptions) {
|
||||
if (opt.seconds === seconds) {
|
||||
return opt.label;
|
||||
}
|
||||
}
|
||||
return intervalOptions[1].label;
|
||||
return customIntervalLabel;
|
||||
}
|
||||
|
||||
function intervalSecondsFor(label) {
|
||||
@@ -84,15 +91,71 @@ Item {
|
||||
SettingsDropdownRow {
|
||||
text: I18n.tr("Check interval")
|
||||
description: I18n.tr("How often the server polls for new updates.")
|
||||
options: root.intervalOptions.map(o => o.label)
|
||||
currentValue: root.intervalLabelFor(SettingsData.updaterIntervalSeconds)
|
||||
options: root.intervalOptions.map(o => o.label).concat([root.customIntervalLabel])
|
||||
currentValue: root.customIntervalSelected ? root.customIntervalLabel : root.intervalLabelFor(SettingsData.updaterIntervalSeconds)
|
||||
onValueChanged: label => {
|
||||
if (label === root.customIntervalLabel) {
|
||||
root.customIntervalSelected = true;
|
||||
return;
|
||||
}
|
||||
root.customIntervalSelected = false;
|
||||
const secs = root.intervalSecondsFor(label);
|
||||
SettingsData.set("updaterIntervalSeconds", secs);
|
||||
SystemUpdateService.setInterval(secs);
|
||||
}
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
height: customIntervalColumn.implicitHeight
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
visible: root.customIntervalSelected
|
||||
|
||||
Column {
|
||||
id: customIntervalColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Custom interval in minutes (minimum 5)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: customIntervalField
|
||||
width: parent.width
|
||||
placeholderText: "30"
|
||||
backgroundColor: Theme.surfaceContainerHighest
|
||||
normalBorderColor: Theme.outlineMedium
|
||||
focusedBorderColor: Theme.primary
|
||||
|
||||
Component.onCompleted: {
|
||||
text = Math.round(SettingsData.updaterIntervalSeconds / 60).toString();
|
||||
}
|
||||
|
||||
onTextEdited: {
|
||||
const minutes = parseInt(text, 10);
|
||||
if (isNaN(minutes) || minutes < 5) {
|
||||
return;
|
||||
}
|
||||
const secs = minutes * 60;
|
||||
SettingsData.set("updaterIntervalSeconds", secs);
|
||||
SystemUpdateService.setInterval(secs);
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: mouse => {
|
||||
customIntervalField.forceActiveFocus();
|
||||
mouse.accepted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Check on startup")
|
||||
description: I18n.tr("When enabled, checks updates on startup. When disabled, only the interval above or a manual refresh runs a check.")
|
||||
@@ -119,6 +182,145 @@ Item {
|
||||
TerminalPickerRow {}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
id: ignoredPackagesCard
|
||||
width: parent.width
|
||||
iconName: "inventory_2"
|
||||
title: I18n.tr("Ignored Packages")
|
||||
settingKey: "systemUpdaterIgnoredPackages"
|
||||
tags: ["system", "update", "package", "ignore"]
|
||||
|
||||
function addIgnoredPackage() {
|
||||
const name = newIgnoredPackageField.text.trim();
|
||||
if (name === "") {
|
||||
return;
|
||||
}
|
||||
if (!/^[A-Za-z0-9@._+:-]+$/.test(name)) {
|
||||
ignoredPackageError.visible = true;
|
||||
return;
|
||||
}
|
||||
ignoredPackageError.visible = false;
|
||||
SystemUpdateService.ignorePackage(name);
|
||||
newIgnoredPackageField.text = "";
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: {
|
||||
if (SettingsData.updaterUseCustomCommand) {
|
||||
return I18n.tr("Ignored packages only apply to the built-in updater. Your custom command controls its own exclusions.");
|
||||
}
|
||||
return (SettingsData.updaterIgnoredPackages || []).length > 0 ? I18n.tr("Ignored packages are hidden from the updater and skipped by 'Update All'.") : I18n.tr("No packages ignored. Add one here or hover an update in the popout and click the hide button.");
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
bottomPadding: Theme.spacingS
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankTextField {
|
||||
id: newIgnoredPackageField
|
||||
width: parent.width - addIgnoredBtn.width - Theme.spacingS
|
||||
height: 36
|
||||
placeholderText: I18n.tr("Package name (e.g., docker)")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
onAccepted: ignoredPackagesCard.addIgnoredPackage()
|
||||
onTextEdited: ignoredPackageError.visible = false
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: addIgnoredBtn
|
||||
buttonSize: 36
|
||||
iconName: "add"
|
||||
iconSize: 20
|
||||
backgroundColor: Theme.primary
|
||||
iconColor: Theme.onPrimary
|
||||
tooltipText: I18n.tr("Ignore package")
|
||||
onClicked: ignoredPackagesCard.addIgnoredPackage()
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: ignoredPackageError
|
||||
visible: false
|
||||
text: I18n.tr("Invalid package name — letters, digits and @._+:- only.")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.error
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "visibility_off"
|
||||
title: I18n.tr("Ignored (%1)").arg((SettingsData.updaterIgnoredPackages || []).length)
|
||||
collapsible: true
|
||||
expanded: false
|
||||
visible: (SettingsData.updaterIgnoredPackages || []).length > 0
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, 0.5)
|
||||
|
||||
Repeater {
|
||||
model: SettingsData.updaterIgnoredPackages
|
||||
|
||||
delegate: Rectangle {
|
||||
required property string modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, 0.5)
|
||||
|
||||
DankIcon {
|
||||
id: ignoredIcon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: "visibility_off"
|
||||
size: 18
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.left: ignoredIcon.right
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: removeIgnoredBtn.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: parent.modelData
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: removeIgnoredBtn
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
buttonSize: 32
|
||||
iconName: "delete"
|
||||
iconSize: 18
|
||||
iconColor: Theme.error
|
||||
backgroundColor: "transparent"
|
||||
tooltipText: I18n.tr("Stop ignoring %1").arg(parent.modelData)
|
||||
onClicked: {
|
||||
const list = (SettingsData.updaterIgnoredPackages || []).slice();
|
||||
list.splice(parent.index, 1);
|
||||
SettingsData.set("updaterIgnoredPackages", list);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "tune"
|
||||
|
||||
@@ -15,10 +15,12 @@ Singleton {
|
||||
property bool sysupdateAvailable: false
|
||||
|
||||
property var availableUpdates: []
|
||||
property var _rawUpdates: []
|
||||
property bool isChecking: false
|
||||
property bool isUpgrading: false
|
||||
property bool hasError: false
|
||||
property string errorMessage: ""
|
||||
property string errorHint: ""
|
||||
property string errorCode: ""
|
||||
property var backends: []
|
||||
property string distribution: ""
|
||||
@@ -31,6 +33,7 @@ Singleton {
|
||||
property int nextCheckUnix: 0
|
||||
|
||||
readonly property int updateCount: availableUpdates.length
|
||||
readonly property int hiddenUpdateCount: _rawUpdates.length - availableUpdates.length
|
||||
readonly property bool helperAvailable: sysupdateAvailable && backends.length > 0
|
||||
|
||||
Connections {
|
||||
@@ -57,6 +60,12 @@ Singleton {
|
||||
function onUpdaterCheckOnStartChanged() {
|
||||
Qt.callLater(() => root._maybeStartupCheck());
|
||||
}
|
||||
function onUpdaterAllowAURChanged() {
|
||||
root._refilter();
|
||||
}
|
||||
function onUpdaterIgnoredPackagesChanged() {
|
||||
root._refilter();
|
||||
}
|
||||
function on_HasLoadedChanged() {
|
||||
Qt.callLater(() => root._maybeStartupCheck());
|
||||
}
|
||||
@@ -100,7 +109,8 @@ Singleton {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
availableUpdates = data.packages || [];
|
||||
_rawUpdates = data.packages || [];
|
||||
availableUpdates = _filterUpdates(_rawUpdates);
|
||||
backends = data.backends || [];
|
||||
distribution = data.distro || "";
|
||||
distributionPretty = data.distroPretty || "";
|
||||
@@ -129,10 +139,12 @@ Singleton {
|
||||
hasError = true;
|
||||
errorMessage = data.error.message || "";
|
||||
errorCode = data.error.code || "";
|
||||
errorHint = data.error.hint || "";
|
||||
} else {
|
||||
hasError = false;
|
||||
errorMessage = "";
|
||||
errorCode = "";
|
||||
errorHint = "";
|
||||
}
|
||||
|
||||
if (backends.length > 0) {
|
||||
@@ -143,6 +155,29 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function _filterUpdates(pkgs) {
|
||||
const ignored = SettingsData.updaterIgnoredPackages || [];
|
||||
return (pkgs || []).filter(p => {
|
||||
if (!SettingsData.updaterAllowAUR && p.repo === "aur")
|
||||
return false;
|
||||
return ignored.indexOf(p.name) === -1;
|
||||
});
|
||||
}
|
||||
|
||||
function _refilter() {
|
||||
availableUpdates = _filterUpdates(_rawUpdates);
|
||||
}
|
||||
|
||||
function ignorePackage(name) {
|
||||
if (!name)
|
||||
return;
|
||||
const list = (SettingsData.updaterIgnoredPackages || []).slice();
|
||||
if (list.indexOf(name) !== -1)
|
||||
return;
|
||||
list.push(name);
|
||||
SettingsData.set("updaterIgnoredPackages", list);
|
||||
}
|
||||
|
||||
function checkForUpdates() {
|
||||
DMSService.sysupdateRefresh(false, null);
|
||||
}
|
||||
@@ -153,6 +188,7 @@ Singleton {
|
||||
_runCustomTerminalCommand();
|
||||
return;
|
||||
}
|
||||
params.ignored = SettingsData.updaterIgnoredPackages || [];
|
||||
DMSService.sysupdateUpgrade(params, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -6804,6 +6804,23 @@
|
||||
"icon": "tune",
|
||||
"description": "Open a terminal and run a custom command instead of the in-shell upgrade flow."
|
||||
},
|
||||
{
|
||||
"section": "systemUpdaterIgnoredPackages",
|
||||
"label": "Ignored Packages",
|
||||
"tabIndex": 20,
|
||||
"category": "System Updater",
|
||||
"keywords": [
|
||||
"ignore",
|
||||
"ignored",
|
||||
"package",
|
||||
"packages",
|
||||
"system",
|
||||
"update",
|
||||
"updater",
|
||||
"upgrade"
|
||||
],
|
||||
"icon": "inventory_2"
|
||||
},
|
||||
{
|
||||
"section": "systemUpdater",
|
||||
"label": "System Updater",
|
||||
|
||||
Reference in New Issue
Block a user