use std::{collections::HashMap, process::Stdio}; use futures_util::StreamExt; use semver::{Version, VersionReq}; use serde::{Deserialize, Serialize}; use sqlx::{query_as, Type}; use tokio::{ fs::{self, OpenOptions}, io::AsyncWriteExt, process::Command, }; use crate::{ db::{DbDep, DbVersion, PgU32}, db_error, get_crate_prefix, internal_error, Errors, State, INDEX_LOCK, }; #[derive(Serialize, Deserialize)] pub struct CrateVersion { pub name: String, pub vers: Version, pub deps: Vec, pub cksum: String, pub features: HashMap>, pub yanked: bool, pub links: Option, /// Should always be `1` for maximum compatability pub v: u32, // `features2` exists, but for out purposes, it can be ignored. See https://doc.rust-lang.org/cargo/reference/registries.html } #[derive(Serialize, Deserialize)] pub struct Dependency { #[serde(flatten)] pub base: BaseDependency, pub package: Option, } #[derive(Serialize, Deserialize)] pub struct BaseDependency { pub name: String, pub version_req: VersionReq, pub features: Vec, pub optional: bool, pub default_features: bool, pub target: Option, pub kind: DependencyKind, pub registry: Option, } #[derive(Serialize, Deserialize, Clone, Copy, Type)] #[serde(rename_all = "snake_case")] #[sqlx(rename_all = "snake_case")] #[sqlx(type_name = "dependency_kind")] pub enum DependencyKind { Dev, Build, Normal, } pub async fn update_crate_from_db( crate_id: PgU32, state: &State, message: &str, ) -> Result<(), Errors> { let lock = INDEX_LOCK.lock().await; let mut db = state.db.acquire().await.map_err(db_error)?; let (mut crate_name,): (String,) = query_as("SELECT name FROM crates WHERE id = $1") .bind(crate_id) .fetch_one(&mut db) .await .map_err(db_error)?; // we use `fetch_all` here since we cant use `fetch` because of conflicting mutable borrows let versions = query_as::<_, DbVersion>("SELECT * FROM versions WHERE crate_id = $1") .bind(crate_id) .fetch_all(&mut db) .await .map_err(db_error)?; let mut versions2 = Vec::new(); for version in versions { let mut deps = query_as::<_, DbDep>("SELECT * FROM deps WHERE version_id = $1") .bind(version.id) .fetch(&mut db); let mut deps2 = Vec::new(); while let Some(dep) = deps.next().await.transpose().map_err(db_error)? { deps2.push(Dependency { base: BaseDependency { name: dep.name, version_req: dep.version_req.parse().unwrap(), features: dep.features, optional: dep.optional, default_features: dep.default_features, target: dep.target, kind: dep.kind, registry: dep.registry, }, package: dep.package, }); } versions2.push(CrateVersion { name: crate_name.clone(), vers: version.vers.parse().unwrap(), deps: deps2, cksum: version.cksum, features: version .features .into_iter() .map(|v| (v.feature, v.enables)) .collect(), yanked: version.yanked, links: version.links, v: 1, }); } crate_name.make_ascii_lowercase(); let mut path = state.index_dir.clone(); path.push(&get_crate_prefix(&crate_name).unwrap()); fs::create_dir_all(&path).await.map_err(internal_error)?; path.push(&crate_name); let mut file = OpenOptions::new() .write(true) .create(true) .truncate(true) .open(&path) .await .map_err(internal_error)?; for version in versions2 { let mut buf = serde_json::to_vec(&version).map_err(internal_error)?; buf.push(b'\n'); file.write_all(&buf).await.map_err(internal_error)?; } Command::new("git") .arg("add") .arg(&path) .current_dir(&state.index_dir) .stdin(Stdio::null()) .stderr(Stdio::null()) .stdout(Stdio::null()) .status() .await .map_err(internal_error)?; Command::new("git") .arg("commit") .arg("-m") .arg(message) .current_dir(&state.index_dir) .stdin(Stdio::null()) .stderr(Stdio::null()) .stdout(Stdio::null()) .status() .await .map_err(internal_error)?; drop(lock); Ok(()) }