-
Notifications
You must be signed in to change notification settings - Fork 7
Create permanent BIOS DAT download route #196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ use crate::AppState; | |
| pub fn routes() -> Router<AppState> { | ||
| Router::new() | ||
| .route("/downloads", get(downloads_page)) | ||
| .route("/bios/{system}", get(download_bios_dat)) | ||
| .route("/datfile/{system}", get(download_dat)) | ||
| .route("/datfile/{system}/{features}", get(download_dat_variant)) | ||
| .route("/cues/{system}", get(download_cue)) | ||
|
|
@@ -50,7 +51,7 @@ struct SystemDownload { | |
|
|
||
| struct BiosDownload { | ||
| name: String, | ||
| href: &'static str, | ||
| href: String, | ||
| } | ||
|
|
||
| struct DatabaseDownload { | ||
|
|
@@ -61,29 +62,29 @@ struct DatabaseDownload { | |
| struct BiosDownloadSpec { | ||
| code: &'static str, | ||
| fallback_name: &'static str, | ||
| href: &'static str, | ||
| path: &'static str, | ||
| } | ||
|
|
||
| const BIOS_DOWNLOADS: &[BiosDownloadSpec] = &[ | ||
| BiosDownloadSpec { | ||
| code: "XBOX", | ||
| fallback_name: "Microsoft Xbox", | ||
| href: "/static/bios/Microsoft%20-%20Xbox%20-%20BIOS%20Images%20%289%29%20%282026-06-16%29.dat", | ||
| path: "static/bios/Microsoft - Xbox - BIOS Images (9) (2026-06-16).dat", | ||
| }, | ||
| BiosDownloadSpec { | ||
| code: "GC", | ||
| fallback_name: "Nintendo GameCube", | ||
| href: "/static/bios/Nintendo%20-%20GameCube%20-%20BIOS%20Images%20%2817%29%20%282026-06-16%29.dat", | ||
| path: "static/bios/Nintendo - GameCube - BIOS Images (17) (2026-06-16).dat", | ||
| }, | ||
| BiosDownloadSpec { | ||
| code: "PSX", | ||
| fallback_name: "Sony PlayStation", | ||
| href: "/static/bios/Sony%20-%20PlayStation%20-%20BIOS%20Images%20%2824%29%20%282026-06-16%29.dat", | ||
| path: "static/bios/Sony - PlayStation - BIOS Images (24) (2026-06-16).dat", | ||
| }, | ||
| BiosDownloadSpec { | ||
| code: "PS2", | ||
| fallback_name: "Sony PlayStation 2", | ||
| href: "/static/bios/Sony%20-%20PlayStation%202%20-%20BIOS%20Datfile%20%28140%29%20%282026-06-16%29.dat", | ||
| path: "static/bios/Sony - PlayStation 2 - BIOS Datfile (140) (2026-06-16).dat", | ||
| }, | ||
| ]; | ||
|
|
||
|
|
@@ -95,11 +96,37 @@ fn bios_downloads(system_names: &HashMap<String, String>) -> Vec<BiosDownload> { | |
| .get(spec.code) | ||
| .cloned() | ||
| .unwrap_or_else(|| spec.fallback_name.to_string()), | ||
| href: spec.href, | ||
| href: format!("/bios/{}", spec.code), | ||
| }) | ||
| .collect() | ||
| } | ||
|
|
||
| async fn download_bios_dat(Path(system): Path<String>, request: Request) -> Response { | ||
| let system = normalize_archive_system_code(&system); | ||
| let Some(spec) = BIOS_DOWNLOADS.iter().find(|s| s.code == system) else { | ||
| return AppError::NotFound.into_response(); | ||
| }; | ||
| let filename = std::path::Path::new(spec.path) | ||
| .file_name() | ||
| .map(|n| n.to_string_lossy().into_owned()) | ||
| .unwrap_or_default(); | ||
| let mut response = ServeFile::new(spec.path) | ||
| .oneshot(request) | ||
| .await | ||
| .expect("ServeFile is infallible") | ||
| .into_response(); | ||
| response.headers_mut().insert( | ||
| header::CONTENT_TYPE, | ||
| HeaderValue::from_static("application/octet-stream"), | ||
| ); | ||
| response.headers_mut().insert( | ||
| header::CONTENT_DISPOSITION, | ||
| HeaderValue::from_str(&format!("attachment; filename=\"{}\"", filename)) | ||
| .expect("validated bios filename is a valid header value"), | ||
| ); | ||
| response | ||
| } | ||
|
|
||
|
Comment on lines
96
to
+129
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '1,145p' src/routes/downloads.rs
sed -n '480,535p' src/routes/downloads.rs
rg -n 'download_bios_dat|/bios/|bios.*dat|BIOS_DOWNLOADS' src testsRepository: superg/vgindex Length of output: 8058 🏁 Script executed: git ls-files | sed -n '1,240p'
printf '\n--- all relevant references ---\n'
rg -n 'download_bios_dat|/bios/|downloads_page_shows_bios_dat_links|BIOS_DOWNLOADS|routes\(\)' --glob '!target/**' --glob '!node_modules/**' .Repository: superg/vgindex Length of output: 10061 Test 🤖 Prompt for AI Agents |
||
| async fn downloads_page(State(state): State<AppState>, user: CurrentUser) -> Html<String> { | ||
| let authenticated = user.is_logged_in(); | ||
| let media_types: Vec<MediaTypeCdRow> = | ||
|
|
@@ -478,21 +505,13 @@ mod tests { | |
|
|
||
| assert!(html.contains(">BIOS<")); | ||
| assert!(html.contains("Microsoft Xbox")); | ||
| assert!(html.contains( | ||
| r#"/static/bios/Microsoft%20-%20Xbox%20-%20BIOS%20Images%20%289%29%20%282026-06-16%29.dat"# | ||
| )); | ||
| assert!(html.contains(r#"/bios/XBOX"#)); | ||
| assert!(html.contains("Nintendo GameCube")); | ||
| assert!(html.contains( | ||
| r#"/static/bios/Nintendo%20-%20GameCube%20-%20BIOS%20Images%20%2817%29%20%282026-06-16%29.dat"# | ||
| )); | ||
| assert!(html.contains(r#"/bios/GC"#)); | ||
| assert!(html.contains("Sony PlayStation")); | ||
| assert!(html.contains( | ||
| r#"/static/bios/Sony%20-%20PlayStation%20-%20BIOS%20Images%20%2824%29%20%282026-06-16%29.dat"# | ||
| )); | ||
| assert!(html.contains(r#"/bios/PSX"#)); | ||
| assert!(html.contains("Sony PlayStation 2")); | ||
| assert!(html.contains( | ||
| r#"/static/bios/Sony%20-%20PlayStation%202%20-%20BIOS%20Datfile%20%28140%29%20%282026-06-16%29.dat"# | ||
| )); | ||
| assert!(html.contains(r#"/bios/PS2"#)); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: superg/vgindex
Length of output: 8274
🤖 get_repo_knowledge executed:
get_repo_knowledge superg/vgindex /tmp/coderabbit-repo-knowledge/superg-vgindex-363d8378/architectureLength of output: 22057
🏁 Script executed:
Repository: superg/vgindex
Length of output: 50371
🏁 Script executed:
Repository: superg/vgindex
Length of output: 19942
🌐 Web query:
tower-http 0.6.8 ServeFile Last-Modified Cache-Control conditional requests documentation source💡 Result:
<search_synthesis>
In tower-http 0.6.8, the ServeFile service handles conditional requests automatically based on file modification times [1][2]. 1. Last-Modified and Conditional Requests: ServeFile (which is implemented as a thin wrapper around ServeDir) automatically includes the Last-Modified header in its responses [1][3]. It evaluates conditional request headers, including If-Modified-Since and If-Unmodified-Since [1][4]. If the conditions are met (or not met), it appropriately responds with 304 Not Modified or 412 Precondition Failed, respectively [1]. 2. Cache-Control: ServeFile does not automatically set a Cache-Control header [2]. This is a deliberate policy decision by the library, as caching strategies often depend on the specific deployment context (e.g., whether files are versioned) [2]. To set a Cache-Control header, you should use the SetResponseHeaderLayer from the tower-http set-header feature to apply the desired policy to your routes [2]. 3. Implementation Details: ServeFile delegates its request handling to ServeDir [3][5]. In version 0.6.8, the library primarily relies on time-based validation for conditional requests [1][6]. Note that while newer versions (such as those incorporating recent updates) may include ETag support to improve accuracy over If-Modified-Since, 0.6.8 is primarily characterized by its Last-Modified and time-based precondition logic [6].
</search_synthesis>
<source_evidence>
Citations:
Require revalidation for the permanent BIOS URL.
download_bios_datserves the configuredBIOS_DOWNLOADSpath throughtower_http::services::ServeFile.ServeFileemitsLast-Modifiedbut noCache-Control. Because/bios/{system}remains stable while the configured file can change, a cache may heuristically reuse an older response without revalidation. A user can then download an outdated DAT file. SetCache-Control: no-cache.Proposed fix
response.headers_mut().insert( header::CONTENT_DISPOSITION, HeaderValue::from_str(&format!("attachment; filename=\"{}\"", filename)) .expect("validated bios filename is a valid header value"), ); + response.headers_mut().insert( + header::CACHE_CONTROL, + HeaderValue::from_static("no-cache"), + );🤖 Prompt for AI Agents