diff options
Diffstat (limited to 'mail-overview/Accounts.qml')
| -rw-r--r-- | mail-overview/Accounts.qml | 195 |
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; - } -} |
