fix init angain again again (hopefully last time)
This commit is contained in:
Binary file not shown.
+8
-3
@@ -139,7 +139,7 @@ def join_query(args) -> str:
|
||||
return " ".join(parts).strip()
|
||||
|
||||
|
||||
def call(host, port, method, route, body=None, query=None):
|
||||
def call(host, port, method, route, body=None, query=None, timeout=15):
|
||||
qs = ""
|
||||
if query:
|
||||
qs = "?" + urllib.parse.urlencode(query)
|
||||
@@ -153,7 +153,7 @@ def call(host, port, method, route, body=None, query=None):
|
||||
req = urllib.request.Request(url=url, data=data, headers=headers, method=method)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
payload = resp.read().decode("utf-8")
|
||||
return resp.status, payload
|
||||
except urllib.error.HTTPError as e:
|
||||
@@ -333,8 +333,13 @@ def main(argv=None):
|
||||
query = {"q": q}
|
||||
if limit is not None and limit > 0:
|
||||
query["limit"] = str(limit)
|
||||
# yt-dlp searches can easily take 30+ seconds on a slow network
|
||||
# (the daemon calls `ytsearchN:QUERY -j` which hits YouTube).
|
||||
# The default 15 s `urllib` timeout short-circuits the picker
|
||||
# before the daemon even returns, so give this one endpoint a
|
||||
# generous budget. All other endpoints keep the 15 s default.
|
||||
status, payload = call(
|
||||
host, port, "GET", "/search/yt", query=query
|
||||
host, port, "GET", "/search/yt", query=query, timeout=120
|
||||
)
|
||||
obj = try_parse(payload)
|
||||
results = (obj or {}).get("results") or []
|
||||
|
||||
@@ -180,7 +180,15 @@ fn handle(
|
||||
.get("limit")
|
||||
.and_then(|s| s.parse::<usize>().ok())
|
||||
.unwrap_or(state.search_count);
|
||||
match state.search_yt_sync(&q, limit) {
|
||||
// Release the synchronous state guard BEFORE calling
|
||||
// search_yt_sync — yt-dlp can take 30+ seconds on a slow
|
||||
// network and holding the daemon mutex here would freeze
|
||||
// every other endpoint (including the GUI's `/init`,
|
||||
// `/downloads`, `/now-playing` polls) for the entire
|
||||
// duration. Mirrors the drop-then-call pattern that
|
||||
// `init_sync` already uses.
|
||||
drop(state);
|
||||
match DaemonState::search_yt_sync(&q, limit) {
|
||||
Ok(results) => json_response(200, &serde_json::json!({
|
||||
"query": q,
|
||||
"limit": limit,
|
||||
|
||||
+10
-1
@@ -556,7 +556,16 @@ impl DaemonState {
|
||||
/// non-zero exit are tolerated as much as possible: an empty list is
|
||||
/// returned when nothing parsed, while a yt-dlp startup failure surfaces
|
||||
/// a descriptive error string so the CLI can show it to the user.
|
||||
pub fn search_yt_sync(&self, query: &str, limit: usize) -> Result<Vec<YtCandidate>, String> {
|
||||
///
|
||||
/// Lives as an associated function (no `&self`) so the HTTP handler can
|
||||
/// release the daemon state mutex before spawning yt-dlp. Earlier
|
||||
/// revisions invoked this as `state.search_yt_sync(...)` *while*
|
||||
/// holding the single-threaded `tiny_http` request handler's state
|
||||
/// guard, which froze the entire daemon (including `/init`, `/queue`,
|
||||
/// `/now-playing`) for the full duration of a yt-dlp search — typically
|
||||
/// 30+ seconds on a slow network. Mirrors the lock-free pattern already
|
||||
/// used for `init_sync` below.
|
||||
pub fn search_yt_sync(query: &str, limit: usize) -> Result<Vec<YtCandidate>, String> {
|
||||
let limit = limit.max(1);
|
||||
let src = format!("ytsearch{limit}:{query}");
|
||||
debug!("yt-dlp search: {src}");
|
||||
|
||||
+35
-7
@@ -330,12 +330,23 @@ impl DaemonClient {
|
||||
}
|
||||
|
||||
pub fn search_yt(&self, q: &str, limit: usize) -> Result<Vec<YtCandidate>, DaemonError> {
|
||||
// `/search/yt` shells out to `yt-dlp ytsearchN:...` on the daemon
|
||||
// side, which can easily take 30+ seconds on a slow network — well
|
||||
// past the agent's 8 s default. Override per-request so the picker
|
||||
// doesn't ghost-fail with a transport timeout. All other
|
||||
// endpoints keep the default; the daemon's state lock is no
|
||||
// longer held during the search (see daemon state.rs), so other
|
||||
// GETs (now-playing, downloads, queue) keep flowing while a
|
||||
// search is in flight.
|
||||
Ok(self
|
||||
.get::<SearchYtResult>(&format!(
|
||||
.get_with_timeout::<SearchYtResult>(
|
||||
&format!(
|
||||
"/search/yt?q={}&limit={}",
|
||||
urlencoding::encode(q),
|
||||
limit
|
||||
))?
|
||||
),
|
||||
Duration::from_secs(120),
|
||||
)?
|
||||
.results)
|
||||
}
|
||||
|
||||
@@ -484,7 +495,19 @@ impl DaemonClient {
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
fn get<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T, DaemonError> {
|
||||
self._request_json("GET", path, None)
|
||||
self._request_json("GET", path, None, None)
|
||||
}
|
||||
|
||||
/// Like `get` but lets callers override the per-request timeout. Used
|
||||
/// only for endpoints that back onto slow subprocess calls (currently
|
||||
/// just `/search/yt`, since yt-dlp's first network round-trip can blow
|
||||
/// past the 8 s agent default).
|
||||
fn get_with_timeout<T: for<'de> Deserialize<'de>>(
|
||||
&self,
|
||||
path: &str,
|
||||
timeout: Duration,
|
||||
) -> Result<T, DaemonError> {
|
||||
self._request_json("GET", path, None, Some(timeout))
|
||||
}
|
||||
|
||||
fn post_json<T: for<'de> Deserialize<'de>>(
|
||||
@@ -492,7 +515,7 @@ impl DaemonClient {
|
||||
path: &str,
|
||||
body: &serde_json::Value,
|
||||
) -> Result<T, DaemonError> {
|
||||
self._request_json("POST", path, Some(body))
|
||||
self._request_json("POST", path, Some(body), None)
|
||||
}
|
||||
|
||||
fn patch<T: for<'de> Deserialize<'de>>(
|
||||
@@ -500,7 +523,7 @@ impl DaemonClient {
|
||||
path: &str,
|
||||
body: &serde_json::Value,
|
||||
) -> Result<T, DaemonError> {
|
||||
self._request_json("PATCH", path, Some(body))
|
||||
self._request_json("PATCH", path, Some(body), None)
|
||||
}
|
||||
|
||||
fn del_json<T: for<'de> Deserialize<'de>>(
|
||||
@@ -508,11 +531,11 @@ impl DaemonClient {
|
||||
path: &str,
|
||||
body: &serde_json::Value,
|
||||
) -> Result<T, DaemonError> {
|
||||
self._request_json("DELETE", path, Some(body))
|
||||
self._request_json("DELETE", path, Some(body), None)
|
||||
}
|
||||
|
||||
fn del<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T, DaemonError> {
|
||||
self._request_json("DELETE", path, None)
|
||||
self._request_json("DELETE", path, None, None)
|
||||
}
|
||||
|
||||
fn _request_json<T: for<'de> Deserialize<'de>>(
|
||||
@@ -520,6 +543,7 @@ impl DaemonClient {
|
||||
method: &str,
|
||||
path: &str,
|
||||
body: Option<&serde_json::Value>,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<T, DaemonError> {
|
||||
let url = format!("{}{}", self.base, path);
|
||||
let req = match method {
|
||||
@@ -535,6 +559,10 @@ impl DaemonClient {
|
||||
)))
|
||||
}
|
||||
};
|
||||
let req = match timeout {
|
||||
Some(t) => req.timeout(t),
|
||||
None => req,
|
||||
};
|
||||
let req = match body {
|
||||
Some(b) => req.send_json(ureq::json!(b.clone())),
|
||||
None => req.call(),
|
||||
|
||||
Reference in New Issue
Block a user