summaryrefslogtreecommitdiff
path: root/services/updates.ts
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-01-15 13:15:06 +1100
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-01-15 13:15:06 +1100
commit9f843558c029daa85cdcb6bc20e7b837c751be08 (patch)
treeaa51dbd7b5eb2f4fa71b6578bed93c127bac8d11 /services/updates.ts
parentPass home and cache through bundler (diff)
downloadcaelestia-shell-9f843558c029daa85cdcb6bc20e7b837c751be08.tar.gz
caelestia-shell-9f843558c029daa85cdcb6bc20e7b837c751be08.tar.bz2
caelestia-shell-9f843558c029daa85cdcb6bc20e7b837c751be08.zip
bar: update indicator
Diffstat (limited to 'services/updates.ts')
-rw-r--r--services/updates.ts148
1 files changed, 148 insertions, 0 deletions
diff --git a/services/updates.ts b/services/updates.ts
new file mode 100644
index 0000000..0b04e85
--- /dev/null
+++ b/services/updates.ts
@@ -0,0 +1,148 @@
+import { execAsync, GLib, GObject, property, readFileAsync, register, writeFileAsync } from "astal";
+import { updates as config } from "../config";
+
+interface Update {
+ name: string;
+ full: string;
+}
+
+interface Repo {
+ repo?: string[];
+ updates: Update[];
+ icon: string;
+ name: string;
+}
+
+interface Data {
+ cached?: boolean;
+ repos: Repo[];
+ errors: string[];
+}
+
+@register({ GTypeName: "Updates" })
+export default class Updates extends GObject.Object {
+ static instance: Updates;
+ static get_default() {
+ if (!this.instance) this.instance = new Updates();
+
+ return this.instance;
+ }
+
+ readonly #cachePath = `${CACHE}/updates.txt`;
+
+ #timeout?: GLib.Source;
+ #loading = false;
+ #data: Data = { cached: true, repos: [], errors: [] };
+
+ @property(Boolean)
+ get loading() {
+ return this.#loading;
+ }
+
+ @property(Object)
+ get data() {
+ return this.#data;
+ }
+
+ @property(Object)
+ get list() {
+ return this.#data.repos.map(r => r.updates).flat();
+ }
+
+ @property(Number)
+ get numUpdates() {
+ return this.#data.repos.reduce((acc, repo) => acc + repo.updates.length, 0);
+ }
+
+ async #updateFromCache() {
+ this.#data = JSON.parse(await readFileAsync(this.#cachePath));
+ this.notify("data");
+ this.notify("list");
+ this.notify("num-updates");
+ }
+
+ async #getRepo(repo: string) {
+ return (await execAsync(`bash -c "comm -12 <(pacman -Qq | sort) <(pacman -Slq '${repo}' | sort)"`)).split("\n");
+ }
+
+ getUpdates() {
+ // Return if already getting updates
+ if (this.#loading) return;
+
+ this.#loading = true;
+ this.notify("loading");
+
+ // Get new updates
+ Promise.allSettled([execAsync("checkupdates"), execAsync("yay -Qua")])
+ .then(async ([pacman, yay]) => {
+ const data: Data = { repos: [], errors: [] };
+
+ // Pacman updates (checkupdates)
+ if (pacman.status === "fulfilled") {
+ const repos: Repo[] = [
+ { repo: await this.#getRepo("core"), updates: [], icon: "hub", name: "Core repository" },
+ {
+ repo: await this.#getRepo("extra"),
+ updates: [],
+ icon: "add_circle",
+ name: "Extra repository",
+ },
+ {
+ repo: await this.#getRepo("multilib"),
+ updates: [],
+ icon: "account_tree",
+ name: "Multilib repository",
+ },
+ ];
+
+ for (const update of pacman.value.split("\n")) {
+ const pkg = update.split(" ")[0];
+ for (const repo of repos)
+ if (repo.repo?.includes(pkg)) repo.updates.push({ name: pkg, full: update });
+ }
+
+ for (const repo of repos) if (repo.updates.length > 0) data.repos.push(repo);
+ }
+
+ // AUR and devel updates (yay -Qua)
+ if (yay.status === "fulfilled") {
+ const aur: Repo = { updates: [], icon: "deployed_code_account", name: "AUR" };
+
+ for (const update of yay.value.split("\n")) {
+ if (/^\s*->/.test(update)) data.errors.push(update); // Error
+ else aur.updates.push({ name: update.split(" ")[0], full: update });
+ }
+
+ if (aur.updates.length > 0) data.repos.push(aur);
+ }
+
+ if (data.errors.length > 0 && data.repos.length === 0) {
+ this.#updateFromCache().catch(console.error);
+ } else {
+ // Cache and set
+ writeFileAsync(this.#cachePath, JSON.stringify({ cached: true, ...data })).catch(console.error);
+ this.#data = data;
+ this.notify("data");
+ this.notify("list");
+ this.notify("num-updates");
+ }
+
+ this.#loading = false;
+ this.notify("loading");
+
+ this.#timeout?.destroy();
+ this.#timeout = setTimeout(() => this.getUpdates(), config.interval);
+ })
+ .catch(console.error);
+ }
+
+ constructor() {
+ super();
+
+ // Initial update from cache, if fail then write valid data to cache so future reads don't fail
+ this.#updateFromCache().catch(() =>
+ writeFileAsync(this.#cachePath, JSON.stringify(this.#data)).catch(console.error)
+ );
+ this.getUpdates();
+ }
+}