704 lines
23 KiB
QML
704 lines
23 KiB
QML
pragma Singleton
|
|
|
|
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import qs.Commons
|
|
|
|
Singleton {
|
|
id: updateService
|
|
|
|
// ============================================================================
|
|
// CORE PROPERTIES
|
|
// ============================================================================
|
|
|
|
// Package data
|
|
property var repoPackages: []
|
|
property var aurPackages: []
|
|
property var selectedPackages: []
|
|
property int selectedPackagesCount: 0
|
|
property string allUpdatesOutput: ""
|
|
|
|
// Update state
|
|
property bool updateInProgress: false
|
|
property bool updateFailed: false
|
|
property string lastUpdateError: ""
|
|
property bool checkFailed: false
|
|
property string lastCheckError: ""
|
|
|
|
// Monitoring state
|
|
property string capturedErrorText: ""
|
|
property string capturedSuccessText: ""
|
|
|
|
// Computed properties
|
|
readonly property bool aurBusy: checkAurUpdatesProcess.running || checkAurOnlyProcess.running
|
|
readonly property int updates: repoPackages.length
|
|
readonly property int aurUpdates: aurPackages.length
|
|
readonly property int totalUpdates: updates + aurUpdates
|
|
|
|
// Terminal validation
|
|
readonly property bool terminalAvailable: Quickshell.env("TERMINAL") !== ""
|
|
readonly property string terminalError: "TERMINAL environment variable not set"
|
|
|
|
// AUR helper validation
|
|
readonly property bool aurHelperAvailable: cachedAurHelper !== ""
|
|
readonly property string aurHelperError: "No AUR helper found (yay or paru not installed)"
|
|
|
|
// Polling cooldown (prevent excessive polling)
|
|
property int lastPollTime: 0
|
|
readonly property int pollCooldownMs: 5 * 60 * 1000 // 5 minutes
|
|
readonly property bool canPoll: (Date.now() - lastPollTime) > pollCooldownMs
|
|
|
|
// ============================================================================
|
|
// TIMERS
|
|
// ============================================================================
|
|
|
|
// Refresh timer for post-update polling
|
|
Timer {
|
|
id: refreshTimer
|
|
interval: 5000
|
|
repeat: false
|
|
onTriggered: {
|
|
doPoll()
|
|
}
|
|
}
|
|
|
|
// Timer to mark update as complete - with error handling
|
|
Timer {
|
|
id: updateCompleteTimer
|
|
interval: 30000 // Increased to 30 seconds to allow more time
|
|
repeat: false
|
|
onTriggered: {
|
|
checkForUpdateFailures()
|
|
}
|
|
}
|
|
|
|
// Timer to check if update processes are still running
|
|
Timer {
|
|
id: updateMonitorTimer
|
|
interval: 2000
|
|
repeat: true
|
|
running: updateInProgress
|
|
onTriggered: {
|
|
// Check if any update-related processes might still be running
|
|
checkUpdateStatus()
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// MONITORING PROCESSES
|
|
// ============================================================================
|
|
|
|
// Process to monitor update completion
|
|
Process {
|
|
id: updateStatusProcess
|
|
command: ["pgrep", "-f", "(yay|paru).*(-S|-Syu)"]
|
|
onExited: function (exitCode) {
|
|
if (exitCode !== 0 && updateInProgress) {
|
|
// No update processes found, update likely completed
|
|
updateInProgress = false
|
|
updateMonitorTimer.stop()
|
|
errorCheckTimer.stop()
|
|
successCheckTimer.stop()
|
|
|
|
// Don't stop the complete timer - let it handle failures
|
|
// If the update actually failed, the timer will trigger and set updateFailed = true
|
|
|
|
// Refresh package lists after a short delay
|
|
Qt.callLater(() => {
|
|
doPoll()
|
|
}, 2000)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process to check for errors in log file (only when update is in progress)
|
|
Process {
|
|
id: errorCheckProcess
|
|
command: ["sh", "-c", "if [ -f /tmp/archupdater_output.log ]; then grep -i 'failed to build\\|could not resolve\\|unable to satisfy\\|failed to install\\|failed to upgrade\\|error:' /tmp/archupdater_output.log | grep -v 'ERROR_DETECTED' | tail -1; fi"]
|
|
onExited: function (exitCode) {
|
|
if (exitCode === 0 && updateInProgress && capturedErrorText.trim() !== "") {
|
|
// Error found in log
|
|
updateInProgress = false
|
|
updateFailed = true
|
|
updateCompleteTimer.stop()
|
|
updateMonitorTimer.stop()
|
|
errorCheckTimer.stop()
|
|
successCheckTimer.stop()
|
|
lastUpdateError = "Build or update error detected"
|
|
|
|
// Refresh to check actual state
|
|
Qt.callLater(() => {
|
|
doPoll()
|
|
}, 1000)
|
|
}
|
|
}
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
capturedErrorText = text || ""
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process to check for successful completion
|
|
Process {
|
|
id: successCheckProcess
|
|
command: ["sh", "-c", "if [ -f /tmp/archupdater_output.log ]; then grep -i 'Update complete!\\|:: Running post-transaction hooks\\|:: Processing package changes\\|upgrading.*\\.\\.\\.\\|installing.*\\.\\.\\.\\|removing.*\\.\\.\\.' /tmp/archupdater_output.log | tail -1; fi"]
|
|
onExited: function (exitCode) {
|
|
if (exitCode === 0 && updateInProgress && capturedSuccessText.trim() !== "") {
|
|
// Success indicators found
|
|
updateInProgress = false
|
|
updateFailed = false
|
|
updateCompleteTimer.stop()
|
|
updateMonitorTimer.stop()
|
|
errorCheckTimer.stop()
|
|
successCheckTimer.stop()
|
|
lastUpdateError = ""
|
|
|
|
// Refresh to check actual state
|
|
Qt.callLater(() => {
|
|
doPoll()
|
|
}, 1000)
|
|
}
|
|
}
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
capturedSuccessText = text || ""
|
|
}
|
|
}
|
|
}
|
|
|
|
// Timer to check for success more frequently when update is in progress
|
|
Timer {
|
|
id: successCheckTimer
|
|
interval: 5000 // Check every 5 seconds
|
|
repeat: true
|
|
running: updateInProgress
|
|
onTriggered: {
|
|
if (updateInProgress && !successCheckProcess.running) {
|
|
successCheckProcess.running = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Timer to check for errors more frequently when update is in progress
|
|
Timer {
|
|
id: errorCheckTimer
|
|
interval: 5000 // Check every 5 seconds
|
|
repeat: true
|
|
running: updateInProgress
|
|
onTriggered: {
|
|
if (updateInProgress && !errorCheckProcess.running) {
|
|
errorCheckProcess.running = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// MONITORING FUNCTIONS
|
|
// ============================================================================
|
|
function checkUpdateStatus() {
|
|
if (updateInProgress && !updateStatusProcess.running) {
|
|
updateStatusProcess.running = true
|
|
}
|
|
}
|
|
|
|
function checkForUpdateFailures() {
|
|
updateInProgress = false
|
|
updateFailed = true
|
|
updateCompleteTimer.stop()
|
|
updateMonitorTimer.stop()
|
|
|
|
// Refresh to check actual state after a delay
|
|
Qt.callLater(() => {
|
|
doPoll()
|
|
}, 2000)
|
|
}
|
|
|
|
// Initial check
|
|
Component.onCompleted: {
|
|
// Start AUR helper detection
|
|
getAurHelper()
|
|
|
|
// Set up a fallback timer in case detection takes too long
|
|
Qt.callLater(() => {
|
|
if (cachedAurHelper === "" && !yayCheckProcess.running && !paruCheckProcess.running) {
|
|
// No AUR helper found after reasonable time and processes have finished
|
|
checkFailed = true
|
|
lastCheckError = "No AUR helper found (yay or paru not installed)"
|
|
Logger.warn("ArchUpdater", "No AUR helper found (yay or paru)")
|
|
}
|
|
}, 5000) // 5 second fallback
|
|
}
|
|
|
|
// ============================================================================
|
|
// PACKAGE CHECKING PROCESSES
|
|
// ============================================================================
|
|
|
|
// Process for checking all updates with AUR helper (repo + AUR)
|
|
Process {
|
|
id: checkAurUpdatesProcess
|
|
command: []
|
|
onExited: function (exitCode) {
|
|
// For both yay and paru: exit code 0 = updates available, exit code 1 = no updates
|
|
if (exitCode !== 0 && exitCode !== 1) {
|
|
Logger.warn("ArchUpdater", "AUR helper check failed (code:", exitCode, ")")
|
|
checkFailed = true
|
|
lastCheckError = "Failed to check for updates (exit code: " + exitCode + ")"
|
|
aurPackages = []
|
|
repoPackages = []
|
|
}
|
|
// Don't clear checkFailed here - wait for the second process to complete
|
|
}
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
allUpdatesOutput = text
|
|
// Now get AUR-only updates to compare
|
|
checkAurOnlyProcess.running = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process for checking AUR-only updates (to separate from repo updates)
|
|
Process {
|
|
id: checkAurOnlyProcess
|
|
command: []
|
|
onExited: function (exitCode) {
|
|
// For both yay and paru: exit code 0 = updates available, exit code 1 = no updates
|
|
if (exitCode !== 0 && exitCode !== 1) {
|
|
Logger.warn("ArchUpdater", "AUR helper AUR-only check failed (code:", exitCode, ")")
|
|
checkFailed = true
|
|
lastCheckError = "Failed to check AUR updates (exit code: " + exitCode + ")"
|
|
aurPackages = []
|
|
repoPackages = []
|
|
} else {
|
|
// Only clear checkFailed if both processes succeeded
|
|
// Check if the first process also succeeded (no error was set)
|
|
if (!checkFailed) {
|
|
checkFailed = false
|
|
lastCheckError = ""
|
|
}
|
|
}
|
|
}
|
|
stdout: StdioCollector {
|
|
onStreamFinished: {
|
|
parseAllUpdatesOutput(allUpdatesOutput, text)
|
|
Logger.log("ArchUpdater", "found", repoPackages.length, "repo package(s) and", aurPackages.length, "AUR package(s) to upgrade")
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// PARSING FUNCTIONS
|
|
// ============================================================================
|
|
|
|
// Generic package parsing function
|
|
function parsePackageOutput(output, source) {
|
|
const lines = output.trim().split('\n').filter(line => line.trim())
|
|
const packages = []
|
|
|
|
for (const line of lines) {
|
|
const m = line.match(/^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/)
|
|
if (m) {
|
|
packages.push({
|
|
"name": m[1],
|
|
"oldVersion": m[2],
|
|
"newVersion": m[3],
|
|
"description": `${m[1]} ${m[2]} -> ${m[3]}`,
|
|
"source": source
|
|
})
|
|
}
|
|
}
|
|
|
|
// Only update if we have new data or if this is a fresh check
|
|
if (packages.length > 0 || output.trim() === "") {
|
|
if (source === "repo") {
|
|
repoPackages = packages
|
|
} else {
|
|
aurPackages = packages
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse all updates output (repo + AUR packages)
|
|
function parseAllUpdatesOutput(allOutput, aurOnlyOutput) {
|
|
const allLines = allOutput.trim().split('\n').filter(line => line.trim())
|
|
const aurOnlyLines = aurOnlyOutput.trim().split('\n').filter(line => line.trim())
|
|
|
|
// Create a set of AUR package names for quick lookup
|
|
const aurPackageNames = new Set()
|
|
for (const line of aurOnlyLines) {
|
|
const m = line.match(/^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/)
|
|
if (m) {
|
|
aurPackageNames.add(m[1])
|
|
}
|
|
}
|
|
|
|
const repoPackages = []
|
|
const aurPackages = []
|
|
|
|
for (const line of allLines) {
|
|
const m = line.match(/^(\S+)\s+([^\s]+)\s+->\s+([^\s]+)$/)
|
|
if (m) {
|
|
const packageInfo = {
|
|
"name": m[1],
|
|
"oldVersion": m[2],
|
|
"newVersion": m[3],
|
|
"description": `${m[1]} ${m[2]} -> ${m[3]}`
|
|
}
|
|
|
|
// Check if this package is in the AUR-only list
|
|
if (aurPackageNames.has(m[1])) {
|
|
packageInfo.source = "aur"
|
|
aurPackages.push(packageInfo)
|
|
} else {
|
|
packageInfo.source = "repo"
|
|
repoPackages.push(packageInfo)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the package lists
|
|
if (repoPackages.length > 0 || aurPackages.length > 0 || allOutput.trim() === "") {
|
|
updateService.repoPackages = repoPackages
|
|
updateService.aurPackages = aurPackages
|
|
}
|
|
}
|
|
|
|
function doPoll() {
|
|
// Prevent excessive polling
|
|
if (aurBusy || !canPoll) {
|
|
return
|
|
}
|
|
|
|
// Check if we have a cached AUR helper
|
|
if (cachedAurHelper !== "") {
|
|
// Clear error state when helper is available
|
|
if (checkFailed && lastCheckError.includes("No AUR helper found")) {
|
|
checkFailed = false
|
|
lastCheckError = ""
|
|
}
|
|
|
|
checkAurUpdatesProcess.command = [cachedAurHelper, "-Qu"]
|
|
checkAurOnlyProcess.command = [cachedAurHelper, getAurOnlyFlag()]
|
|
|
|
// Start AUR updates check (includes both repo and AUR packages)
|
|
checkAurUpdatesProcess.running = true
|
|
lastPollTime = Date.now()
|
|
} else {
|
|
// AUR helper detection is still in progress or failed
|
|
// Try to detect again if not already in progress
|
|
if (!yayCheckProcess.running && !paruCheckProcess.running) {
|
|
getAurHelper()
|
|
}
|
|
Logger.warn("ArchUpdater", "AUR helper detection in progress or failed")
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// UPDATE FUNCTIONS
|
|
// ============================================================================
|
|
|
|
// Helper function to generate update command with error detection
|
|
function generateUpdateCommand(baseCommand) {
|
|
return baseCommand + " 2>&1 | tee /tmp/archupdater_output.log; if [ $? -ne 0 ]; then echo 'ERROR_DETECTED'; fi; echo 'Update complete! Press Enter to close...'; read -p 'Press Enter to continue...'"
|
|
}
|
|
|
|
// Update all packages (repo + AUR)
|
|
function runUpdate() {
|
|
if (totalUpdates === 0) {
|
|
doPoll()
|
|
return
|
|
}
|
|
|
|
// Reset any previous error states
|
|
updateFailed = false
|
|
lastUpdateError = ""
|
|
updateInProgress = true
|
|
capturedErrorText = ""
|
|
capturedSuccessText = ""
|
|
|
|
const terminal = Quickshell.env("TERMINAL")
|
|
if (!terminal) {
|
|
updateInProgress = false
|
|
updateFailed = true
|
|
lastUpdateError = "TERMINAL environment variable not set"
|
|
ToastService.showWarning("ArchUpdater", "TERMINAL environment variable not set")
|
|
return
|
|
}
|
|
|
|
// Check if we have an AUR helper for full system update
|
|
if (cachedAurHelper !== "" && (aurUpdates > 0 || updates > 0)) {
|
|
// Use AUR helper for full system update (handles both repo and AUR)
|
|
const command = generateUpdateCommand(cachedAurHelper + " -Syu")
|
|
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
|
|
} else if (cachedAurHelper === "") {
|
|
// No AUR helper found
|
|
updateInProgress = false
|
|
updateFailed = true
|
|
lastUpdateError = "No AUR helper found (yay or paru not installed)"
|
|
Logger.warn("ArchUpdater", "No AUR helper found for update")
|
|
}
|
|
|
|
// Start monitoring and timeout timers
|
|
refreshTimer.start()
|
|
updateCompleteTimer.start()
|
|
updateMonitorTimer.start()
|
|
}
|
|
|
|
// Update selected packages
|
|
function runSelectiveUpdate() {
|
|
if (selectedPackages.length === 0)
|
|
return
|
|
|
|
// Reset any previous error states
|
|
updateFailed = false
|
|
lastUpdateError = ""
|
|
updateInProgress = true
|
|
capturedErrorText = ""
|
|
capturedSuccessText = ""
|
|
|
|
const terminal = Quickshell.env("TERMINAL")
|
|
if (!terminal) {
|
|
updateInProgress = false
|
|
updateFailed = true
|
|
lastUpdateError = "TERMINAL environment variable not set"
|
|
ToastService.showWarning("ArchUpdater", "TERMINAL environment variable not set")
|
|
return
|
|
}
|
|
|
|
// Update all packages with AUR helper (handles both repo and AUR)
|
|
if (selectedPackages.length > 0) {
|
|
if (cachedAurHelper !== "") {
|
|
const packageList = selectedPackages.join(" ")
|
|
|
|
// Handle ghostty terminal differently due to command parsing issues
|
|
if (terminal.includes("ghostty")) {
|
|
const simpleCommand = cachedAurHelper + " -S " + packageList
|
|
Quickshell.execDetached([terminal, "-e", simpleCommand])
|
|
} else {
|
|
const command = generateUpdateCommand(cachedAurHelper + " -S " + packageList)
|
|
Quickshell.execDetached([terminal, "-e", "bash", "-c", command])
|
|
}
|
|
} else {
|
|
updateInProgress = false
|
|
updateFailed = true
|
|
lastUpdateError = "No AUR helper found (yay or paru not installed)"
|
|
Logger.warn("ArchUpdater", "No AUR helper found for packages:", selectedPackages.join(", "))
|
|
}
|
|
}
|
|
|
|
// Start monitoring and timeout timers
|
|
refreshTimer.start()
|
|
updateCompleteTimer.start()
|
|
updateMonitorTimer.start()
|
|
}
|
|
|
|
// Reset update state (useful for manual recovery)
|
|
function resetUpdateState() {
|
|
// Clear all update states
|
|
updateInProgress = false
|
|
updateFailed = false
|
|
lastUpdateError = ""
|
|
checkFailed = false
|
|
lastCheckError = ""
|
|
updateCompleteTimer.stop()
|
|
updateMonitorTimer.stop()
|
|
refreshTimer.stop()
|
|
errorCheckTimer.stop()
|
|
successCheckTimer.stop()
|
|
|
|
// Refresh to get current state
|
|
doPoll()
|
|
}
|
|
|
|
// Manual refresh function (bypasses cooldown)
|
|
function forceRefresh() {
|
|
// Prevent multiple simultaneous refreshes
|
|
if (aurBusy) {
|
|
return
|
|
}
|
|
|
|
// Clear error states when refreshing
|
|
updateFailed = false
|
|
lastUpdateError = ""
|
|
checkFailed = false
|
|
lastCheckError = ""
|
|
|
|
// Check if we have a cached AUR helper
|
|
if (cachedAurHelper !== "") {
|
|
// Clear error state when helper is available
|
|
if (checkFailed && lastCheckError.includes("No AUR helper found")) {
|
|
checkFailed = false
|
|
lastCheckError = ""
|
|
}
|
|
|
|
checkAurUpdatesProcess.command = [cachedAurHelper, "-Qu"]
|
|
checkAurOnlyProcess.command = [cachedAurHelper, getAurOnlyFlag()]
|
|
|
|
// Force refresh by bypassing cooldown
|
|
checkAurUpdatesProcess.running = true
|
|
lastPollTime = Date.now()
|
|
} else {
|
|
// AUR helper detection is still in progress or failed
|
|
// Try to detect again if not already in progress
|
|
if (!yayCheckProcess.running && !paruCheckProcess.running) {
|
|
getAurHelper()
|
|
}
|
|
Logger.warn("ArchUpdater", "AUR helper detection in progress or failed")
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// UTILITY PROCESSES
|
|
// ============================================================================
|
|
|
|
// Process for checking yay availability
|
|
Process {
|
|
id: yayCheckProcess
|
|
command: ["which", "yay"]
|
|
onExited: function (exitCode) {
|
|
if (exitCode === 0) {
|
|
cachedAurHelper = "yay"
|
|
Logger.log("ArchUpdater", "Found yay AUR helper (preferred)")
|
|
// Clear error state when helper is found
|
|
if (checkFailed && lastCheckError.includes("No AUR helper found")) {
|
|
checkFailed = false
|
|
lastCheckError = ""
|
|
}
|
|
// Trigger initial check when helper is found
|
|
triggerInitialCheck()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process for checking paru availability
|
|
Process {
|
|
id: paruCheckProcess
|
|
command: ["which", "paru"]
|
|
onExited: function (exitCode) {
|
|
if (exitCode === 0) {
|
|
// Only use paru if yay wasn't found (yay is preferred)
|
|
if (cachedAurHelper === "") {
|
|
cachedAurHelper = "paru"
|
|
Logger.log("ArchUpdater", "Found paru AUR helper")
|
|
// Clear error state when helper is found
|
|
if (checkFailed && lastCheckError.includes("No AUR helper found")) {
|
|
checkFailed = false
|
|
lastCheckError = ""
|
|
}
|
|
// Trigger initial check when helper is found
|
|
triggerInitialCheck()
|
|
} else {
|
|
Logger.log("ArchUpdater", "Found paru but using", cachedAurHelper, "(preferred)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cached AUR helper detection
|
|
property string cachedAurHelper: ""
|
|
|
|
// Helper function to detect AUR helper
|
|
function getAurHelper() {
|
|
// Return cached result if available
|
|
if (cachedAurHelper !== "") {
|
|
return cachedAurHelper
|
|
}
|
|
|
|
// Check for AUR helpers using Process objects
|
|
Logger.log("ArchUpdater", "Detecting AUR helper...")
|
|
|
|
// Start the detection processes
|
|
yayCheckProcess.running = true
|
|
paruCheckProcess.running = true
|
|
|
|
// Return empty string to indicate no helper found yet
|
|
// The processes will update cachedAurHelper when they complete
|
|
return ""
|
|
}
|
|
|
|
// Helper function to get the correct AUR-only flag for the detected helper
|
|
function getAurOnlyFlag() {
|
|
if (cachedAurHelper === "yay") {
|
|
return "-Qua"
|
|
} else if (cachedAurHelper === "paru") {
|
|
return "-Qua" // paru uses the same flag but different exit code behavior
|
|
}
|
|
return "-Qua" // fallback
|
|
}
|
|
|
|
// Helper function to trigger the initial package check
|
|
function triggerInitialCheck() {
|
|
// Only trigger if this is the first time (no packages have been checked yet)
|
|
if (repoPackages.length === 0 && aurPackages.length === 0 && !aurBusy) {
|
|
// Clear any previous error state
|
|
checkFailed = false
|
|
lastCheckError = ""
|
|
|
|
// Wait a bit for the system to be ready before the first check
|
|
Qt.callLater(() => {
|
|
checkAurUpdatesProcess.command = [cachedAurHelper, "-Qu"]
|
|
checkAurOnlyProcess.command = [cachedAurHelper, getAurOnlyFlag()]
|
|
checkAurUpdatesProcess.running = true
|
|
lastPollTime = Date.now()
|
|
}, 1000)
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// PACKAGE SELECTION FUNCTIONS
|
|
// ============================================================================
|
|
function togglePackageSelection(packageName) {
|
|
const index = selectedPackages.indexOf(packageName)
|
|
if (index > -1) {
|
|
selectedPackages.splice(index, 1)
|
|
} else {
|
|
selectedPackages.push(packageName)
|
|
}
|
|
selectedPackagesCount = selectedPackages.length
|
|
}
|
|
|
|
function selectAllPackages() {
|
|
selectedPackages = [...repoPackages.map(pkg => pkg.name), ...aurPackages.map(pkg => pkg.name)]
|
|
selectedPackagesCount = selectedPackages.length
|
|
}
|
|
|
|
function deselectAllPackages() {
|
|
selectedPackages = []
|
|
selectedPackagesCount = 0
|
|
}
|
|
|
|
function isPackageSelected(packageName) {
|
|
return selectedPackages.indexOf(packageName) > -1
|
|
}
|
|
|
|
// ============================================================================
|
|
// REFRESH FUNCTIONS
|
|
// ============================================================================
|
|
|
|
// ============================================================================
|
|
// UTILITY FUNCTIONS
|
|
// ============================================================================
|
|
|
|
// Notification helper
|
|
function notify(title, body) {
|
|
Quickshell.execDetached(["notify-send", "-a", "UpdateService", "-i", "system-software-update", title, body])
|
|
}
|
|
|
|
// ============================================================================
|
|
// AUTO-POLL TIMER
|
|
// ============================================================================
|
|
|
|
// Auto-poll every 15 minutes (respects cooldown)
|
|
Timer {
|
|
interval: 15 * 60 * 1000 // 15 minutes
|
|
repeat: true
|
|
running: true
|
|
onTriggered: {
|
|
if (!updateInProgress && canPoll) {
|
|
doPoll()
|
|
}
|
|
}
|
|
}
|
|
}
|