Skip to content

Commit b9d79d5

Browse files
authored
perf: use latest stable Deno for YouTube extraction (#534)
* perf: use latest stable Deno for YouTube extraction * style: apply rustfmt to Deno runtime changes * fix: update tests for Deno runtime migration * style: finish rustfmt cleanup for Deno switch
1 parent 9bcdc97 commit b9d79d5

17 files changed

Lines changed: 569 additions & 635 deletions

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ libadwaita = { version = "0.9", features = ["v1_4", "v1_5", "v1_6", "v1_7", "v1_
1616
glib = "0.22"
1717
gio = "0.22"
1818
url = "2"
19+
# Read the single verified Deno executable from the official release ZIP.
20+
zip = { version = "8.6.0", default-features = false, features = ["deflate", "deflate-flate2-zlib-rs"] }
1921
percent-encoding = "2"
2022
serde = { version = "1", features = ["derive"] }
2123
serde_json = "1"

‎README.md‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,14 @@ Undo with `flatpak override --user --reset io.github.linuxuser67.Grab`.
9393

9494
### Media tool installs
9595

96-
Grab fetches yt-dlp, gallery-dl, and quickjs from their upstream GitHub
97-
releases on first run; Preferences → Check for Updates keeps those tools
98-
current. FFmpeg and ffprobe are resolved from `/app/bin` or the GNOME runtime
99-
on Flatpak, or from system `PATH` on other builds. Grab does not download or
100-
update FFmpeg. Each downloaded tool is verified against the SHA-256 digest
101-
published in the same GitHub release before installation; a missing digest
102-
refuses the install.
96+
Grab fetches yt-dlp and gallery-dl from upstream GitHub releases.
97+
Flatpak builds provision the latest stable Deno release for YouTube extraction;
98+
tarball/dev builds require a supported Deno installation. Preferences → Check
99+
for Updates keeps these tools current. FFmpeg and ffprobe are resolved from
100+
`/app/bin` or the GNOME runtime on Flatpak, or from system `PATH` on other
101+
builds. Grab does not download or update FFmpeg. Each downloaded tool is
102+
verified against the SHA-256 digest published in the same GitHub release
103+
before installation; a missing digest refuses the install.
103104

104105
That digest detects corrupted or truncated downloads, not a compromised
105106
release — it shares the release's trust root. If you need a separate trust

‎docs/installer-trust.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
# Tool installer trust model
22

3-
Grab downloads yt-dlp, gallery-dl, and quickjs from GitHub releases. Those
4-
downloads are SHA-256 verified before installation.
3+
Grab downloads yt-dlp, gallery-dl, and Deno from GitHub releases. Downloads
4+
are SHA-256 verified before installation. Deno is downloaded as the official
5+
Linux ZIP archive; Grab extracts only the expected executable after checking
6+
the archive digest and enforcing compressed and uncompressed size limits.
57

68
Grab does not download or update FFmpeg. Flatpak resolves `ffmpeg` and
79
`ffprobe` from `/app/bin` or the GNOME runtime (`/usr/bin`). Other builds

‎po/grab.pot‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ msgstr ""
390390

391391
#: src/install_help.rs:76
392392
msgid ""
393-
"Install yt-dlp, ffmpeg and quickjs yourself and make sure they are on your "
393+
"Install yt-dlp, ffmpeg, Deno and gallery-dl yourself and make sure they are on your "
394394
"PATH, then press Check Again."
395395
msgstr ""
396396

@@ -422,8 +422,8 @@ msgstr ""
422422
msgid "yt-dlp install progress"
423423
msgstr ""
424424

425-
#: src/install_progress.rs:163
426-
msgid "quickjs install progress"
425+
#: src/install_progress.rs:183
426+
msgid "deno install progress"
427427
msgstr ""
428428

429429
#: src/inline_add.rs:125

‎src/cookies.rs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,13 @@ async fn stage_cookie_file(
212212
page_url: &str,
213213
timeout: Duration,
214214
) -> Option<PathBuf> {
215+
// Every YouTube yt-dlp invocation pins Deno, including this cookie-export
216+
// probe. Prepare it first so a fresh install doesn't silently lose browser
217+
// cookies while the metadata lookup provisions the runtime concurrently.
218+
if let Err(error) = crate::video_tools::ensure_deno(page_url).await {
219+
tracing::debug!(error = %error, "Deno unavailable for YouTube cookie export");
220+
return None;
221+
}
215222
use std::sync::atomic::{AtomicU64, Ordering};
216223
static SEQ: AtomicU64 = AtomicU64::new(0);
217224
let dir = crate::download_net::cookie_staging_dir()?;

‎src/download_net.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,7 @@ pub(crate) fn cookie_staging_dir() -> Option<PathBuf> {
755755
Some(dir)
756756
}
757757

758-
/// Client builder for tool installs (ffmpeg, quickjs, yt-dlp): honors env
758+
/// Client builder for tool installs (ffmpeg, Deno, yt-dlp): honors env
759759
/// proxies via reqwest's default. See `tool_client_builder_proxied` for the
760760
/// variant that also honors Grab's app proxy (Preferences → Network).
761761
pub(crate) fn tool_client_builder() -> reqwest::ClientBuilder {

‎src/inline_gallery.rs‎

Lines changed: 17 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -135,77 +135,35 @@ pub(crate) async fn resolve_gallery_auth(url: &str, browser: &str) -> GalleryAut
135135
/// export step, so it starts immediately; gallery-dl resolves its auth first
136136
/// (`resolve_gallery_auth`).
137137
pub(crate) async fn dual_probe(ctx: DualProbeCtx<'_>) -> DualProbeOutcome {
138-
let started = std::time::Instant::now();
139138
let url = ctx.url.to_string();
140-
let browser = ctx.browser;
141-
let proxy = ctx.proxy;
142-
let tool = ctx.tool;
143-
let libs = ctx.libs;
144-
let newest_first = ctx.newest_first;
145-
146-
// Keep both probes: yt-dlp resolves video and playlist metadata while
147-
// gallery-dl determines whether the URL also exposes a selectable gallery.
148-
let ytdlp_browser = browser.clone();
149-
let ytdlp_proxy = proxy.clone();
150-
let ytdlp_fut = async {
151-
let phase_started = std::time::Instant::now();
152-
let result = crate::video::fetch_video_infos(
153-
libs,
154-
url.clone(),
155-
ytdlp_browser,
156-
newest_first,
157-
ytdlp_proxy,
158-
None,
159-
)
160-
.await;
161-
tracing::info!(
162-
elapsed_ms = phase_started.elapsed().as_millis() as u64,
163-
success = result.is_ok(),
164-
"yt-dlp URL lookup completed"
165-
);
166-
result
167-
};
168-
169-
// Auth preparation is timed separately because exporting browser cookies
170-
// may be a substantial fraction of lookup latency on some configurations.
139+
// yt-dlp starts immediately: it uses the handoff jar or
140+
// --cookies-from-browser, not the exported file below.
141+
let ytdlp_fut = crate::video::fetch_video_infos(
142+
ctx.libs,
143+
url.clone(),
144+
ctx.browser.clone(),
145+
ctx.newest_first,
146+
ctx.proxy.clone(),
147+
None,
148+
);
149+
// gallery-dl needs its auth first (handoff jar preferred); the export
150+
// runs while yt-dlp is already probing.
171151
let gallery_fut = async {
172-
let auth_started = std::time::Instant::now();
173-
let auth = resolve_gallery_auth(&url, &browser).await;
174-
tracing::info!(
175-
elapsed_ms = auth_started.elapsed().as_millis() as u64,
176-
has_cookies = auth.cookies_path.is_some(),
177-
has_user_agent = auth.user_agent.is_some(),
178-
"gallery-dl authentication preparation completed"
179-
);
180-
181-
let probe_started = std::time::Instant::now();
182-
let result = crate::gallery_probe::probe_gallery_with_proxy(
183-
&tool,
152+
let auth = resolve_gallery_auth(&url, &ctx.browser).await;
153+
let r = crate::gallery_probe::probe_gallery_with_proxy(
154+
&ctx.tool,
184155
&url,
185156
auth.cookies_path.as_deref(),
186157
auth.user_agent.as_deref(),
187-
proxy.as_ref(),
158+
ctx.proxy.as_ref(),
188159
)
189160
.await;
190161
// The auth (and its staged jar) must outlive the probe.
191162
drop(auth);
192-
tracing::info!(
193-
elapsed_ms = probe_started.elapsed().as_millis() as u64,
194-
success = result.is_ok(),
195-
has_items = matches!(&result, Ok(Some(items)) if !items.is_empty()),
196-
"gallery-dl URL lookup completed"
197-
);
198-
result
163+
r
199164
};
200165

201166
let (ytdlp, gallery) = tokio::join!(ytdlp_fut, gallery_fut);
202-
tracing::info!(
203-
host = %crate::video_probe::page_host(&url),
204-
elapsed_ms = started.elapsed().as_millis() as u64,
205-
ytdlp_success = ytdlp.is_ok(),
206-
gallery_success = gallery.is_ok(),
207-
"dual-backend URL lookup completed"
208-
);
209167
DualProbeOutcome::Complete { gallery, ytdlp }
210168
}
211169

‎src/install_help.rs‎

Lines changed: 11 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Guided yt-dlp/ffmpeg/quickjs install for tarball/dev builds (Flatpak Install is the only sandbox path).
1+
//! Guided yt-dlp/ffmpeg/Deno install for tarball/dev builds (Flatpak Install is the only sandbox path).
22
33
use adw::prelude::*;
44
use gettextrs::gettext;
@@ -7,6 +7,8 @@ use gtk4::glib;
77
use gtk4::prelude::*;
88
use libadwaita as adw;
99

10+
const DENO_RELEASES_URL: &str = "https://github.com/denoland/deno/releases";
11+
1012
/// Show the install-help dialog; `on_check` re-resolves tools and refreshes the caller.
1113
pub fn show(parent: &impl glib::object::IsA<gtk4::Widget>, on_check: impl Fn() + 'static) {
1214
let dialog = adw::AlertDialog::new(
@@ -26,19 +28,16 @@ pub fn show(parent: &impl glib::object::IsA<gtk4::Widget>, on_check: impl Fn() +
2628
.title(pkgs.distro.clone())
2729
.build();
2830
command_row(&group, &gettext("Install tools"), &pkgs.install_all);
29-
// No distro quickjs package: the command above installs only
30-
// yt-dlp and ffmpeg, so link the upstream quickjs releases
31-
// instead of dead-ending.
32-
for (tool, url) in extra_link_rows(&pkgs) {
33-
link_row(&group, tool, url);
34-
}
31+
// Deno is installed from the verified upstream release, not a
32+
// distro package; always show its official release page.
33+
link_row(&group, "Deno", DENO_RELEASES_URL);
3534
content.append(&group);
3635
}
3736
None => {
3837
let group = adw::PreferencesGroup::builder()
3938
.title(gettext("Install the tools manually"))
4039
.description(gettext(
41-
"Install yt-dlp, ffmpeg, quickjs and gallery-dl yourself and make sure they are on your PATH.",
40+
"Install yt-dlp, ffmpeg, Deno and gallery-dl yourself and make sure they are on your PATH.",
4241
))
4342
.build();
4443
link_row(
@@ -47,11 +46,7 @@ pub fn show(parent: &impl glib::object::IsA<gtk4::Widget>, on_check: impl Fn() +
4746
"https://github.com/yt-dlp/yt-dlp#installation",
4847
);
4948
link_row(&group, "ffmpeg", "https://ffmpeg.org/download.html");
50-
link_row(
51-
&group,
52-
"quickjs",
53-
"https://github.com/quickjs-ng/quickjs/releases",
54-
);
49+
link_row(&group, "Deno", DENO_RELEASES_URL);
5550
link_row(
5651
&group,
5752
"gallery-dl",
@@ -117,18 +112,6 @@ pub(crate) fn command_row(
117112
row
118113
}
119114

120-
/// Link rows the distro group needs beyond the install command: the upstream
121-
/// quickjs releases when the distro has no quickjs package (the command then
122-
/// covers only yt-dlp and ffmpeg). Pure so tests pin the dialog's rows
123-
/// without needing a display.
124-
fn extra_link_rows(pkgs: &crate::video_tools::DistroPackages) -> Vec<(&'static str, &'static str)> {
125-
if pkgs.has_quickjs_package {
126-
Vec::new()
127-
} else {
128-
vec![("quickjs", "https://github.com/quickjs-ng/quickjs/releases")]
129-
}
130-
}
131-
132115
/// One outbound-link row for the manual fallback.
133116
fn link_row(group: &adw::PreferencesGroup, tool: &str, url: &'static str) {
134117
let row = adw::ActionRow::builder().title(tool).build();
@@ -147,29 +130,12 @@ fn link_row(group: &adw::PreferencesGroup, tool: &str, url: &'static str) {
147130
#[cfg(test)]
148131
mod tests {
149132
use super::*;
150-
use crate::video_tools::DistroPackages;
151-
152-
fn pkgs(has_quickjs_package: bool) -> DistroPackages {
153-
DistroPackages {
154-
distro: "Void".to_string(),
155-
install_all: "sudo xbps-install -S yt-dlp ffmpeg".to_string(),
156-
has_quickjs_package,
157-
}
158-
}
159133

160134
#[test]
161-
fn quickjs_link_row_appears_without_distro_package() {
162-
// The dialog must not dead-end on distros without a quickjs package:
163-
// the upstream releases link has to be among the group's rows.
135+
fn official_deno_release_link_is_stable_upstream() {
164136
assert_eq!(
165-
extra_link_rows(&pkgs(false)),
166-
vec![("quickjs", "https://github.com/quickjs-ng/quickjs/releases")]
137+
DENO_RELEASES_URL,
138+
"https://github.com/denoland/deno/releases"
167139
);
168140
}
169-
170-
#[test]
171-
fn no_quickjs_link_row_with_distro_package() {
172-
// The install command already covers quickjs here; no extra row.
173-
assert!(extra_link_rows(&pkgs(true)).is_empty());
174-
}
175141
}

‎src/install_progress.rs‎

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -171,16 +171,16 @@ pub fn run(
171171
};
172172
let do_yt = include("yt-dlp");
173173
let do_gdl = include("gallery-dl");
174-
let do_js = include("quickjs");
174+
let do_deno = include("deno");
175175

176-
// Order: yt-dlp, gallery-dl (under yt-dlp), then quickjs.
176+
// Order: yt-dlp, gallery-dl (under yt-dlp), then Deno.
177177
let yt = ToolRow::new("yt-dlp", &gettext("yt-dlp install progress"), &active);
178178
let gdl = ToolRow::new(
179179
"gallery-dl",
180180
&gettext("gallery-dl install progress"),
181181
&active,
182182
);
183-
let js = ToolRow::new("quickjs", &gettext("quickjs install progress"), &active);
183+
let deno = ToolRow::new("Deno", &gettext("deno install progress"), &active);
184184

185185
let rows = gtk4::Box::new(gtk4::Orientation::Vertical, 12);
186186
// HIG card padding: 12, matching the rest of the app (not 18).
@@ -194,8 +194,8 @@ pub fn run(
194194
if do_gdl {
195195
rows.append(&gdl.widget());
196196
}
197-
if do_js {
198-
rows.append(&js.widget());
197+
if do_deno {
198+
rows.append(&deno.widget());
199199
}
200200
let pop = gtk4::Popover::new();
201201
pop.set_child(Some(&rows));
@@ -264,22 +264,22 @@ pub fn run(
264264
.flatten();
265265
gdl.set_installed(version);
266266
}
267-
if do_js {
268-
// quickjs-ng is the JS runtime Grab pins for YouTube; installed up
267+
if do_deno {
268+
// Deno is Grab's JavaScript runtime for YouTube; installed up
269269
// front so YouTube spawns never wait on a lazy install.
270-
js.set_downloading();
271-
let js_path = match crate::video::install_quickjs().await {
270+
deno.set_downloading();
271+
let deno_path = match crate::video::install_deno().await {
272272
Ok(path) => path,
273273
Err(e) => {
274274
let message = e.to_string();
275-
js.set_failed(&message);
275+
deno.set_failed(&message);
276276
RUNNING.with(|r| r.set(false));
277277
on_error(message);
278278
return;
279279
}
280280
};
281-
let version = crate::video_tools::tool_display_version(js_path, "--version").await;
282-
js.set_installed(version);
281+
let version = crate::video_tools::tool_display_version(deno_path, "--version").await;
282+
deno.set_installed(version);
283283
}
284284

285285
// Linger on checkmarks so completion registers, then close.

0 commit comments

Comments
 (0)