aboutsummaryrefslogtreecommitdiffstats
path: root/mail-overview/Accounts.qml
diff options
context:
space:
mode:
Diffstat (limited to 'mail-overview/Accounts.qml')
-rw-r--r--mail-overview/Accounts.qml195
1 files changed, 0 insertions, 195 deletions
diff --git a/mail-overview/Accounts.qml b/mail-overview/Accounts.qml
deleted file mode 100644
index 0ae6886..0000000
--- a/mail-overview/Accounts.qml
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License version 2 as
-// published by the Free Software Foundation.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-pragma Singleton
-
-import Quickshell
-import Quickshell.Io
-import QtQuick
-
-// Which accounts exist, how much unread mail each has, and what the newest of
-// it is.
-//
-// The account list is not here. qtmaildir.conf already carries one
-// [account.<key>] section per account, where <key> is exactly the suffix of
-// the notmuch tag account-<key>, plus a short label and a colour. Parsing that
-// means adding an account to qtmaildir makes it appear here with no edit.
-Singleton {
- id: root
-
- readonly property string config: `${Quickshell.env("HOME")}/.config/qtmaildir/qtmaildir.conf`
-
- // Counting by the account-* tag rather than by a path glob is deliberate.
- // notmuch deduplicates by message id, so a message that arrived at two of
- // the configured addresses is one message with two paths: a path glob
- // counts it under both accounts and the rows then sum to more than the
- // total the waybar icon shows. The tag is a property of the message, so
- // it is singular and the rows always sum to the header.
- readonly property string scope: "tag:unread and tag:inbox"
-
- // [{ key, label, color, count, threads: [{ authors, date, subject }] }]
- // count is -1 until known, and stays -1 on failure: a zero there would
- // read as an empty inbox.
- property var accounts: []
-
- property string error: ""
- property bool loading: false
-
- readonly property int total:
- accounts.reduce((sum, a) => sum + Math.max(a.count, 0), 0)
-
- readonly property bool anyUnknown: accounts.some(a => a.count < 0)
-
- // The config is watched, so adding an account in qtmaildir updates this
- // list without restarting the shell.
- FileView {
- path: root.config
- watchChanges: true
- onFileChanged: reload()
- onLoadFailed: {
- root.accounts = [];
- root.error = "cannot read qtmaildir.conf";
- }
- onLoaded: {
- root.accounts = root.parseAccounts(text());
- root.error = root.accounts.length ? "" : "no accounts in qtmaildir.conf";
- root.refresh();
- }
- }
-
- // Sections in file order, which is the display order.
- //
- // A line walk rather than one regex over the whole file. The obvious
- // pattern for a section body, "everything up to the next [", is wrong
- // here: several accounts carry folder names like "[Gmail]/Bozze", so the
- // body ended before its label and three of five accounts silently fell
- // back to displaying their key. Walking lines says what it means, and the
- // only state is which section we are in.
- function parseAccounts(conf) {
- const out = [];
- let cur = null;
-
- for (const line of conf.split("\n")) {
- // At least one key contains a dot of its own, so the key runs to
- // the closing bracket rather than to the first dot.
- const header = line.match(/^\[account\.([^\]]+)\]/);
- if (header) {
- cur = { key: header[1], label: "", color: "", count: -1, threads: [] };
- out.push(cur);
- continue;
- }
- // Any other section ends the current account.
- if (line.startsWith("[")) {
- cur = null;
- continue;
- }
- if (!cur) continue;
-
- const field = line.match(/^\s*(label|color)\s*=\s*(.+)$/);
- if (field) cur[field[1]] = field[2].trim();
- }
-
- // A missing label shows the key, which is ugly but true.
- for (const a of out) if (!a.label) a.label = a.key;
- return out;
- }
-
- function refresh() {
- if (!accounts.length) return;
- loading = true;
- proc.next = 0;
- proc.loadNext();
- }
-
- // One process per account, in turn, fetching the count and the newest
- // three threads together and splitting on a marker: two calls are needed
- // because a search limited to three rows cannot report the total, and
- // doing them as one command keeps the pair consistent.
- Process {
- id: proc
- property int next: 0
- property int current: 0
-
- function loadNext() {
- if (next >= root.accounts.length) {
- root.loading = false;
- return;
- }
- current = next;
- next++;
- const q = `${root.scope} and tag:account-${root.accounts[current].key}`;
- command = ["sh", "-c",
- `notmuch count ${JSON.stringify(q)}; ` +
- `echo '===SPLIT==='; ` +
- `notmuch search --format=json --limit=3 --sort=newest-first ${JSON.stringify(q)}`];
- // Assigning true to an already-true `running` does nothing, and
- // this Process is reused for every account in turn.
- running = false;
- running = true;
- }
-
- stdout: StdioCollector {
- onStreamFinished: {
- root.applyResult(proc.current, text);
- proc.loadNext();
- }
- }
-
- onExited: code => {
- // A non-zero exit leaves this account's count at whatever it was,
- // which for a first load is -1 and renders as a dash. Carrying on
- // to the next account matters: one failure must not blank the
- // whole panel.
- if (code !== 0) proc.loadNext();
- }
- }
-
- function applyResult(index, out) {
- const parts = out.split("===SPLIT===");
- if (parts.length < 2) return;
-
- const next = accounts.slice();
- const acct = Object.assign({}, next[index]);
-
- // The output is the test, not the exit status: a query notmuch rejects
- // prints nothing, and an empty string must not become a zero, which
- // would read as "no new mail".
- const n = parts[0].trim();
- acct.count = /^\d+$/.test(n) ? parseInt(n, 10) : -1;
-
- acct.threads = [];
- try {
- const rows = JSON.parse(parts[1]);
- if (Array.isArray(rows)) {
- acct.threads = rows.map(r => ({
- authors: String(r.authors ?? ""),
- date: String(r.date_relative ?? ""),
- subject: String(r.subject ?? "(no subject)"),
- }));
- }
- } catch (e) {
- // Keep the count, drop the thread list: a count with no preview is
- // still useful, and an empty search is a legitimate "[]".
- }
-
- next[index] = acct;
- accounts = next;
- }
-
- // Launching qtmaildir. It takes no arguments, so there is nothing to tell
- // it about the account or thread clicked.
- Process { id: openProc; command: [`${Quickshell.env("HOME")}/bin/qtmaildir`] }
-
- function openClient() {
- openProc.running = false;
- openProc.running = true;
- }
-}