bar: update indicator
This commit is contained in:
parent
01540ca9d2
commit
9f843558c0
4 changed files with 175 additions and 0 deletions
|
|
@ -1,3 +1,4 @@
|
|||
// Modules
|
||||
export const bar = {
|
||||
wsPerGroup: 10,
|
||||
dateTimeFormat: "%d/%m/%y %R",
|
||||
|
|
@ -19,3 +20,8 @@ export const notifpopups = {
|
|||
maxPopups: -1,
|
||||
expire: false,
|
||||
};
|
||||
|
||||
// Services
|
||||
export const updates = {
|
||||
interval: 900000,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import AstalNotifd from "gi://AstalNotifd";
|
|||
import AstalTray from "gi://AstalTray";
|
||||
import { bar as config } from "../config";
|
||||
import Players from "../services/players";
|
||||
import Updates from "../services/updates";
|
||||
import { getAppCategoryIcon } from "../utils/icons";
|
||||
import { ellipsize } from "../utils/strings";
|
||||
import { osIcon } from "../utils/system";
|
||||
|
|
@ -307,6 +308,21 @@ const StatusIcons = () => (
|
|||
</box>
|
||||
);
|
||||
|
||||
const PkgUpdates = () => (
|
||||
<box
|
||||
className="module updates"
|
||||
setup={self =>
|
||||
setupCustomTooltip(
|
||||
self,
|
||||
bind(Updates.get_default(), "numUpdates").as(n => `${n} update${n === 1 ? "" : "s"} available`)
|
||||
)
|
||||
}
|
||||
>
|
||||
<label className="icon" label="download" />
|
||||
<label label={bind(Updates.get_default(), "numUpdates").as(String)} />
|
||||
</box>
|
||||
);
|
||||
|
||||
const Notifications = () => {
|
||||
const unreadCount = Variable(0);
|
||||
return (
|
||||
|
|
@ -396,6 +412,7 @@ export default ({ monitor }: { monitor: AstalHyprland.Monitor }) => (
|
|||
<box halign={Gtk.Align.END}>
|
||||
<Tray />
|
||||
<StatusIcons />
|
||||
<PkgUpdates />
|
||||
<Notifications />
|
||||
<DateTime />
|
||||
<Power />
|
||||
|
|
|
|||
|
|
@ -88,6 +88,10 @@
|
|||
color: scheme.$rosewater;
|
||||
}
|
||||
|
||||
.updates {
|
||||
color: scheme.$blue;
|
||||
}
|
||||
|
||||
.notifications {
|
||||
color: scheme.$mauve;
|
||||
}
|
||||
|
|
|
|||
148
services/updates.ts
Normal file
148
services/updates.ts
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue