| 1 | pragma Singleton |
| 2 | import QtQuick |
| 3 | import Quickshell |
| 4 | import Quickshell.Io |
| 5 | |
| 6 | // pending package updates: checkupdates (pacman-contrib, syncs a private db copy) + paru -Qua (AUR). |
| 7 | // Checked a minute after start, then every 2h; the sys tab asks for a fresh one when opened (check(false) |
| 8 | // skips it if the last one is under 10 min old). upgrade() runs paru in foot, started through Hyprland so |
| 9 | // restarting aurora can't kill a pacman transaction; the terminal calls the `updates` IPC when it closes. |
| 10 | Singleton { |
| 11 | id: root |
| 12 | property bool available: false // checkupdates installed |
| 13 | property bool checking: false |
| 14 | property bool upgrading: false |
| 15 | property var list: [] // [{name, from, to, aur}], repo packages first |
| 16 | property int repo: 0 |
| 17 | property int aur: 0 |
| 18 | readonly property int count: repo + aur |
| 19 | property date lastCheck: new Date(0) |
| 20 | property string error: "" // checkupdates failed (offline, db locked...): the old list is kept |
| 21 | |
| 22 | function check(force) { |
| 23 | if (!available || checking) return |
| 24 | if (!force && Date.now() - lastCheck.getTime() < 600000) return |
| 25 | checking = true |
| 26 | checkProc.running = true |
| 27 | } |
| 28 | |
| 29 | function upgrade() { |
| 30 | if (upgrading) return |
| 31 | upgrading = true |
| 32 | upgradeGuard.restart() |
| 33 | Quickshell.execDetached(["hyprctl", "dispatch", "hl.dsp.exec_cmd([[foot -T 'system upgrade' -e sh -c " |
| 34 | + "'paru; printf \"\\ndone, press enter to close\"; read _; qs -c aurora ipc call shell updates']])"]) |
| 35 | } |
| 36 | // called by the upgrade terminal when it closes (and usable by hand after updating elsewhere) |
| 37 | function finished() { |
| 38 | upgrading = false |
| 39 | upgradeGuard.stop() |
| 40 | check(true) |
| 41 | } |
| 42 | |
| 43 | // "name old -> new [ignored]" lines; @repo <exit code> separates the two lists |
| 44 | function parse(text) { |
| 45 | var out = [], nr = 0, na = 0, isAur = false, code = 0 |
| 46 | var lines = text.split("\n") |
| 47 | for (var i = 0; i < lines.length; i++) { |
| 48 | var l = lines[i].trim() |
| 49 | var c = l.match(/^@repo (\d+)$/) |
| 50 | if (c) { code = parseInt(c[1]); isAur = true; continue } |
| 51 | var m = l.match(/^(\S+)\s+(\S+)\s+->\s+(\S+)/) |
| 52 | if (!m || l.indexOf("[ignored]") >= 0) continue |
| 53 | out.push({ name: m[1], from: m[2], to: m[3], aur: isAur }) |
| 54 | if (isAur) na++; else nr++ |
| 55 | } |
| 56 | // checkupdates: 0 = updates, 2 = none, anything else = failed |
| 57 | if (code !== 0 && code !== 2) { |
| 58 | error = "checkupdates failed (" + code + ")" |
| 59 | out = list.filter(function (p) { return !p.aur }).concat(out.filter(function (p) { return p.aur })) |
| 60 | nr = repo |
| 61 | } else { |
| 62 | error = "" |
| 63 | } |
| 64 | list = out |
| 65 | repo = nr |
| 66 | aur = na |
| 67 | lastCheck = new Date() |
| 68 | } |
| 69 | |
| 70 | Process { |
| 71 | command: ["sh", "-c", "command -v checkupdates"] |
| 72 | running: true |
| 73 | onExited: code => root.available = code === 0 |
| 74 | } |
| 75 | |
| 76 | Process { |
| 77 | id: checkProc |
| 78 | command: ["sh", "-c", 'checkupdates 2>/dev/null; echo "@repo $?"; command -v paru >/dev/null && paru -Qua 2>/dev/null; true'] |
| 79 | stdout: StdioCollector { onStreamFinished: { root.parse(text); root.checking = false } } |
| 80 | } |
| 81 | |
| 82 | // the terminal may be closed with the window's X, skipping the IPC call |
| 83 | Timer { id: upgradeGuard; interval: 3600000; onTriggered: root.upgrading = false } |
| 84 | |
| 85 | Timer { interval: 60000; running: root.available; onTriggered: root.check(true) } |
| 86 | Timer { interval: 7200000; running: root.available; repeat: true; onTriggered: root.check(true) } |
| 87 | } |