From fa3d81d2ced749bfd4ebd754063edd530468f637 Mon Sep 17 00:00:00 2001 From: BoySanic Date: Thu, 2 Apr 2026 16:23:47 -0700 Subject: [PATCH 01/12] Refactor individual urls into a file table --- api/src/get.rs | 11 +++++-- cli/src/args.rs | 10 ++----- cli/src/client.rs | 35 +++++++++-------------- common/src/records/file.rs | 16 +++++++++++ common/src/records/mod.rs | 2 ++ common/src/records/platform.rs | 4 +-- common/src/records/project_version.rs | 4 +-- server/migrations/20250426220809_init.sql | 11 +++++-- server/src/main.rs | 6 ++-- server/src/util/select.rs | 26 +++++++++++++++-- 10 files changed, 84 insertions(+), 41 deletions(-) create mode 100644 common/src/records/file.rs diff --git a/api/src/get.rs b/api/src/get.rs index be0e29c..599999b 100644 --- a/api/src/get.rs +++ b/api/src/get.rs @@ -1,6 +1,7 @@ use clusterizer_common::records::{ - Assignment, AssignmentFilter, Platform, PlatformFilter, Project, ProjectFilter, ProjectVersion, - ProjectVersionFilter, Result, ResultFilter, Task, TaskFilter, User, UserFilter, + Assignment, AssignmentFilter, File, FileFilter, Platform, PlatformFilter, Project, + ProjectFilter, ProjectVersion, ProjectVersionFilter, Result, ResultFilter, Task, TaskFilter, + User, UserFilter, }; pub trait Get { @@ -33,6 +34,12 @@ impl Get for ProjectVersion { const PATH: &str = "project_versions"; } +impl Get for File { + type Filter = FileFilter; + + const PATH: &str = "files"; +} + impl Get for Task { type Filter = TaskFilter; diff --git a/cli/src/args.rs b/cli/src/args.rs index a23614b..5526e4e 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -1,8 +1,8 @@ use std::{num::NonZero, path::PathBuf, thread}; use clap::{ - Args, Parser, Subcommand, builder::{OsStr, Resettable}, + Args, Parser, Subcommand, }; #[derive(Debug, Parser)] @@ -43,12 +43,8 @@ pub struct RunArgs { } impl RunArgs { - pub fn project_versions_dir(&self) -> PathBuf { - self.cache_dir.join("project_versions") - } - - pub fn platform_testers_dir(&self) -> PathBuf { - self.cache_dir.join("platform_testers") + pub fn binaries_dir(&self) -> PathBuf { + self.cache_dir.join("bin") } } diff --git a/cli/src/client.rs b/cli/src/client.rs index e4d7d9a..8c71dd8 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -150,17 +150,14 @@ impl ClusterizerClient { project_version, .. } in &tasks { - let project_version_dir = self + let file = self.client.get_one(project_version.file_id).await?; + + let binary_dir = self .args - .project_versions_dir() + .binaries_dir() .join(project_version.id.to_string()); - download_archive( - &project_version.archive_url, - &project_version_dir, - &self.args.cache_dir, - ) - .await?; + download_archive(&file.url, &binary_dir, &self.args.cache_dir).await?; } Ok(Return::FetchTasks(tasks)) @@ -179,14 +176,14 @@ impl ClusterizerClient { info!("Task id: {}, stdin: {}", task.id, task.stdin); info!("Project id: {}, name: {}", project.id, project.name); debug!( - "Project version id: {}, archive url: {}", - project_version.id, project_version.archive_url + "Project version id: {}, file id: {}", + project_version.id, project_version.file_id ); debug!("Slot dir: {}", slot_dir.path().display()); let project_version_dir = self .args - .project_versions_dir() + .binaries_dir() .join(project_version.id.to_string()); let program = project_version_dir @@ -235,8 +232,7 @@ impl ClusterizerClient { } pub async fn run(client: ApiClient, args: RunArgs) -> ClientResult<()> { - fs::create_dir_all(args.project_versions_dir())?; - fs::create_dir_all(args.platform_testers_dir())?; + fs::create_dir_all(args.binaries_dir())?; let mut platform_ids = Vec::new(); let mut platform_names = Vec::new(); @@ -245,19 +241,16 @@ pub async fn run(client: ApiClient, args: RunArgs) -> ClientResult<()> { .get_all::(&PlatformFilter::default()) .await? { + let file = client.get_one(platform.file_id).await?; + debug!( "Platform id: {}, tester archive url: {}", - platform.id, platform.tester_archive_url + platform.id, file.url ); - let platform_tester_dir = args.platform_testers_dir().join(platform.id.to_string()); + let platform_tester_dir = args.binaries_dir().join(file.id.to_string()); - download_archive( - &platform.tester_archive_url, - &platform_tester_dir, - &args.cache_dir, - ) - .await?; + download_archive(&file.url, &platform_tester_dir, &args.cache_dir).await?; let slot_dir = tempfile::tempdir()?; diff --git a/common/src/records/file.rs b/common/src/records/file.rs new file mode 100644 index 0000000..1efe33f --- /dev/null +++ b/common/src/records/file.rs @@ -0,0 +1,16 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::Id; + +#[derive(Clone, Hash, Debug, Serialize, Deserialize)] +pub struct File { + pub id: Id, + pub created_at: DateTime, + pub url: String, + pub hash: Vec, +} + +#[non_exhaustive] +#[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] +pub struct FileFilter {} diff --git a/common/src/records/mod.rs b/common/src/records/mod.rs index c04af48..d16c5f3 100644 --- a/common/src/records/mod.rs +++ b/common/src/records/mod.rs @@ -1,4 +1,5 @@ pub mod assignment; +pub mod file; pub mod platform; pub mod project; pub mod project_version; @@ -7,6 +8,7 @@ pub mod task; pub mod user; pub use assignment::{Assignment, AssignmentFilter}; +pub use file::{File, FileFilter}; pub use platform::{Platform, PlatformFilter}; pub use project::{Project, ProjectFilter}; pub use project_version::{ProjectVersion, ProjectVersionFilter}; diff --git a/common/src/records/platform.rs b/common/src/records/platform.rs index c1ac995..be52af5 100644 --- a/common/src/records/platform.rs +++ b/common/src/records/platform.rs @@ -1,14 +1,14 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::types::Id; +use crate::{records::File, types::Id}; #[derive(Clone, Hash, Debug, Serialize, Deserialize)] pub struct Platform { pub id: Id, pub created_at: DateTime, pub name: String, - pub tester_archive_url: String, + pub file_id: Id, } #[non_exhaustive] diff --git a/common/src/records/project_version.rs b/common/src/records/project_version.rs index 54f509c..bf18659 100644 --- a/common/src/records/project_version.rs +++ b/common/src/records/project_version.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::types::Id; -use super::{Platform, Project}; +use super::{File, Platform, Project}; #[derive(Clone, Hash, Debug, Serialize, Deserialize)] pub struct ProjectVersion { @@ -12,7 +12,7 @@ pub struct ProjectVersion { pub disabled_at: Option>, pub project_id: Id, pub platform_id: Id, - pub archive_url: String, + pub file_id: Id, } #[non_exhaustive] diff --git a/server/migrations/20250426220809_init.sql b/server/migrations/20250426220809_init.sql index 5a24c59..98b7fc5 100644 --- a/server/migrations/20250426220809_init.sql +++ b/server/migrations/20250426220809_init.sql @@ -16,11 +16,18 @@ CREATE TABLE projects ( name text NOT NULL ); +CREATE TABLE files ( + id int8 GENERATED ALWAYS AS IDENTITY NOT NULL PRIMARY KEY, + created_at timestamptz NOT NULL DEFAULT now(), + url text NOT NULL, + hash bytea NOT NULL +); + CREATE TABLE platforms ( id int8 GENERATED ALWAYS AS IDENTITY NOT NULL PRIMARY KEY, created_at timestamptz NOT NULL DEFAULT now(), name text NOT NULL, - tester_archive_url text NOT NULL + file_id int8 NOT NULL REFERENCES files(id) ); CREATE TABLE project_versions ( @@ -29,7 +36,7 @@ CREATE TABLE project_versions ( disabled_at timestamptz, project_id int8 NOT NULL REFERENCES projects(id) ON DELETE CASCADE ON UPDATE CASCADE, platform_id int8 NOT NULL REFERENCES platforms(id) ON DELETE RESTRICT ON UPDATE RESTRICT, - archive_url text NOT NULL + file_id int8 NOT NULL REFERENCES files(id) ); CREATE TABLE tasks ( diff --git a/server/src/main.rs b/server/src/main.rs index 1aa9526..7055a41 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,11 +7,11 @@ mod util; use std::time::Duration; use axum::{ - Router, routing::{get, post}, + Router, }; use clusterizer_common::records::{ - Assignment, Platform, Project, ProjectVersion, Result, Task, User, + Assignment, File, Platform, Project, ProjectVersion, Result, Task, User, }; use routes::{get_all, get_one}; @@ -41,6 +41,8 @@ async fn main() { async fn serve_task(state: AppState, address: String) { let app = Router::new() + .route("/files", get(get_all::)) + .route("/files/{id}", get(get_one::)) .route("/users", get(get_all::)) .route("/users/{id}", get(get_one::)) .route("/projects", get(get_all::)) diff --git a/server/src/util/select.rs b/server/src/util/select.rs index 7bb2ef9..6550a89 100644 --- a/server/src/util/select.rs +++ b/server/src/util/select.rs @@ -1,8 +1,8 @@ use clusterizer_common::{ records::{ - Assignment, AssignmentFilter, Platform, PlatformFilter, Project, ProjectFilter, - ProjectVersion, ProjectVersionFilter, Result, ResultFilter, Task, TaskFilter, User, - UserFilter, + Assignment, AssignmentFilter, File, FileFilter, Platform, PlatformFilter, Project, + ProjectFilter, ProjectVersion, ProjectVersionFilter, Result, ResultFilter, Task, + TaskFilter, User, UserFilter, }, types::Id, }; @@ -111,6 +111,26 @@ impl Select for ProjectVersion { } } +impl Select for File { + type Filter = FileFilter; + + fn select_all(_: &Self::Filter) -> Map { + sqlx::query_as_unchecked!( + Self, + r#" + SELECT + * + FROM + files + "#, + ) + } + + fn select_one(id: Id) -> Map { + sqlx::query_as_unchecked!(Self, "SELECT * FROM files WHERE id = $1", id) + } +} + impl Select for Task { type Filter = TaskFilter; From 83010c9c94959523259854b1277d8ff448b1a95e Mon Sep 17 00:00:00 2001 From: BoySanic Date: Thu, 2 Apr 2026 16:28:05 -0700 Subject: [PATCH 02/12] sqlx prepare --- ...edb9ea3cd06c2c1189c29703b35f4894d7521.json | 4 +- ...f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json | 4 +- ...59f4e0bb2d2046793df4c2317778f1784bf92.json | 38 ++++++++++++++++++ ...f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json | 40 +++++++++++++++++++ ...c4842facf875aa48e0b63003bd4f8e043597b.json | 4 +- ...a21c3c1d24643ce92e2fc64021566146f6eff.json | 4 +- 6 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 server/.sqlx/query-a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92.json create mode 100644 server/.sqlx/query-d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json diff --git a/server/.sqlx/query-110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521.json b/server/.sqlx/query-110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521.json index 16e3dd1..5f055f5 100644 --- a/server/.sqlx/query-110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521.json +++ b/server/.sqlx/query-110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521.json @@ -20,8 +20,8 @@ }, { "ordinal": 3, - "name": "tester_archive_url", - "type_info": "Text" + "name": "file_id", + "type_info": "Int8" } ], "parameters": { diff --git a/server/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json b/server/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json index da9a4b6..1b32efe 100644 --- a/server/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json +++ b/server/.sqlx/query-60d06b4ddeb3f621f6da75d1ad4f37cb37aa552d2cf4e9f3ec8365afd2555e2a.json @@ -30,8 +30,8 @@ }, { "ordinal": 5, - "name": "archive_url", - "type_info": "Text" + "name": "file_id", + "type_info": "Int8" } ], "parameters": { diff --git a/server/.sqlx/query-a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92.json b/server/.sqlx/query-a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92.json new file mode 100644 index 0000000..4d6dbfb --- /dev/null +++ b/server/.sqlx/query-a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92.json @@ -0,0 +1,38 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n *\n FROM\n files\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "url", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "hash", + "type_info": "Bytea" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "a1f58978cf86180b0a48bef158759f4e0bb2d2046793df4c2317778f1784bf92" +} diff --git a/server/.sqlx/query-d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json b/server/.sqlx/query-d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json new file mode 100644 index 0000000..5c85d36 --- /dev/null +++ b/server/.sqlx/query-d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT * FROM files WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "url", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "hash", + "type_info": "Bytea" + } + ], + "parameters": { + "Left": [ + "Int8" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "d169c558df910c04f98627f46d1f0ee4fbbab4c51667ccbcee9cf966ba8db9b1" +} diff --git a/server/.sqlx/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json b/server/.sqlx/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json index 5bd55d1..f788679 100644 --- a/server/.sqlx/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json +++ b/server/.sqlx/query-d4273a3142cd2ace08d03b22dddc4842facf875aa48e0b63003bd4f8e043597b.json @@ -20,8 +20,8 @@ }, { "ordinal": 3, - "name": "tester_archive_url", - "type_info": "Text" + "name": "file_id", + "type_info": "Int8" } ], "parameters": { diff --git a/server/.sqlx/query-f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff.json b/server/.sqlx/query-f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff.json index dd04b0a..09fc36b 100644 --- a/server/.sqlx/query-f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff.json +++ b/server/.sqlx/query-f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff.json @@ -30,8 +30,8 @@ }, { "ordinal": 5, - "name": "archive_url", - "type_info": "Text" + "name": "file_id", + "type_info": "Int8" } ], "parameters": { From 715bf8b68a859a456fbf0aaf7b1bba82f2e1075c Mon Sep 17 00:00:00 2001 From: BoySanic Date: Thu, 2 Apr 2026 16:40:50 -0700 Subject: [PATCH 03/12] Format --- cli/src/args.rs | 2 +- server/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/args.rs b/cli/src/args.rs index 5526e4e..f883a3b 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -1,8 +1,8 @@ use std::{num::NonZero, path::PathBuf, thread}; use clap::{ - builder::{OsStr, Resettable}, Args, Parser, Subcommand, + builder::{OsStr, Resettable}, }; #[derive(Debug, Parser)] diff --git a/server/src/main.rs b/server/src/main.rs index 7055a41..fd285bc 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,8 +7,8 @@ mod util; use std::time::Duration; use axum::{ - routing::{get, post}, Router, + routing::{get, post}, }; use clusterizer_common::records::{ Assignment, File, Platform, Project, ProjectVersion, Result, Task, User, From f21b10a82bf18b839e5aaf66de61793392eeeb0a Mon Sep 17 00:00:00 2001 From: BoySanic Date: Fri, 3 Apr 2026 09:01:51 -0700 Subject: [PATCH 04/12] Hash folders --- cli/src/client.rs | 14 +++++--------- common/src/records/file.rs | 12 ++++++++++++ server/migrations/20250426220809_init.sql | 4 ++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/cli/src/client.rs b/cli/src/client.rs index 8c71dd8..226f685 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -152,10 +152,7 @@ impl ClusterizerClient { { let file = self.client.get_one(project_version.file_id).await?; - let binary_dir = self - .args - .binaries_dir() - .join(project_version.id.to_string()); + let binary_dir = self.args.binaries_dir().join(format!("{:x}", file)); download_archive(&file.url, &binary_dir, &self.args.cache_dir).await?; } @@ -181,10 +178,9 @@ impl ClusterizerClient { ); debug!("Slot dir: {}", slot_dir.path().display()); - let project_version_dir = self - .args - .binaries_dir() - .join(project_version.id.to_string()); + let file = self.client.get_one(project_version.file_id).await?; + + let project_version_dir = self.args.binaries_dir().join(format!("{:x}", file)); let program = project_version_dir .join(format!("main{}", env::consts::EXE_SUFFIX)) @@ -248,7 +244,7 @@ pub async fn run(client: ApiClient, args: RunArgs) -> ClientResult<()> { platform.id, file.url ); - let platform_tester_dir = args.binaries_dir().join(file.id.to_string()); + let platform_tester_dir = args.binaries_dir().join(format!("{:x}", file)); download_archive(&file.url, &platform_tester_dir, &args.cache_dir).await?; diff --git a/common/src/records/file.rs b/common/src/records/file.rs index 1efe33f..b02f8a7 100644 --- a/common/src/records/file.rs +++ b/common/src/records/file.rs @@ -1,5 +1,6 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +use std::fmt; use crate::types::Id; @@ -14,3 +15,14 @@ pub struct File { #[non_exhaustive] #[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] pub struct FileFilter {} + +impl fmt::LowerHex for File { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val: Vec = self.hash.clone(); + let mut str_builder: String = String::from(""); + for elem in val { + str_builder.push_str(&format!("{:x}", elem)); + } + write!(f, "{}", str_builder) + } +} diff --git a/server/migrations/20250426220809_init.sql b/server/migrations/20250426220809_init.sql index 98b7fc5..4fac810 100644 --- a/server/migrations/20250426220809_init.sql +++ b/server/migrations/20250426220809_init.sql @@ -27,7 +27,7 @@ CREATE TABLE platforms ( id int8 GENERATED ALWAYS AS IDENTITY NOT NULL PRIMARY KEY, created_at timestamptz NOT NULL DEFAULT now(), name text NOT NULL, - file_id int8 NOT NULL REFERENCES files(id) + file_id int8 NOT NULL REFERENCES files(id) ON DELETE RESTRICT ON UPDATE RESTRICT ); CREATE TABLE project_versions ( @@ -36,7 +36,7 @@ CREATE TABLE project_versions ( disabled_at timestamptz, project_id int8 NOT NULL REFERENCES projects(id) ON DELETE CASCADE ON UPDATE CASCADE, platform_id int8 NOT NULL REFERENCES platforms(id) ON DELETE RESTRICT ON UPDATE RESTRICT, - file_id int8 NOT NULL REFERENCES files(id) + file_id int8 NOT NULL REFERENCES files(id) ON DELETE RESTRICT ON UPDATE RESTRICT ); CREATE TABLE tasks ( From fc8c6de3f33766874fe53ed3e75c7fe70b151e5e Mon Sep 17 00:00:00 2001 From: BoySanic Date: Fri, 3 Apr 2026 13:58:23 -0700 Subject: [PATCH 05/12] Cleanup fmt implementation --- common/src/records/file.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/common/src/records/file.rs b/common/src/records/file.rs index b02f8a7..15b8736 100644 --- a/common/src/records/file.rs +++ b/common/src/records/file.rs @@ -18,11 +18,9 @@ pub struct FileFilter {} impl fmt::LowerHex for File { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let val: Vec = self.hash.clone(); - let mut str_builder: String = String::from(""); - for elem in val { - str_builder.push_str(&format!("{:x}", elem)); + for byte in &self.hash { + write!(f, "{:02x}", byte)?; } - write!(f, "{}", str_builder) + Ok(()) } } From f77e232142e5278665797b4eb6d97fbb6dc19011 Mon Sep 17 00:00:00 2001 From: BoySanic Date: Fri, 3 Apr 2026 16:11:09 -0700 Subject: [PATCH 06/12] Cleanup client implementation --- cli/src/client.rs | 78 ++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/cli/src/client.rs b/cli/src/client.rs index 226f685..32d9e8c 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -5,7 +5,7 @@ use std::{ fs, io::{Cursor, ErrorKind}, iter::{self, Empty}, - path::Path, + path::{Path, PathBuf}, process::{Output, Stdio}, sync::Arc, time::Duration, @@ -16,8 +16,8 @@ use clusterizer_client::result::ClientResult; use clusterizer_common::{ errors::SubmitResultError, records::{ - Platform, PlatformFilter, Project, ProjectFilter, ProjectVersion, ProjectVersionFilter, - Task, + File, FileFilter, Platform, PlatformFilter, Project, ProjectFilter, ProjectVersion, + ProjectVersionFilter, Task, }, requests::{FetchTasksRequest, SubmitResultRequest}, types::Id, @@ -35,9 +35,9 @@ struct ClusterizerClient { } struct TaskInfo { + file: File, + program_dir: PathBuf, task: Task, - project: Project, - project_version: ProjectVersion, } enum Return { @@ -92,24 +92,41 @@ impl ClusterizerClient { async fn fetch_tasks(self: Arc) -> ClientResult { let tasks = loop { - let mut projects: HashMap<_, _> = self + let projects: HashMap<_, _> = self .client - .get_all::(&ProjectFilter::default()) + .get_all::(&ProjectFilter::default().disabled(false)) .await? .into_iter() .map(|project| (project.id, project)) .collect(); - let projects: HashMap<_, _> = self + let project_versions: HashMap<_, _> = self .client .get_all::(&ProjectVersionFilter::default().disabled(false)) .await? .into_iter() .filter(|project_version| self.platform_ids.contains(&project_version.platform_id)) - .filter_map(|project_version| { - projects - .remove(&project_version.project_id) - .map(|project| (project.id, (project, project_version))) + .map(|project_version| (project_version.id, project_version)) + .collect(); + + let files: HashMap<_, _> = self + .client + .get_all::(&FileFilter::default()) + .await? + .into_iter() + .filter(|file| { + project_versions + .clone() + .into_iter() + .any(|(_, project_version)| project_version.file_id == file.id) + }) + .filter_map(|file| { + for project_version in project_versions.values() { + if project_version.file_id == file.id { + return Some((project_version.project_id, file)); + } + } + None }) .collect(); @@ -122,13 +139,12 @@ impl ClusterizerClient { .await? .into_iter() .filter_map(|task| { - let info = projects - .get(&task.project_id) - .map(|(project, project_version)| TaskInfo { - task, - project: project.clone(), - project_version: project_version.clone(), - }); + let file_path = self.args.cache_dir.join("bin"); + let info = files.get(&task.project_id).map(|file| TaskInfo { + task, + file: file.clone(), + program_dir: file_path.clone().join(format!("{:x}", file)), + }); if info.is_none() { warn!("Unwanted task received from server."); @@ -147,14 +163,10 @@ impl ClusterizerClient { }; for TaskInfo { - project_version, .. + program_dir, file, .. } in &tasks { - let file = self.client.get_one(project_version.file_id).await?; - - let binary_dir = self.args.binaries_dir().join(format!("{:x}", file)); - - download_archive(&file.url, &binary_dir, &self.args.cache_dir).await?; + download_archive(&file.url, program_dir, &self.args.cache_dir).await?; } Ok(Return::FetchTasks(tasks)) @@ -163,26 +175,16 @@ impl ClusterizerClient { async fn execute_task( self: Arc, TaskInfo { - task, - project, - project_version, + task, program_dir, .. }: TaskInfo, ) -> ClientResult { let slot_dir = tempfile::tempdir()?; info!("Task id: {}, stdin: {}", task.id, task.stdin); - info!("Project id: {}, name: {}", project.id, project.name); - debug!( - "Project version id: {}, file id: {}", - project_version.id, project_version.file_id - ); + info!("Project id: {}", task.project_id); debug!("Slot dir: {}", slot_dir.path().display()); - let file = self.client.get_one(project_version.file_id).await?; - - let project_version_dir = self.args.binaries_dir().join(format!("{:x}", file)); - - let program = project_version_dir + let program = program_dir .join(format!("main{}", env::consts::EXE_SUFFIX)) .canonicalize()?; From bec2630e786f5f09eb67b496397ed23803773935 Mon Sep 17 00:00:00 2001 From: BoySanic Date: Fri, 3 Apr 2026 21:27:24 -0700 Subject: [PATCH 07/12] Revert one line --- cli/src/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/client.rs b/cli/src/client.rs index 32d9e8c..45430b4 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -35,9 +35,9 @@ struct ClusterizerClient { } struct TaskInfo { + task: Task, file: File, program_dir: PathBuf, - task: Task, } enum Return { From 013bbb6937f20b4847c1d39112c752d8e4f91665 Mon Sep 17 00:00:00 2001 From: BoySanic Date: Sat, 4 Apr 2026 14:14:40 -0700 Subject: [PATCH 08/12] Put all relevant structs in TaskInfo for future extensibility --- cli/src/client.rs | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/cli/src/client.rs b/cli/src/client.rs index 45430b4..ad641b0 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -5,7 +5,7 @@ use std::{ fs, io::{Cursor, ErrorKind}, iter::{self, Empty}, - path::{Path, PathBuf}, + path::Path, process::{Output, Stdio}, sync::Arc, time::Duration, @@ -36,8 +36,9 @@ struct ClusterizerClient { struct TaskInfo { task: Task, + project: Project, + project_version: ProjectVersion, file: File, - program_dir: PathBuf, } enum Return { @@ -139,11 +140,16 @@ impl ClusterizerClient { .await? .into_iter() .filter_map(|task| { - let file_path = self.args.cache_dir.join("bin"); + let project = projects.get(&task.project_id)?; + let file = files.get(&task.project_id)?; + let project_version = project_versions + .iter() + .find(|(_, project_version)| project_version.file_id == file.id)?; let info = files.get(&task.project_id).map(|file| TaskInfo { task, file: file.clone(), - program_dir: file_path.clone().join(format!("{:x}", file)), + project: project.clone(), + project_version: project_version.1.clone(), }); if info.is_none() { @@ -162,11 +168,17 @@ impl ClusterizerClient { time::sleep(Duration::from_millis(15000)).await; }; - for TaskInfo { - program_dir, file, .. - } in &tasks - { - download_archive(&file.url, program_dir, &self.args.cache_dir).await?; + for TaskInfo { file, .. } in &tasks { + download_archive( + &file.url, + self.args + .cache_dir + .join("bin") + .join(format!("{:x}", file)) + .as_path(), + &self.args.cache_dir, + ) + .await?; } Ok(Return::FetchTasks(tasks)) @@ -175,15 +187,24 @@ impl ClusterizerClient { async fn execute_task( self: Arc, TaskInfo { - task, program_dir, .. + task, + project_version, + project, + file, }: TaskInfo, ) -> ClientResult { let slot_dir = tempfile::tempdir()?; info!("Task id: {}, stdin: {}", task.id, task.stdin); - info!("Project id: {}", task.project_id); + info!( + "Project id: {}, Project name: {}", + task.project_id, project.name + ); + debug!("Platform id: {}", project_version.platform_id); debug!("Slot dir: {}", slot_dir.path().display()); + let program_dir = self.args.cache_dir.join("bin").join(format!("{:x}", file)); + let program = program_dir .join(format!("main{}", env::consts::EXE_SUFFIX)) .canonicalize()?; From e383eba0d28f8d89e6ef23053b88dc4a9b9cac3a Mon Sep 17 00:00:00 2001 From: BoySanic Date: Sat, 4 Apr 2026 14:18:36 -0700 Subject: [PATCH 09/12] Make more idiomatic --- cli/src/client.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/cli/src/client.rs b/cli/src/client.rs index ad641b0..a9bea5a 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -117,17 +117,14 @@ impl ClusterizerClient { .into_iter() .filter(|file| { project_versions - .clone() - .into_iter() + .iter() .any(|(_, project_version)| project_version.file_id == file.id) }) .filter_map(|file| { - for project_version in project_versions.values() { - if project_version.file_id == file.id { - return Some((project_version.project_id, file)); - } - } - None + project_versions + .values() + .find(|project_version| project_version.file_id == file.id) + .map(|project_version| (project_version.project_id, file)) }) .collect(); From 0a80bc8b127992f75d893f5a6a00f82d74b4dc94 Mon Sep 17 00:00:00 2001 From: BoySanic Date: Tue, 7 Apr 2026 12:23:01 -0700 Subject: [PATCH 10/12] remove redundant filter --- cli/src/client.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cli/src/client.rs b/cli/src/client.rs index a9bea5a..570a866 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -115,11 +115,6 @@ impl ClusterizerClient { .get_all::(&FileFilter::default()) .await? .into_iter() - .filter(|file| { - project_versions - .iter() - .any(|(_, project_version)| project_version.file_id == file.id) - }) .filter_map(|file| { project_versions .values() From 5e4996c24309c5b156b6ba43e5fad3516315f524 Mon Sep 17 00:00:00 2001 From: BoySanic Date: Tue, 7 Apr 2026 12:26:55 -0700 Subject: [PATCH 11/12] Add file_id filters --- common/src/records/platform.rs | 11 ++++++++++- common/src/records/project_version.rs | 5 +++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/common/src/records/platform.rs b/common/src/records/platform.rs index be52af5..81e1716 100644 --- a/common/src/records/platform.rs +++ b/common/src/records/platform.rs @@ -13,4 +13,13 @@ pub struct Platform { #[non_exhaustive] #[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] -pub struct PlatformFilter {} +pub struct PlatformFilter { + pub file_id: Option>, +} + +impl PlatformFilter { + pub fn file_id(mut self, file_id: Id) -> Self { + self.file_id = Some(file_id); + self + } +} diff --git a/common/src/records/project_version.rs b/common/src/records/project_version.rs index bf18659..e724e8a 100644 --- a/common/src/records/project_version.rs +++ b/common/src/records/project_version.rs @@ -21,6 +21,7 @@ pub struct ProjectVersionFilter { pub disabled: Option, pub project_id: Option>, pub platform_id: Option>, + pub file_id: Option>, } impl ProjectVersionFilter { @@ -38,4 +39,8 @@ impl ProjectVersionFilter { self.platform_id = Some(platform_id); self } + pub fn file_id(mut self, file_id: Id) -> Self { + self.file_id = Some(file_id); + self + } } From cd887b7c5f4e3e6509907d6dd6bb041a534e4e6e Mon Sep 17 00:00:00 2001 From: Chen Steenvoorden Date: Tue, 7 Apr 2026 22:31:13 +0200 Subject: [PATCH 12/12] Fixed (hopefully) all the bugs --- Cargo.lock | 5 + Cargo.toml | 2 +- cli/Cargo.toml | 1 + cli/src/args.rs | 4 + cli/src/client.rs | 94 +++++++++---------- common/src/records/file.rs | 10 -- ...0ad5a1bc3133f8aa01f8b14d5ae84f889881.json} | 8 +- ...cf0a2945a63a68f7407b9a6404da2c48dd38.json} | 5 +- server/src/util/select.rs | 7 +- util/Cargo.toml | 6 ++ util/src/lib.rs | 14 +++ 11 files changed, 91 insertions(+), 65 deletions(-) rename server/.sqlx/{query-110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521.json => query-2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881.json} (73%) rename server/.sqlx/{query-f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff.json => query-767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38.json} (86%) create mode 100644 util/Cargo.toml create mode 100644 util/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 1c29e6e..2e9bb8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,6 +384,7 @@ dependencies = [ "clusterizer-api", "clusterizer-client", "clusterizer-common", + "clusterizer-util", "dirs", "reqwest", "tempfile", @@ -433,6 +434,10 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "clusterizer-util" +version = "0.1.0" + [[package]] name = "cmake" version = "0.1.58" diff --git a/Cargo.toml b/Cargo.toml index 24bbbeb..0b6c6a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "2" -members = ["api", "cli", "client", "common", "server"] +members = ["api", "cli", "client", "common", "server", "util"] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index b0a448f..5b0543b 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -8,6 +8,7 @@ clap = { version = "4.6.0", features = ["derive", "string"] } clusterizer-api = { version = "0.1.0", path = "../api" } clusterizer-client = { version = "0.1.0", path = "../client" } clusterizer-common = { version = "0.1.0", path = "../common" } +clusterizer-util = { version = "0.1.0", path = "../util" } dirs = "6.0.0" reqwest = { version = "0.13.2" } tempfile = "3.27.0" diff --git a/cli/src/args.rs b/cli/src/args.rs index f883a3b..50d54d9 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -46,6 +46,10 @@ impl RunArgs { pub fn binaries_dir(&self) -> PathBuf { self.cache_dir.join("bin") } + + pub fn temp_dir(&self) -> PathBuf { + self.cache_dir.join("tmp") + } } fn cache_dir() -> Resettable { diff --git a/cli/src/client.rs b/cli/src/client.rs index 570a866..f422aaf 100644 --- a/cli/src/client.rs +++ b/cli/src/client.rs @@ -5,7 +5,7 @@ use std::{ fs, io::{Cursor, ErrorKind}, iter::{self, Empty}, - path::Path, + path::PathBuf, process::{Output, Stdio}, sync::Arc, time::Duration, @@ -22,6 +22,7 @@ use clusterizer_common::{ requests::{FetchTasksRequest, SubmitResultRequest}, types::Id, }; +use clusterizer_util::Hex; use tokio::{io::AsyncWriteExt, process::Command, task::JoinSet, time}; use tracing::{debug, info, warn}; use zip::ZipArchive; @@ -93,56 +94,55 @@ impl ClusterizerClient { async fn fetch_tasks(self: Arc) -> ClientResult { let tasks = loop { - let projects: HashMap<_, _> = self + let project_versions_by_project_id: HashMap<_, _> = self .client - .get_all::(&ProjectFilter::default().disabled(false)) + .get_all::(&ProjectVersionFilter::default().disabled(false)) .await? .into_iter() - .map(|project| (project.id, project)) + .filter(|project_version| self.platform_ids.contains(&project_version.platform_id)) + .map(|project_version| (project_version.project_id, project_version)) .collect(); - let project_versions: HashMap<_, _> = self + let projects_by_project_id: HashMap<_, _> = self .client - .get_all::(&ProjectVersionFilter::default().disabled(false)) + .get_all::(&ProjectFilter::default().disabled(false)) .await? .into_iter() - .filter(|project_version| self.platform_ids.contains(&project_version.platform_id)) - .map(|project_version| (project_version.id, project_version)) + .filter(|project| project_versions_by_project_id.contains_key(&project.id)) + .map(|project| (project.id, project)) .collect(); - let files: HashMap<_, _> = self + let files_by_file_id: HashMap<_, _> = self .client .get_all::(&FileFilter::default()) .await? .into_iter() - .filter_map(|file| { - project_versions - .values() - .find(|project_version| project_version.file_id == file.id) - .map(|project_version| (project_version.project_id, file)) - }) + .map(|file| (file.id, file)) .collect(); + let get_task_info = |task: &Task| { + let project = projects_by_project_id.get(&task.project_id)?; + let project_version = project_versions_by_project_id.get(&task.project_id)?; + let file = files_by_file_id.get(&project_version.file_id)?; + + Some(TaskInfo { + task: task.clone(), + file: file.clone(), + project: project.clone(), + project_version: project_version.clone(), + }) + }; + let tasks: Vec<_> = self .client .fetch_tasks(&FetchTasksRequest { - project_ids: projects.keys().copied().collect(), + project_ids: projects_by_project_id.keys().copied().collect(), limit: self.args.threads, }) .await? .into_iter() .filter_map(|task| { - let project = projects.get(&task.project_id)?; - let file = files.get(&task.project_id)?; - let project_version = project_versions - .iter() - .find(|(_, project_version)| project_version.file_id == file.id)?; - let info = files.get(&task.project_id).map(|file| TaskInfo { - task, - file: file.clone(), - project: project.clone(), - project_version: project_version.1.clone(), - }); + let info = get_task_info(&task); if info.is_none() { warn!("Unwanted task received from server."); @@ -161,16 +161,7 @@ impl ClusterizerClient { }; for TaskInfo { file, .. } in &tasks { - download_archive( - &file.url, - self.args - .cache_dir - .join("bin") - .join(format!("{:x}", file)) - .as_path(), - &self.args.cache_dir, - ) - .await?; + download_archive(file, &self.args).await?; } Ok(Return::FetchTasks(tasks)) @@ -195,9 +186,10 @@ impl ClusterizerClient { debug!("Platform id: {}", project_version.platform_id); debug!("Slot dir: {}", slot_dir.path().display()); - let program_dir = self.args.cache_dir.join("bin").join(format!("{:x}", file)); - - let program = program_dir + let program = self + .args + .binaries_dir() + .join(format!("{}", Hex(&file.hash))) .join(format!("main{}", env::consts::EXE_SUFFIX)) .canonicalize()?; @@ -244,6 +236,7 @@ impl ClusterizerClient { pub async fn run(client: ApiClient, args: RunArgs) -> ClientResult<()> { fs::create_dir_all(args.binaries_dir())?; + fs::create_dir_all(args.temp_dir())?; let mut platform_ids = Vec::new(); let mut platform_names = Vec::new(); @@ -259,9 +252,7 @@ pub async fn run(client: ApiClient, args: RunArgs) -> ClientResult<()> { platform.id, file.url ); - let platform_tester_dir = args.binaries_dir().join(format!("{:x}", file)); - - download_archive(&file.url, &platform_tester_dir, &args.cache_dir).await?; + let platform_tester_dir = download_archive(&file, &args).await?; let slot_dir = tempfile::tempdir()?; @@ -303,18 +294,25 @@ pub async fn run(client: ApiClient, args: RunArgs) -> ClientResult<()> { .await } -async fn download_archive(url: &str, dir: &Path, cache_dir: &Path) -> ClientResult<()> { +async fn download_archive(file: &File, args: &RunArgs) -> ClientResult { + let dir = args.binaries_dir().join(format!("{}", Hex(&file.hash))); + if dir.exists() { debug!("Archive {} was cached.", dir.display()); } else { debug!("Archive {} is not cached.", dir.display()); - let bytes = reqwest::get(url).await?.error_for_status()?.bytes().await?; - let extract_dir = tempfile::tempdir_in(cache_dir)?; + let bytes = reqwest::get(&file.url) + .await? + .error_for_status()? + .bytes() + .await?; + + let extract_dir = tempfile::tempdir_in(args.temp_dir())?; ZipArchive::new(Cursor::new(bytes))?.extract(&extract_dir)?; - fs::rename(&extract_dir, dir)?; + fs::rename(&extract_dir, &dir)?; } - Ok(()) + Ok(dir) } diff --git a/common/src/records/file.rs b/common/src/records/file.rs index 15b8736..1efe33f 100644 --- a/common/src/records/file.rs +++ b/common/src/records/file.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use std::fmt; use crate::types::Id; @@ -15,12 +14,3 @@ pub struct File { #[non_exhaustive] #[derive(Clone, Hash, Debug, Default, Serialize, Deserialize)] pub struct FileFilter {} - -impl fmt::LowerHex for File { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for byte in &self.hash { - write!(f, "{:02x}", byte)?; - } - Ok(()) - } -} diff --git a/server/.sqlx/query-110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521.json b/server/.sqlx/query-2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881.json similarity index 73% rename from server/.sqlx/query-110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521.json rename to server/.sqlx/query-2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881.json index 5f055f5..e4524c8 100644 --- a/server/.sqlx/query-110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521.json +++ b/server/.sqlx/query-2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n platforms\n ", + "query": "\n SELECT\n *\n FROM\n platforms\n WHERE\n file_id = $1 IS NOT FALSE\n ", "describe": { "columns": [ { @@ -25,7 +25,9 @@ } ], "parameters": { - "Left": [] + "Left": [ + "Int8" + ] }, "nullable": [ false, @@ -34,5 +36,5 @@ false ] }, - "hash": "110699ecb02de724e7b96c93a01edb9ea3cd06c2c1189c29703b35f4894d7521" + "hash": "2ef52d712fe9cda17273356da3ee0ad5a1bc3133f8aa01f8b14d5ae84f889881" } diff --git a/server/.sqlx/query-f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff.json b/server/.sqlx/query-767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38.json similarity index 86% rename from server/.sqlx/query-f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff.json rename to server/.sqlx/query-767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38.json index 09fc36b..8461a2f 100644 --- a/server/.sqlx/query-f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff.json +++ b/server/.sqlx/query-767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n *\n FROM\n project_versions\n WHERE\n disabled_at IS NULL IS DISTINCT FROM $1\n AND project_id = $2 IS NOT FALSE\n AND platform_id = $3 IS NOT FALSE\n ", + "query": "\n SELECT\n *\n FROM\n project_versions\n WHERE\n disabled_at IS NULL IS DISTINCT FROM $1\n AND project_id = $2 IS NOT FALSE\n AND platform_id = $3 IS NOT FALSE\n AND file_id = $4 IS NOT FALSE\n ", "describe": { "columns": [ { @@ -38,6 +38,7 @@ "Left": [ "Bool", "Int8", + "Int8", "Int8" ] }, @@ -50,5 +51,5 @@ false ] }, - "hash": "f81a9819a313d83b482f9c83aa3a21c3c1d24643ce92e2fc64021566146f6eff" + "hash": "767d70fb7dfab388eb34d3cc3e44cf0a2945a63a68f7407b9a6404da2c48dd38" } diff --git a/server/src/util/select.rs b/server/src/util/select.rs index 6550a89..5af08c0 100644 --- a/server/src/util/select.rs +++ b/server/src/util/select.rs @@ -67,7 +67,7 @@ impl Select for Project { impl Select for Platform { type Filter = PlatformFilter; - fn select_all(_: &Self::Filter) -> Map { + fn select_all(filter: &Self::Filter) -> Map { sqlx::query_as_unchecked!( Self, r#" @@ -75,7 +75,10 @@ impl Select for Platform { * FROM platforms + WHERE + file_id = $1 IS NOT FALSE "#, + filter.file_id, ) } @@ -99,10 +102,12 @@ impl Select for ProjectVersion { disabled_at IS NULL IS DISTINCT FROM $1 AND project_id = $2 IS NOT FALSE AND platform_id = $3 IS NOT FALSE + AND file_id = $4 IS NOT FALSE "#, filter.disabled, filter.project_id, filter.platform_id, + filter.file_id, ) } diff --git a/util/Cargo.toml b/util/Cargo.toml new file mode 100644 index 0000000..22462ae --- /dev/null +++ b/util/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "clusterizer-util" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/util/src/lib.rs b/util/src/lib.rs new file mode 100644 index 0000000..75d8265 --- /dev/null +++ b/util/src/lib.rs @@ -0,0 +1,14 @@ +use std::fmt::{self, Display, Formatter}; + +#[derive(Debug)] +pub struct Hex<'a>(pub &'a [u8]); + +impl Display for Hex<'_> { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + for byte in self.0 { + write!(f, "{byte:02x}")?; + } + + Ok(()) + } +}