From 2c2930e8760eb4cac52f6d8310b9e5670b5fbebf Mon Sep 17 00:00:00 2001 From: bbedward Date: Mon, 27 Oct 2025 16:13:10 -0400 Subject: [PATCH] proc: timeout to CLI helper --- Common/Proc.qml | 38 ++++++++- Services/DSearchService.qml | 163 ++---------------------------------- 2 files changed, 42 insertions(+), 159 deletions(-) diff --git a/Common/Proc.qml b/Common/Proc.qml index 4a736a4f..f694772f 100644 --- a/Common/Proc.qml +++ b/Common/Proc.qml @@ -9,20 +9,23 @@ Singleton { id: root property int defaultDebounceMs: 50 + property int defaultTimeoutMs: 10000 property var _procDebouncers: ({}) // id -> { timer, command, callback, waitMs } - function runCommand(id, command, callback, debounceMs) { + function runCommand(id, command, callback, debounceMs, timeoutMs) { const wait = (typeof debounceMs === "number" && debounceMs >= 0) ? debounceMs : defaultDebounceMs - let procId = id ? id : Math.random() + const timeout = (typeof timeoutMs === "number" && timeoutMs > 0) ? timeoutMs : defaultTimeoutMs + let procId = id ? id : Math.random() if (!_procDebouncers[procId]) { const t = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root) t.triggered.connect(function() { _launchProc(procId) }) - _procDebouncers[procId] = { timer: t, command: command, callback: callback, waitMs: wait } + _procDebouncers[procId] = { timer: t, command: command, callback: callback, waitMs: wait, timeoutMs: timeout } } else { _procDebouncers[procId].command = command _procDebouncers[procId].callback = callback _procDebouncers[procId].waitMs = wait + _procDebouncers[procId].timeoutMs = timeout } const entry = _procDebouncers[procId] @@ -37,34 +40,61 @@ Singleton { const proc = Qt.createQmlObject('import Quickshell.Io; Process { running: false }', root) const out = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc) const err = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc) + const timeoutTimer = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root) proc.stdout = out proc.stderr = err proc.command = entry.command let capturedOut = "" + let capturedErr = "" let exitSeen = false let exitCodeValue = -1 + let outSeen = false + let errSeen = false + let timedOut = false + + timeoutTimer.interval = entry.timeoutMs + timeoutTimer.triggered.connect(function() { + if (!exitSeen) { + timedOut = true + proc.running = false + exitSeen = true + exitCodeValue = 124 + maybeComplete() + } + }) out.streamFinished.connect(function() { capturedOut = out.text || "" + outSeen = true + maybeComplete() + }) + + err.streamFinished.connect(function() { + capturedErr = err.text || "" + errSeen = true maybeComplete() }) proc.exited.connect(function(code) { + timeoutTimer.stop() exitSeen = true exitCodeValue = code maybeComplete() }) function maybeComplete() { - if (!exitSeen) return + if (!exitSeen || !outSeen || !errSeen) return + timeoutTimer.stop() if (typeof entry.callback === "function") { try { entry.callback(capturedOut, exitCodeValue) } catch (e) { console.warn("runCommand callback error:", e) } } try { proc.destroy() } catch (_) {} + try { timeoutTimer.destroy() } catch (_) {} } proc.running = true + timeoutTimer.start() } } diff --git a/Services/DSearchService.qml b/Services/DSearchService.qml index d97fdb6b..e9b02dae 100644 --- a/Services/DSearchService.qml +++ b/Services/DSearchService.qml @@ -12,7 +12,7 @@ Singleton { id: root property bool dsearchAvailable: false - property int requestIdCounter: 0 + property int searchIdCounter: 0 signal searchResultsReceived(var results) signal statsReceived(var stats) @@ -121,6 +121,12 @@ Singleton { callback({ "error": error }) } } + } else if (exitCode === 124) { + const error = "search timed out" + errorOccurred(error) + if (callback) { + callback({ "error": error }) + } } else { const error = "search failed" errorOccurred(error) @@ -128,160 +134,7 @@ Singleton { callback({ "error": error }) } } - }, 100) - } - - function getStats(callback) { - if (!dsearchAvailable) { - if (callback) { - callback({ "error": "dsearch not available" }) - } - return - } - - Proc.runCommand("dsearch-stats", ["dsearch", "stats", "--json"], (stdout, exitCode) => { - if (exitCode === 0) { - try { - const response = JSON.parse(stdout) - statsReceived(response) - if (callback) { - callback({ "result": response }) - } - } catch (e) { - const error = "failed to parse stats response" - errorOccurred(error) - if (callback) { - callback({ "error": error }) - } - } - } else { - const error = "stats failed" - errorOccurred(error) - if (callback) { - callback({ "error": error }) - } - } - }) - } - - function sync(callback) { - if (!dsearchAvailable) { - if (callback) { - callback({ "error": "dsearch not available" }) - } - return - } - - Proc.runCommand("dsearch-sync", ["dsearch", "sync", "--json"], (stdout, exitCode) => { - if (callback) { - if (exitCode === 0) { - try { - const response = JSON.parse(stdout) - callback({ "result": response }) - } catch (e) { - callback({ "error": "failed to parse sync response" }) - } - } else { - callback({ "error": "sync failed" }) - } - } - }) - } - - function reindex(callback) { - if (!dsearchAvailable) { - if (callback) { - callback({ "error": "dsearch not available" }) - } - return - } - - Proc.runCommand("dsearch-reindex", ["dsearch", "reindex", "--json"], (stdout, exitCode) => { - if (callback) { - if (exitCode === 0) { - try { - const response = JSON.parse(stdout) - callback({ "result": response }) - } catch (e) { - callback({ "error": "failed to parse reindex response" }) - } - } else { - callback({ "error": "reindex failed" }) - } - } - }) - } - - function watchStart(callback) { - if (!dsearchAvailable) { - if (callback) { - callback({ "error": "dsearch not available" }) - } - return - } - - Proc.runCommand("dsearch-watch-start", ["dsearch", "watch", "start", "--json"], (stdout, exitCode) => { - if (callback) { - if (exitCode === 0) { - try { - const response = JSON.parse(stdout) - callback({ "result": response }) - } catch (e) { - callback({ "error": "failed to parse watch start response" }) - } - } else { - callback({ "error": "watch start failed" }) - } - } - }) - } - - function watchStop(callback) { - if (!dsearchAvailable) { - if (callback) { - callback({ "error": "dsearch not available" }) - } - return - } - - Proc.runCommand("dsearch-watch-stop", ["dsearch", "watch", "stop", "--json"], (stdout, exitCode) => { - if (callback) { - if (exitCode === 0) { - try { - const response = JSON.parse(stdout) - callback({ "result": response }) - } catch (e) { - callback({ "error": "failed to parse watch stop response" }) - } - } else { - callback({ "error": "watch stop failed" }) - } - } - }) - } - - function watchStatus(callback) { - if (!dsearchAvailable) { - if (callback) { - callback({ "error": "dsearch not available" }) - } - return - } - - Proc.runCommand("dsearch-watch-status", ["dsearch", "watch", "status", "--json"], (stdout, exitCode) => { - if (callback) { - if (exitCode === 0) { - try { - const response = JSON.parse(stdout) - callback({ "result": response }) - } catch (e) { - callback({ "error": "failed to parse watch status response" }) - } - } else { - callback({ "error": "watch status failed" }) - } - } - }) + }, 100, 5000) } function rediscover() {