Split out hpk-package into separate crate; Implement Repository::build()
This commit is contained in:
parent
9126e1da93
commit
082c215b44
105
Cargo.lock
generated
105
Cargo.lock
generated
@ -127,9 +127,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.3"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
@ -226,7 +226,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -243,7 +243,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -380,24 +380,36 @@ dependencies = [
|
||||
name = "hpk"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"deku",
|
||||
"hpk-package",
|
||||
"rayon",
|
||||
"ron",
|
||||
"serde",
|
||||
"sha2",
|
||||
"tar",
|
||||
"ureq",
|
||||
"url",
|
||||
"walkdir",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hpk-package"
|
||||
version = "0.1.0"
|
||||
source = "git+https://git.hitchhiker-linux.org/jeang3nie/hpk-package.git#90b163eb1b7373085d3c5eb93919eb8ecfedb219"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"deku",
|
||||
"libc",
|
||||
"rayon",
|
||||
"ron",
|
||||
"serde",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.55"
|
||||
version = "0.1.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "716f12fbcfac6ffab0a5e9ec51d0a0ff70503742bb2dc7b99396394c9dc323f0"
|
||||
checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
@ -574,9 +586,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.54"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534"
|
||||
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -757,7 +769,7 @@ checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -807,9 +819,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.12"
|
||||
version = "2.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927"
|
||||
checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -822,15 +834,6 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"deku",
|
||||
"libc",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.2.0"
|
||||
@ -857,7 +860,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
"syn 2.0.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1110,9 +1113,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2649ff315bee4c98757f15dac226efe3d81927adbb6e882084bb1ee3e0c330a7"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
@ -1134,17 +1137,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f8996d3f43b4b2d44327cd71b7b0efd1284ab60e6e9d0e8b630e18555d87d3e"
|
||||
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.47.0",
|
||||
"windows_aarch64_msvc 0.47.0",
|
||||
"windows_i686_gnu 0.47.0",
|
||||
"windows_i686_msvc 0.47.0",
|
||||
"windows_x86_64_gnu 0.47.0",
|
||||
"windows_x86_64_gnullvm 0.47.0",
|
||||
"windows_x86_64_msvc 0.47.0",
|
||||
"windows_aarch64_gnullvm 0.48.0",
|
||||
"windows_aarch64_msvc 0.48.0",
|
||||
"windows_i686_gnu 0.48.0",
|
||||
"windows_i686_msvc 0.48.0",
|
||||
"windows_x86_64_gnu 0.48.0",
|
||||
"windows_x86_64_gnullvm 0.48.0",
|
||||
"windows_x86_64_msvc 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1155,9 +1158,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "831d567d53d4f3cb1db332b68e6e2b6260228eb4d99a777d8b2e8ed794027c90"
|
||||
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
@ -1167,9 +1170,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a42d54a417c60ce4f0e31661eed628f0fa5aca73448c093ec4d45fab4c51cdf"
|
||||
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
@ -1179,9 +1182,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1925beafdbb22201a53a483db861a5644123157c1c3cee83323a2ed565d71e3"
|
||||
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
@ -1191,9 +1194,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a8ef8f2f1711b223947d9b69b596cf5a4e452c930fb58b6fc3fdae7d0ec6b31"
|
||||
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
@ -1203,9 +1206,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7acaa0c2cf0d2ef99b61c308a0c3dbae430a51b7345dedec470bd8f53f5a3642"
|
||||
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
@ -1215,9 +1218,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5a0628f71be1d11e17ca4a0e9e15b3a5180f6fbf1c2d55e3ba3f850378052c1"
|
||||
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
@ -1227,9 +1230,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.47.0"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6e62c256dc6d40b8c8707df17df8d774e60e39db723675241e7c15e910bce7"
|
||||
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
|
15
Cargo.toml
15
Cargo.toml
@ -6,25 +6,12 @@ license = "GPL-3.0-only"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[workspace]
|
||||
members = [ "tar" ]
|
||||
|
||||
[workspace.dependencies]
|
||||
deku = "0.16"
|
||||
|
||||
[dependencies]
|
||||
deku.workspace = true
|
||||
hpk-package = { git = "https://git.hitchhiker-linux.org/jeang3nie/hpk-package.git" }
|
||||
rayon = "1.7"
|
||||
ron = "0.8"
|
||||
sha2 = "0.10"
|
||||
tar = { path = "tar" }
|
||||
walkdir = "2.3"
|
||||
zstd = "0.12"
|
||||
|
||||
[dependencies.chrono]
|
||||
version = "0.4"
|
||||
features = ["serde"]
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
features = ["derive"]
|
||||
|
@ -1,5 +1,6 @@
|
||||
use {
|
||||
crate::{Entry, Item, Package, Plist, Specs},
|
||||
crate::{Item, Package, Plist, Specs},
|
||||
hpk_package::Entry,
|
||||
rayon::prelude::{IntoParallelRefIterator, ParallelIterator},
|
||||
std::{
|
||||
borrow::BorrowMut,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use {
|
||||
crate::{Package, Repository, Version},
|
||||
hpk_package::ron::{self, ser::PrettyConfig},
|
||||
rayon::prelude::*,
|
||||
ron::ser::PrettyConfig,
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{
|
||||
collections::HashMap,
|
||||
|
@ -1,5 +1,8 @@
|
||||
use crate::Entry;
|
||||
use sha2::{Digest, Sha256};
|
||||
use hpk_package::{
|
||||
sha2::{Digest, Sha256},
|
||||
tar::{Node, Owner},
|
||||
Entry,
|
||||
};
|
||||
use std::{
|
||||
error::Error,
|
||||
ffi::OsStr,
|
||||
@ -9,7 +12,6 @@ use std::{
|
||||
os::unix::fs::MetadataExt,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use tar::{Node, Owner};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Item {
|
||||
|
@ -3,10 +3,7 @@ mod creator;
|
||||
mod db;
|
||||
mod hooks;
|
||||
mod item;
|
||||
mod package;
|
||||
mod plist;
|
||||
mod repository;
|
||||
mod version;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -14,12 +11,9 @@ pub use {
|
||||
creator::{Creator, Message},
|
||||
db::Database,
|
||||
hooks::Hooks,
|
||||
hpk_package::{tar, Dependency, GitRev, Package, Plist, Rapid, SemVer, Specs, Version},
|
||||
item::Item,
|
||||
package::{Dependency, Package, Specs},
|
||||
plist::*,
|
||||
repository::Repository,
|
||||
tar,
|
||||
version::*,
|
||||
};
|
||||
|
||||
const DB: [&str; 4] = ["var", "db", "hpk", "db.ron.zstd"];
|
||||
|
@ -1,28 +0,0 @@
|
||||
use {
|
||||
super::Package,
|
||||
crate::Version,
|
||||
serde::{Deserialize, Serialize},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Dependency {
|
||||
pub name: String,
|
||||
pub version: (Option<Version>, Option<Version>),
|
||||
}
|
||||
|
||||
impl Dependency {
|
||||
#[allow(clippy::must_use_candidate)]
|
||||
pub fn satisfied(&self, package: &Package) -> bool {
|
||||
if self.name.as_str() == package.name.as_str() {
|
||||
match &self.version {
|
||||
(Some(low), Some(high)) => &package.version >= low && &package.version < high,
|
||||
(Some(low), None) => &package.version >= low,
|
||||
(None, Some(high)) => &package.version < high,
|
||||
// no version requirements
|
||||
_ => true,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
@ -1,122 +0,0 @@
|
||||
mod dependency;
|
||||
mod specs;
|
||||
|
||||
use {
|
||||
crate::{Plist, Version},
|
||||
ron::ser::{to_string_pretty, PrettyConfig},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{
|
||||
error::Error,
|
||||
fs,
|
||||
fs::File,
|
||||
io::{BufWriter, Write},
|
||||
path::Path,
|
||||
},
|
||||
tar::{Node, Owner},
|
||||
};
|
||||
|
||||
pub use {dependency::Dependency, specs::Specs};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
pub uid: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Group {
|
||||
pub name: String,
|
||||
pub gid: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
/// the metadata associated with a package
|
||||
pub struct Package {
|
||||
/// The name of the package minus all version information
|
||||
pub name: String,
|
||||
/// The `Version` of the package
|
||||
pub version: Version,
|
||||
/// The release number for this package
|
||||
pub release: u8,
|
||||
/// a single line description of the package
|
||||
pub description: String,
|
||||
/// a more verbose description of the package
|
||||
pub long_description: String,
|
||||
/// an optional link to an
|
||||
/// [AppStream](https://www.freedesktop.org/wiki/Distributions/AppStream/)
|
||||
/// metadata file
|
||||
pub appstream_data: Option<String>,
|
||||
/// a listing of all files, directories and symlinks which are a part of
|
||||
/// this package
|
||||
pub plist: Plist,
|
||||
/// the total size of this package
|
||||
pub size: usize,
|
||||
/// all of this package's runtime dependencies
|
||||
pub dependencies: Vec<Dependency>,
|
||||
/// an optional list of users to be created upon installation
|
||||
pub users: Option<Vec<User>>,
|
||||
/// an optional list of groups to be created upon installation
|
||||
pub groups: Option<Vec<Group>>,
|
||||
/// an optional post installation shell script to be run
|
||||
pub post_install: Option<String>,
|
||||
}
|
||||
|
||||
impl From<Specs> for Package {
|
||||
fn from(value: Specs) -> Self {
|
||||
Package {
|
||||
name: value.name,
|
||||
version: value.version,
|
||||
release: value.release,
|
||||
description: value.description,
|
||||
long_description: value.long_description,
|
||||
appstream_data: value.appstream_data,
|
||||
dependencies: value.dependencies,
|
||||
users: value.users,
|
||||
groups: value.groups,
|
||||
post_install: value.post_install,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Package {
|
||||
fn as_ron(&self) -> Result<String, ron::Error> {
|
||||
let cfg = PrettyConfig::new().struct_names(true);
|
||||
to_string_pretty(self, cfg)
|
||||
}
|
||||
|
||||
pub(crate) fn save_ron_and_create_tar_node(
|
||||
&self,
|
||||
outdir: &Path,
|
||||
) -> Result<Node, Box<dyn Error>> {
|
||||
if !outdir.exists() {
|
||||
fs::create_dir_all(outdir)?;
|
||||
}
|
||||
let mut outfile = outdir.to_path_buf();
|
||||
outfile.push("package.ron");
|
||||
let fd = File::create(&outfile)?;
|
||||
let s = self.as_ron()?;
|
||||
let mut writer = BufWriter::new(&fd);
|
||||
writer.write_all(s.as_bytes())?;
|
||||
writer.flush()?;
|
||||
let node = Node::read_data_to_tar(
|
||||
s.as_bytes(),
|
||||
"package.ron",
|
||||
&outfile.metadata()?,
|
||||
Some(Owner::default()),
|
||||
)?;
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
/// Returns the formatted full package name including version and release strings
|
||||
pub fn fullname(&self) -> String {
|
||||
format!("{}-{}_{}", self.name, self.version, self.release)
|
||||
}
|
||||
|
||||
/// Tests whether this package is an update for another
|
||||
pub fn is_upgrade(&self, other: &Self) -> bool {
|
||||
self.name == other.name
|
||||
&& (self.version > other.version
|
||||
|| (self.version == other.version && self.release > other.release))
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
use {
|
||||
super::{Group, User},
|
||||
crate::{Dependency, Version},
|
||||
serde::{Deserialize, Serialize},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Specs {
|
||||
/// The name of the package minus all version information
|
||||
pub name: String,
|
||||
/// The `Version` of the package
|
||||
pub version: Version,
|
||||
/// The release number for this package
|
||||
pub release: u8,
|
||||
/// a single line description of the package
|
||||
pub description: String,
|
||||
/// a more verbose description of the package
|
||||
pub long_description: String,
|
||||
/// an optional link to an
|
||||
/// [AppStream](https://www.freedesktop.org/wiki/Distributions/AppStream/)
|
||||
/// metadata file
|
||||
pub appstream_data: Option<String>,
|
||||
/// all of this package's runtime dependencies
|
||||
pub dependencies: Vec<Dependency>,
|
||||
/// an optional list of users to be created upon installation
|
||||
pub users: Option<Vec<User>>,
|
||||
/// an optional list of groups to be created upon installation
|
||||
pub groups: Option<Vec<Group>>,
|
||||
/// an optional post installation shell script to be run
|
||||
pub post_install: Option<String>,
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
use {
|
||||
rayon::prelude::*,
|
||||
serde::{Deserialize, Serialize},
|
||||
sha2::{digest::Digest, Sha256},
|
||||
std::{
|
||||
error::Error,
|
||||
fmt::Write,
|
||||
fs,
|
||||
io::Read,
|
||||
os::unix::fs::MetadataExt,
|
||||
path::{Path, PathBuf},
|
||||
},
|
||||
walkdir::WalkDir,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Plist {
|
||||
pub entries: Vec<Entry>,
|
||||
}
|
||||
|
||||
impl TryFrom<&Path> for Plist {
|
||||
type Error = Box<dyn Error>;
|
||||
|
||||
fn try_from(value: &Path) -> Result<Self, Self::Error> {
|
||||
let entries = WalkDir::new(value)
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>()
|
||||
.par_iter()
|
||||
.filter(|x| x.is_ok())
|
||||
.filter_map(|x| {
|
||||
Entry::try_from(x.as_ref().unwrap().path().to_path_buf().as_path()).ok()
|
||||
})
|
||||
.collect();
|
||||
Ok(Self { entries })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum Entry {
|
||||
File {
|
||||
path: PathBuf,
|
||||
sha256sum: String,
|
||||
mode: u32,
|
||||
size: usize,
|
||||
},
|
||||
Directory {
|
||||
path: PathBuf,
|
||||
mode: u32,
|
||||
},
|
||||
Link {
|
||||
path: PathBuf,
|
||||
target: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
impl TryFrom<&Path> for Entry {
|
||||
type Error = Box<dyn Error>;
|
||||
|
||||
fn try_from(value: &Path) -> Result<Self, Self::Error> {
|
||||
let mut path = PathBuf::from("/");
|
||||
path.push(value);
|
||||
let meta = fs::metadata(value)?;
|
||||
if meta.is_file() {
|
||||
let mut buf = vec![];
|
||||
let mut fd = fs::File::open(value)?;
|
||||
let size = fd.read_to_end(&mut buf)?;
|
||||
let mut sha256sum = String::new();
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(&buf);
|
||||
let res = hasher.finalize();
|
||||
for c in res {
|
||||
write!(sha256sum, "{c:02x}")?;
|
||||
}
|
||||
let mode = meta.mode();
|
||||
Ok(Self::File {
|
||||
path,
|
||||
sha256sum,
|
||||
mode,
|
||||
size,
|
||||
})
|
||||
} else if meta.is_dir() {
|
||||
let mode = meta.mode();
|
||||
Ok(Self::Directory { path, mode })
|
||||
} else if meta.is_symlink() {
|
||||
let target = fs::read_link(value)?;
|
||||
Ok(Self::Link { path, target })
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,15 @@
|
||||
use std::sync::PoisonError;
|
||||
|
||||
use {
|
||||
crate::Package,
|
||||
hpk_package::ron,
|
||||
rayon::prelude::*,
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{collections::HashMap, sync::Mutex},
|
||||
std::{
|
||||
collections::HashMap,
|
||||
error::Error,
|
||||
io::Read,
|
||||
sync::{mpsc::Sender, Mutex, PoisonError},
|
||||
time::Duration,
|
||||
},
|
||||
url::Url,
|
||||
};
|
||||
|
||||
@ -15,7 +20,43 @@ pub struct Repository {
|
||||
pub packages: HashMap<String, Package>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
ContentLength(usize),
|
||||
BytesRead(usize),
|
||||
Failure(String),
|
||||
Success(usize),
|
||||
}
|
||||
|
||||
impl Repository {
|
||||
pub fn build(name: &str, url: &Url, sender: Sender<Message>) -> Result<Self, Box<dyn Error>> {
|
||||
let url = url.join("packages.ron.zstd")?;
|
||||
let resp = ureq::get(&url.to_string())
|
||||
.timeout(Duration::from_secs(10))
|
||||
.call()?;
|
||||
if let Some(val) = resp.header("Content-Length") {
|
||||
if let Ok(num) = val.parse::<usize>() {
|
||||
sender.send(Message::ContentLength(num))?;
|
||||
}
|
||||
}
|
||||
let reader = resp.into_reader();
|
||||
let mut reader = zstd::Decoder::new(reader)?;
|
||||
let mut buf = vec![];
|
||||
loop {
|
||||
let bytes = reader.read(&mut buf)?;
|
||||
if bytes == 0 {
|
||||
break;
|
||||
}
|
||||
sender.send(Message::BytesRead(bytes))?;
|
||||
}
|
||||
let packages = ron::de::from_bytes(&buf)?;
|
||||
Ok(Self {
|
||||
name: name.to_string(),
|
||||
base_url: url.clone(),
|
||||
packages,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn search_names(&self, query: &str) -> Result<Vec<&Package>, PoisonError<Vec<&Package>>> {
|
||||
let mut results = vec![];
|
||||
if let Some(p) = self.packages.get(query) {
|
||||
|
@ -1,79 +0,0 @@
|
||||
use {
|
||||
crate::Version,
|
||||
chrono::{offset::Utc, DateTime},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{cmp::Ordering, error::Error, fmt, str::FromStr},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct GitRev {
|
||||
/// the short revision hash
|
||||
pub hash: String,
|
||||
/// the time of the revision commit
|
||||
pub datetime: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl fmt::Display for GitRev {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "git_{}.{}", self.hash, self.datetime.format("%Y%m%d"))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for GitRev {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
self.datetime.partial_cmp(&other.datetime)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for GitRev {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.datetime.cmp(&other.datetime)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseGitRevError;
|
||||
|
||||
impl fmt::Display for ParseGitRevError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Error parsing git version")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseGitRevError {}
|
||||
|
||||
impl From<chrono::ParseError> for ParseGitRevError {
|
||||
fn from(_value: chrono::ParseError) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for GitRev {
|
||||
type Err = ParseGitRevError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Some(gitrev) = s.strip_prefix("git_") {
|
||||
if let Some((hash, date)) = gitrev.split_once('_') {
|
||||
if hash.len() == 7 {
|
||||
let datetime = DateTime::parse_from_str(date, "%Y%m%d")?;
|
||||
return Ok(Self {
|
||||
hash: hash.to_string(),
|
||||
datetime: datetime.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(ParseGitRevError)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Version> for GitRev {
|
||||
type Error = ParseGitRevError;
|
||||
|
||||
fn try_from(value: Version) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Version::Git(g) => Ok(g),
|
||||
_ => Err(ParseGitRevError),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
use {
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{error::Error, fmt, str::FromStr},
|
||||
};
|
||||
|
||||
mod gitrev;
|
||||
mod rapid;
|
||||
mod semver;
|
||||
|
||||
pub use {gitrev::GitRev, rapid::Rapid, semver::SemVer};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum Version {
|
||||
Number(u32),
|
||||
Rapid(Rapid),
|
||||
SemVer(SemVer),
|
||||
Git(GitRev),
|
||||
}
|
||||
|
||||
impl Default for Version {
|
||||
fn default() -> Self {
|
||||
Self::SemVer(SemVer {
|
||||
major: 0,
|
||||
minor: 1,
|
||||
patch: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Number(n) => write!(f, "{n}"),
|
||||
Self::SemVer(s) => write!(f, "{s}"),
|
||||
Self::Rapid(r) => write!(f, "{r}"),
|
||||
Self::Git(g) => write!(f, "{g}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SemVer> for Version {
|
||||
fn from(value: SemVer) -> Self {
|
||||
Self::SemVer(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rapid> for Version {
|
||||
fn from(value: Rapid) -> Self {
|
||||
Self::Rapid(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GitRev> for Version {
|
||||
fn from(value: GitRev) -> Self {
|
||||
Self::Git(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Version {
|
||||
fn from(value: u32) -> Self {
|
||||
Self::Number(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Version {
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
match (self, other) {
|
||||
(Self::Number(s), Self::Number(o)) => s.partial_cmp(o),
|
||||
(Self::Number(s), Self::Rapid(o)) => s.partial_cmp(o),
|
||||
(Self::Number(s), Self::SemVer(o)) => s.partial_cmp(o),
|
||||
(Self::Rapid(s), Self::Number(o)) => s.partial_cmp(o),
|
||||
(Self::Rapid(s), Self::Rapid(o)) => s.partial_cmp(o),
|
||||
(Self::Rapid(s), Self::SemVer(o)) => s.partial_cmp(o),
|
||||
(Self::SemVer(s), Self::Number(o)) => s.partial_cmp(o),
|
||||
(Self::SemVer(s), Self::Rapid(o)) => s.partial_cmp(o),
|
||||
(Self::SemVer(s), Self::SemVer(o)) => s.partial_cmp(o),
|
||||
(Self::Git(s), Self::Git(o)) => s.partial_cmp(o),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseVersionError;
|
||||
|
||||
impl fmt::Display for ParseVersionError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "error parsing version")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseVersionError {}
|
||||
|
||||
impl FromStr for Version {
|
||||
type Err = ParseVersionError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Ok(v) = s.parse::<SemVer>() {
|
||||
Ok(v.into())
|
||||
} else if let Ok(v) = s.parse::<Rapid>() {
|
||||
Ok(v.into())
|
||||
} else if let Ok(v) = s.parse::<GitRev>() {
|
||||
Ok(v.into())
|
||||
} else if let Ok(v) = s.parse::<u32>() {
|
||||
Ok(v.into())
|
||||
} else {
|
||||
Err(ParseVersionError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cmp_semver_rapid_gt() {
|
||||
let sem = "1.42.1".parse::<SemVer>().unwrap();
|
||||
let rpd = "1.42".parse::<Rapid>().unwrap();
|
||||
assert!(sem > rpd);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cmp_semver_rapid_eq() {
|
||||
let sem = "1.42.0".parse::<SemVer>().unwrap();
|
||||
let rpd = "1.42".parse::<Rapid>().unwrap();
|
||||
assert!(sem == rpd);
|
||||
}
|
||||
}
|
@ -1,206 +0,0 @@
|
||||
use {
|
||||
chrono::{offset::Utc, DateTime},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{error::Error, fmt, str::FromStr},
|
||||
};
|
||||
|
||||
mod gitrev;
|
||||
mod rapid;
|
||||
mod semver;
|
||||
|
||||
pub use {gitrev::GitRev, rapid::Rapid, semver::SemVer};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum Version {
|
||||
Number(u32),
|
||||
Rapid {
|
||||
major: u32,
|
||||
minor: u32,
|
||||
},
|
||||
SemVer {
|
||||
major: u32,
|
||||
minor: u32,
|
||||
patch: u32,
|
||||
},
|
||||
Git {
|
||||
hash: String,
|
||||
datetime: DateTime<Utc>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for Version {
|
||||
fn default() -> Self {
|
||||
Self::SemVer {
|
||||
major: 0,
|
||||
minor: 1,
|
||||
patch: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Number(n) => write!(f, "{n}"),
|
||||
Self::SemVer {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
} => {
|
||||
let v = SemVer {
|
||||
major: *major,
|
||||
minor: *minor,
|
||||
patch: *patch,
|
||||
};
|
||||
write!(f, "{v}")
|
||||
}
|
||||
Self::Rapid { major, minor } => {
|
||||
let v = Rapid {
|
||||
major: *major,
|
||||
minor: *minor,
|
||||
};
|
||||
write!(f, "{v}")
|
||||
}
|
||||
Self::Git { hash, datetime } => {
|
||||
let v = GitRev {
|
||||
hash: hash.clone(),
|
||||
datetime: *datetime,
|
||||
};
|
||||
write!(f, "{v}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SemVer> for Version {
|
||||
fn from(value: SemVer) -> Self {
|
||||
Self::SemVer {
|
||||
major: value.major,
|
||||
minor: value.minor,
|
||||
patch: value.patch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rapid> for Version {
|
||||
fn from(value: Rapid) -> Self {
|
||||
Self::Rapid {
|
||||
major: value.major,
|
||||
minor: value.minor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GitRev> for Version {
|
||||
fn from(value: GitRev) -> Self {
|
||||
Self::Git {
|
||||
hash: value.hash,
|
||||
datetime: value.datetime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Version {
|
||||
fn from(value: u32) -> Self {
|
||||
Self::Number(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Version {
|
||||
#[allow(clippy::many_single_char_names)]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
match (self, other) {
|
||||
(Self::Number(s), Self::Number(o)) => s.partial_cmp(o),
|
||||
(
|
||||
Self::SemVer {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
},
|
||||
Self::SemVer {
|
||||
major: a,
|
||||
minor: b,
|
||||
patch: c,
|
||||
},
|
||||
) => (major, minor, patch).partial_cmp(&(a, b, c)),
|
||||
(Self::Rapid { major, minor }, Self::Rapid { major: a, minor: b }) => {
|
||||
(major, minor).partial_cmp(&(a, b))
|
||||
}
|
||||
(
|
||||
Self::Git {
|
||||
hash: _a,
|
||||
datetime: b,
|
||||
},
|
||||
Self::Git {
|
||||
hash: _c,
|
||||
datetime: d,
|
||||
},
|
||||
) => b.partial_cmp(&d),
|
||||
(
|
||||
Self::SemVer {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
},
|
||||
Self::Rapid { major: a, minor: b },
|
||||
) => SemVer {
|
||||
major: *major,
|
||||
minor: *minor,
|
||||
patch: *patch,
|
||||
}
|
||||
.partial_cmp(&Rapid {
|
||||
major: *a,
|
||||
minor: *b,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseVersionError;
|
||||
|
||||
impl fmt::Display for ParseVersionError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "error parsing version")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseVersionError {}
|
||||
|
||||
impl FromStr for Version {
|
||||
type Err = ParseVersionError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Ok(v) = s.parse::<SemVer>() {
|
||||
Ok(v.into())
|
||||
} else if let Ok(v) = s.parse::<Rapid>() {
|
||||
Ok(v.into())
|
||||
} else if let Ok(v) = s.parse::<GitRev>() {
|
||||
Ok(v.into())
|
||||
} else if let Ok(v) = s.parse::<u32>() {
|
||||
Ok(v.into())
|
||||
} else {
|
||||
Err(ParseVersionError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cmp_semver_rapid_gt() {
|
||||
let sem = "1.42.1".parse::<SemVer>().unwrap();
|
||||
let rpd = "1.42".parse::<Rapid>().unwrap();
|
||||
assert!(sem > rpd);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cmp_semver_rapid_eq() {
|
||||
let sem = "1.42.0".parse::<SemVer>().unwrap();
|
||||
let rpd = "1.42".parse::<Rapid>().unwrap();
|
||||
assert!(sem == rpd);
|
||||
}
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
use crate::SemVer;
|
||||
|
||||
use {
|
||||
crate::Version,
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{cmp::Ordering, error::Error, fmt, num::ParseIntError, str::FromStr},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Rapid {
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
}
|
||||
|
||||
impl fmt::Display for Rapid {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}.{}", self.major, self.minor)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Rapid {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match self.major.partial_cmp(&other.major) {
|
||||
Some(Ordering::Greater) => Some(Ordering::Greater),
|
||||
Some(Ordering::Less) => Some(Ordering::Less),
|
||||
Some(Ordering::Equal) => self.minor.partial_cmp(&other.minor),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Rapid {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
match self.major.cmp(&other.major) {
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
Ordering::Less => Ordering::Less,
|
||||
Ordering::Equal => self.minor.cmp(&other.minor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u32> for Rapid {
|
||||
fn eq(&self, other: &u32) -> bool {
|
||||
self.major == *other && self.minor == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<u32> for Rapid {
|
||||
fn partial_cmp(&self, other: &u32) -> Option<Ordering> {
|
||||
match self.major.partial_cmp(other) {
|
||||
Some(Ordering::Greater) => Some(Ordering::Greater),
|
||||
Some(Ordering::Less) => Some(Ordering::Less),
|
||||
None => None,
|
||||
Some(Ordering::Equal) => {
|
||||
if self.minor == 0 {
|
||||
Some(Ordering::Equal)
|
||||
} else {
|
||||
Some(Ordering::Greater)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<SemVer> for Rapid {
|
||||
fn eq(&self, other: &SemVer) -> bool {
|
||||
other.eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<SemVer> for Rapid {
|
||||
fn partial_cmp(&self, other: &SemVer) -> Option<Ordering> {
|
||||
match other.partial_cmp(self) {
|
||||
Some(Ordering::Less) => Some(Ordering::Greater),
|
||||
Some(Ordering::Greater) => Some(Ordering::Less),
|
||||
Some(Ordering::Equal) => Some(Ordering::Equal),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Rapid> for u32 {
|
||||
fn eq(&self, other: &Rapid) -> bool {
|
||||
other.eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<Rapid> for u32 {
|
||||
fn partial_cmp(&self, other: &Rapid) -> Option<Ordering> {
|
||||
match other.partial_cmp(self) {
|
||||
Some(Ordering::Equal) => Some(Ordering::Equal),
|
||||
Some(Ordering::Less) => Some(Ordering::Greater),
|
||||
Some(Ordering::Greater) => Some(Ordering::Less),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ParseRapidError;
|
||||
|
||||
impl fmt::Display for ParseRapidError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Error parsing Rapid version")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseRapidError {}
|
||||
|
||||
impl From<ParseIntError> for ParseRapidError {
|
||||
fn from(_value: ParseIntError) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Rapid {
|
||||
type Err = ParseRapidError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let split = s.split('.').collect::<Vec<_>>();
|
||||
match split.len() {
|
||||
2 => {
|
||||
let major = split.first().unwrap().parse::<u32>()?;
|
||||
let minor = split.get(1).unwrap().parse::<u32>()?;
|
||||
Ok(Self { major, minor })
|
||||
}
|
||||
_ => Err(ParseRapidError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Version> for Rapid {
|
||||
type Error = ParseRapidError;
|
||||
|
||||
fn try_from(value: Version) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Version::SemVer(s) => {
|
||||
if s.patch == 0 {
|
||||
Ok(Self {
|
||||
major: s.major,
|
||||
minor: s.minor,
|
||||
})
|
||||
} else {
|
||||
Err(ParseRapidError)
|
||||
}
|
||||
}
|
||||
Version::Rapid(s) => Ok(s),
|
||||
Version::Number(major) => Ok(Self { major, minor: 0 }),
|
||||
Version::Git(_) => Err(ParseRapidError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_semver() {
|
||||
assert_eq!(
|
||||
"93.0".parse::<Rapid>(),
|
||||
Ok(Rapid {
|
||||
major: 93,
|
||||
minor: 0,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -1,191 +0,0 @@
|
||||
use {
|
||||
crate::{Rapid, Version},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{cmp::Ordering, error::Error, fmt, num::ParseIntError, str::FromStr},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct SemVer {
|
||||
pub major: u32,
|
||||
pub minor: u32,
|
||||
pub patch: u32,
|
||||
}
|
||||
|
||||
impl fmt::Display for SemVer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for SemVer {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match self.major.partial_cmp(&other.major) {
|
||||
Some(Ordering::Greater) => Some(Ordering::Greater),
|
||||
Some(Ordering::Less) => Some(Ordering::Less),
|
||||
None => None,
|
||||
Some(Ordering::Equal) => match self.minor.partial_cmp(&other.minor) {
|
||||
Some(Ordering::Greater) => Some(Ordering::Greater),
|
||||
Some(Ordering::Less) => Some(Ordering::Less),
|
||||
None => None,
|
||||
Some(Ordering::Equal) => self.patch.partial_cmp(&other.patch),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for SemVer {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
match self.major.cmp(&other.major) {
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
Ordering::Less => Ordering::Less,
|
||||
Ordering::Equal => match self.minor.cmp(&other.minor) {
|
||||
Ordering::Greater => Ordering::Greater,
|
||||
Ordering::Less => Ordering::Less,
|
||||
Ordering::Equal => self.patch.cmp(&other.patch),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Rapid> for SemVer {
|
||||
fn eq(&self, other: &Rapid) -> bool {
|
||||
self.major == other.major && self.minor == other.minor && self.patch == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<Rapid> for SemVer {
|
||||
fn partial_cmp(&self, other: &Rapid) -> Option<std::cmp::Ordering> {
|
||||
match self.major.partial_cmp(&other.major) {
|
||||
Some(Ordering::Greater) => Some(Ordering::Greater),
|
||||
Some(Ordering::Less) => Some(Ordering::Less),
|
||||
None => None,
|
||||
Some(Ordering::Equal) => match self.minor.partial_cmp(&other.minor) {
|
||||
Some(Ordering::Greater) => Some(Ordering::Greater),
|
||||
Some(Ordering::Less) => Some(Ordering::Less),
|
||||
None => None,
|
||||
Some(Ordering::Equal) => match self.patch {
|
||||
0 => Some(Ordering::Equal),
|
||||
_ => Some(Ordering::Greater),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<u32> for SemVer {
|
||||
fn eq(&self, other: &u32) -> bool {
|
||||
self.major == *other && self.minor == 0 && self.patch == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<u32> for SemVer {
|
||||
fn partial_cmp(&self, other: &u32) -> Option<Ordering> {
|
||||
match self.major.cmp(other) {
|
||||
Ordering::Greater => Some(Ordering::Greater),
|
||||
Ordering::Less => Some(Ordering::Less),
|
||||
Ordering::Equal => {
|
||||
if self.minor == 0 && self.patch == 0 {
|
||||
Some(Ordering::Equal)
|
||||
} else {
|
||||
Some(Ordering::Greater)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<SemVer> for u32 {
|
||||
fn eq(&self, other: &SemVer) -> bool {
|
||||
other.eq(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd<SemVer> for u32 {
|
||||
fn partial_cmp(&self, other: &SemVer) -> Option<Ordering> {
|
||||
match other.partial_cmp(self) {
|
||||
Some(Ordering::Less) => Some(Ordering::Greater),
|
||||
Some(Ordering::Greater) => Some(Ordering::Less),
|
||||
Some(Ordering::Equal) => Some(Ordering::Equal),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ParseSemVerError;
|
||||
|
||||
impl fmt::Display for ParseSemVerError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Error parsing SemVer")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseSemVerError {}
|
||||
|
||||
impl From<ParseIntError> for ParseSemVerError {
|
||||
fn from(_value: ParseIntError) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SemVer {
|
||||
type Err = ParseSemVerError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let split = s.split('.').collect::<Vec<_>>();
|
||||
match split.len() {
|
||||
3 => {
|
||||
let major = split.first().unwrap().parse::<u32>()?;
|
||||
let minor = split.get(1).unwrap().parse::<u32>()?;
|
||||
let patch = split.get(2).unwrap().parse::<u32>()?;
|
||||
Ok(Self {
|
||||
major,
|
||||
minor,
|
||||
patch,
|
||||
})
|
||||
}
|
||||
_ => Err(ParseSemVerError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Version> for SemVer {
|
||||
type Error = ParseSemVerError;
|
||||
fn try_from(value: Version) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Version::SemVer(s) => Ok(SemVer {
|
||||
major: s.major,
|
||||
minor: s.minor,
|
||||
patch: s.patch,
|
||||
}),
|
||||
Version::Rapid(r) => Ok(SemVer {
|
||||
major: r.major,
|
||||
minor: r.minor,
|
||||
patch: 0,
|
||||
}),
|
||||
Version::Number(n) => Ok(Self {
|
||||
major: n,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
}),
|
||||
Version::Git(_) => Err(ParseSemVerError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_semver() {
|
||||
assert_eq!(
|
||||
"1.0.3".parse::<SemVer>(),
|
||||
Ok(SemVer {
|
||||
major: 1,
|
||||
minor: 0,
|
||||
patch: 3
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
[package]
|
||||
name = "tar"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-only"
|
||||
|
||||
[dependencies]
|
||||
deku = { workspace = true }
|
||||
libc = "0.2"
|
||||
thiserror = "1.0.40"
|
@ -1,8 +0,0 @@
|
||||
## About
|
||||
Derived originally from [minitar](https://github.com/genonullfree/minitar), this
|
||||
crate implements basic functionality for creating and extracting tar archives. It
|
||||
has been adapted to allow creation of a Node (Tar header + 512byte blocks of data)
|
||||
from the raw data plus file metadata. This allows for better efficiency when it
|
||||
is embedded into another application (such as a package manager), as the raw data
|
||||
and metadata about each file can be extracted once and reused for purposes such
|
||||
as generating checksums, getting file sizes and creating packing lists.
|
@ -1,22 +0,0 @@
|
||||
use std::{fmt, io, num::ParseIntError, str::Utf8Error};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("DekuError: {0}")]
|
||||
Deku(#[from] deku::DekuError),
|
||||
#[error("IoError: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("Error in conversion of oct_to_dev")]
|
||||
Utf8Error(#[from] Utf8Error),
|
||||
#[error("Error in conversion of oct_to_dev")]
|
||||
ParseIntError(#[from] ParseIntError),
|
||||
#[error("End of tar")]
|
||||
EndOfTar,
|
||||
#[error("Invalid magic")]
|
||||
InvalidMagic,
|
||||
#[error("Invalid Checksum")]
|
||||
InvalidChecksum,
|
||||
#[error("Parse int failed")]
|
||||
Parse(#[from] fmt::Error),
|
||||
}
|
@ -1,441 +0,0 @@
|
||||
use crate::Error;
|
||||
use deku::prelude::*;
|
||||
use std::{
|
||||
env,
|
||||
ffi::CStr,
|
||||
fmt::{self, Write},
|
||||
fs::{self, Metadata},
|
||||
io,
|
||||
ops::Deref,
|
||||
os::{linux::fs::MetadataExt, unix::fs::FileTypeExt},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum FileType {
|
||||
Normal = 0x30,
|
||||
Hardlink = 0x31,
|
||||
Symlink = 0x32,
|
||||
Char = 0x33,
|
||||
Block = 0x34,
|
||||
Dir = 0x35,
|
||||
FIFO = 0x36,
|
||||
Unknown = 0x00,
|
||||
}
|
||||
|
||||
impl<T> From<T> for FileType
|
||||
where
|
||||
T: Deref<Target = Metadata>,
|
||||
{
|
||||
fn from(meta: T) -> Self {
|
||||
if meta.is_dir() {
|
||||
return FileType::Dir;
|
||||
}
|
||||
|
||||
let file_type = meta.file_type();
|
||||
if file_type.is_fifo() {
|
||||
return FileType::FIFO;
|
||||
} else if file_type.is_char_device() {
|
||||
return FileType::Char;
|
||||
} else if file_type.is_block_device() {
|
||||
return FileType::Block;
|
||||
} else if file_type.is_fifo() {
|
||||
return FileType::FIFO;
|
||||
} else if file_type.is_symlink() {
|
||||
return FileType::Symlink;
|
||||
} else if file_type.is_file() {
|
||||
return FileType::Normal;
|
||||
}
|
||||
|
||||
FileType::Unknown
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Owner {
|
||||
pub uid: u32,
|
||||
pub gid: u32,
|
||||
pub username: String,
|
||||
pub groupname: String,
|
||||
}
|
||||
|
||||
impl Default for Owner {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
uid: 0,
|
||||
gid: 0,
|
||||
username: "root".into(),
|
||||
groupname: "root".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, DekuRead, DekuWrite)]
|
||||
#[deku(endian = "little")]
|
||||
pub struct Header {
|
||||
pub(crate) fname: [u8; 100],
|
||||
pub(crate) mode: [u8; 8],
|
||||
pub(crate) uid: [u8; 8],
|
||||
pub(crate) gid: [u8; 8],
|
||||
pub(crate) size: [u8; 12],
|
||||
pub(crate) mtime: [u8; 12],
|
||||
pub(crate) header_checksum: [u8; 8],
|
||||
pub(crate) link_indicator: [u8; 1],
|
||||
pub(crate) link_name: [u8; 100],
|
||||
pub(crate) ustar_magic: [u8; 6],
|
||||
pub(crate) ustar_version: [u8; 2],
|
||||
pub(crate) username: [u8; 32],
|
||||
pub(crate) groupname: [u8; 32],
|
||||
pub(crate) device_major: [u8; 8],
|
||||
pub(crate) device_minor: [u8; 8],
|
||||
pub(crate) file_prefix: [u8; 155],
|
||||
pub(crate) reserved: [u8; 12],
|
||||
}
|
||||
|
||||
impl Default for Header {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fname: [0; 100],
|
||||
mode: [0; 8],
|
||||
uid: [0; 8],
|
||||
gid: [0; 8],
|
||||
size: [0; 12],
|
||||
mtime: [0; 12],
|
||||
header_checksum: [0x20; 8],
|
||||
link_indicator: [0; 1],
|
||||
link_name: [0; 100],
|
||||
ustar_magic: [0x75, 0x73, 0x74, 0x61, 0x72, 0x20],
|
||||
ustar_version: [0x20, 0x00],
|
||||
username: [0; 32],
|
||||
groupname: [0; 32],
|
||||
device_major: [0; 8],
|
||||
device_minor: [0; 8],
|
||||
file_prefix: [0; 155],
|
||||
reserved: [0; 12],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Header {
|
||||
pub fn filename(&self) -> Result<String, fmt::Error> {
|
||||
let mut s = String::new();
|
||||
for c in self.fname {
|
||||
if c != b'\0' {
|
||||
write!(s, "{c}")?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn mode(&self) -> Result<u32, Error> {
|
||||
let mut s = String::new();
|
||||
for c in self.mode {
|
||||
if c != b'\0' {
|
||||
write!(s, "{c}")?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mode = u32::from_str_radix(&s, 8)?;
|
||||
Ok(mode)
|
||||
}
|
||||
|
||||
fn uid(&self) -> Result<u32, Error> {
|
||||
let mut s = String::new();
|
||||
for c in self.mode {
|
||||
if c != b'\0' {
|
||||
write!(s, "{c}")?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let uid = u32::from_str_radix(&s, 8)?;
|
||||
Ok(uid)
|
||||
}
|
||||
|
||||
fn gid(&self) -> Result<u32, Error> {
|
||||
let mut s = String::new();
|
||||
for c in self.mode {
|
||||
if c != b'\0' {
|
||||
write!(s, "{c}")?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let gid = u32::from_str_radix(&s, 8)?;
|
||||
Ok(gid)
|
||||
}
|
||||
|
||||
fn username(&self) -> Result<String, fmt::Error> {
|
||||
let mut s = String::new();
|
||||
for c in self.username {
|
||||
if c != b'\0' {
|
||||
write!(s, "{c}")?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn groupname(&self) -> Result<String, fmt::Error> {
|
||||
let mut s = String::new();
|
||||
for c in self.groupname {
|
||||
if c != b'\0' {
|
||||
write!(s, "{c}")?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn owner(&self) -> Result<Owner, Error> {
|
||||
let uid = self.uid()?;
|
||||
let gid = self.gid()?;
|
||||
let username = self.username()?;
|
||||
let groupname = self.groupname()?;
|
||||
Ok(Owner {
|
||||
uid,
|
||||
gid,
|
||||
username,
|
||||
groupname,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn prefix(&self) -> Result<String, fmt::Error> {
|
||||
let mut s = String::new();
|
||||
for c in self.file_prefix {
|
||||
if c != b'\0' {
|
||||
write!(s, "{c}")?;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn new(filename: &str) -> Result<Self, Error> {
|
||||
let mut header = Header::default();
|
||||
let meta = fs::symlink_metadata(filename)?;
|
||||
let (filename, prefix) = if filename.len() < 100 {
|
||||
(filename.to_string(), None)
|
||||
} else {
|
||||
// Deal with file names longer than 100 bytes
|
||||
let path = PathBuf::from(&filename);
|
||||
let name = match path.file_name().and_then(|n| n.to_str()) {
|
||||
Some(n) => n.to_string(),
|
||||
None => {
|
||||
return Err(Error::Io(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Cannot get file name",
|
||||
)))
|
||||
}
|
||||
};
|
||||
let dir = match path.parent() {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
return Err(Error::Io(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Cannot get path prefix",
|
||||
)))
|
||||
}
|
||||
};
|
||||
(name, Some(format!("{}", dir.display())))
|
||||
};
|
||||
|
||||
/* Fill in metadata */
|
||||
header.fname[..filename.len()].copy_from_slice(filename.as_bytes());
|
||||
let mode = format!("{:07o}", (meta.st_mode() & 0o777));
|
||||
header.mode[..mode.len()].copy_from_slice(mode.as_bytes());
|
||||
let user = format!("{:07o}", meta.st_uid());
|
||||
header.uid[..user.len()].copy_from_slice(user.as_bytes());
|
||||
let group = format!("{:07o}", meta.st_gid());
|
||||
header.gid[..group.len()].copy_from_slice(group.as_bytes());
|
||||
let size = format!("{:011o}", meta.st_size());
|
||||
header.size[..size.len()].copy_from_slice(size.as_bytes());
|
||||
let mtime = format!("{:011o}", meta.st_mtime());
|
||||
header.mtime[..mtime.len()].copy_from_slice(mtime.as_bytes());
|
||||
if let Some(prefix) = prefix {
|
||||
header.file_prefix[..prefix.len()].copy_from_slice(prefix.as_bytes());
|
||||
}
|
||||
|
||||
/* Get the file type and conditional metadata */
|
||||
header.link_indicator[0] = FileType::from(&meta) as u8;
|
||||
if header.link_indicator[0] == FileType::Symlink as u8 {
|
||||
let link = fs::read_link(filename)?.to_str().unwrap().to_string();
|
||||
header.link_name[..link.len()].copy_from_slice(link.as_bytes());
|
||||
} else if header.link_indicator[0] == FileType::Block as u8 {
|
||||
let major = format!("{:07o}", meta.st_dev());
|
||||
header.device_major[..major.len()].copy_from_slice(major.as_bytes());
|
||||
let minor = format!("{:07o}", meta.st_rdev());
|
||||
header.device_minor[..minor.len()].copy_from_slice(minor.as_bytes());
|
||||
}
|
||||
|
||||
/* TODO: Find better way to get username */
|
||||
let key = "USER";
|
||||
if let Ok(val) = env::var(key) {
|
||||
header.username[..val.len()].copy_from_slice(val.as_bytes())
|
||||
}
|
||||
/* TODO: Find way to get groupname */
|
||||
|
||||
/* Update the header checksum value */
|
||||
header.update_checksum()?;
|
||||
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
pub fn new_from_meta(
|
||||
filename: &str,
|
||||
meta: &Metadata,
|
||||
owner: Option<Owner>,
|
||||
) -> Result<Self, Error> {
|
||||
let mut header = Header::default();
|
||||
let (filename, prefix) = if filename.len() < 100 {
|
||||
(filename.to_string(), None)
|
||||
} else {
|
||||
// Deal with file names longer than 100 bytes
|
||||
let path = PathBuf::from(&filename);
|
||||
let name = match path.file_name().and_then(|n| n.to_str()) {
|
||||
Some(n) => n.to_string(),
|
||||
None => {
|
||||
return Err(Error::Io(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Cannot get file name",
|
||||
)))
|
||||
}
|
||||
};
|
||||
let dir = match path.parent() {
|
||||
Some(d) => d,
|
||||
None => {
|
||||
return Err(Error::Io(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Cannot get path prefix",
|
||||
)))
|
||||
}
|
||||
};
|
||||
(name, Some(format!("{}", dir.display())))
|
||||
};
|
||||
header.fname[..filename.len()].copy_from_slice(filename.as_bytes());
|
||||
let mode = format!("{:07o}", meta.st_mode());
|
||||
header.mode[..mode.len()].copy_from_slice(mode.as_bytes());
|
||||
let owner = match owner {
|
||||
Some(o) => o,
|
||||
None => Owner {
|
||||
uid: meta.st_uid(),
|
||||
gid: meta.st_gid(),
|
||||
username: get_username_for_uid(meta.st_uid())?.into(),
|
||||
groupname: get_groupname_for_gid(meta.st_gid())?.into(),
|
||||
},
|
||||
};
|
||||
let uid = format!("{:07o}", owner.uid);
|
||||
header.uid[..uid.len()].copy_from_slice(uid.as_bytes());
|
||||
let gid = format!("{:07o}", owner.gid);
|
||||
header.gid[..gid.len()].copy_from_slice(gid.as_bytes());
|
||||
let size = format!("{:011o}", meta.len());
|
||||
header.size[..size.len()].copy_from_slice(size.as_bytes());
|
||||
let mtime = format!("{:011o}", meta.st_mtime());
|
||||
header.mtime[..mtime.len()].copy_from_slice(mtime.as_bytes());
|
||||
if let Some(prefix) = prefix {
|
||||
header.file_prefix[..prefix.len()].copy_from_slice(prefix.as_bytes());
|
||||
}
|
||||
header.link_indicator[0] = FileType::from(meta) as u8;
|
||||
if header.link_indicator[0] == FileType::Symlink as u8 {
|
||||
let link = fs::read_link(filename)?.to_str().unwrap().to_string();
|
||||
header.link_name[..link.len()].copy_from_slice(link.as_bytes());
|
||||
} else if header.link_indicator[0] == FileType::Block as u8 {
|
||||
let major = format!("{:07o}", meta.st_dev());
|
||||
header.device_major[..major.len()].copy_from_slice(major.as_bytes());
|
||||
let minor = format!("{:07o}", meta.st_rdev());
|
||||
header.device_minor[..minor.len()].copy_from_slice(minor.as_bytes());
|
||||
}
|
||||
header.username[..owner.username.len()].copy_from_slice(owner.username.as_bytes());
|
||||
header.groupname[..owner.groupname.len()].copy_from_slice(owner.groupname.as_bytes());
|
||||
header.update_checksum()?;
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
/// Validates that the magic value received matches the magic value required in the Tar specification.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tar::tar::Header;
|
||||
/// let header = Header::default();
|
||||
/// if !header.validate_magic() {
|
||||
/// println!("Magic value is invalid");
|
||||
/// }
|
||||
/// ```
|
||||
pub fn validate_magic(self) -> bool {
|
||||
self.ustar_magic == "ustar ".as_bytes()
|
||||
}
|
||||
|
||||
/// Validates the header checksum computes to the expected value.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tar::tar::Header;
|
||||
/// let header = Header::default();
|
||||
/// if header.validate_checksum().unwrap() {
|
||||
/// println!("Checksum is valid");
|
||||
/// }
|
||||
/// ```
|
||||
pub fn validate_checksum(self) -> Result<bool, Error> {
|
||||
let mut test = self;
|
||||
let mut new = [0x20u8; 8];
|
||||
test.header_checksum.copy_from_slice(&[0x20; 8]);
|
||||
|
||||
let tmp = format!("{:06o}\x00", test.calc_checksum()?);
|
||||
new[..tmp.len()].copy_from_slice(tmp.as_bytes());
|
||||
|
||||
Ok(self.header_checksum == new)
|
||||
}
|
||||
|
||||
/// Updates the header checksum value.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tar::tar::Header;
|
||||
/// let mut header = Header::default();
|
||||
///
|
||||
/// /* Fill in header information */
|
||||
///
|
||||
/// header.update_checksum();
|
||||
/// ```
|
||||
pub fn update_checksum(&mut self) -> Result<(), Error> {
|
||||
let checksum = format!("{:06o}\x00", self.calc_checksum()?);
|
||||
self.header_checksum[..checksum.len()].copy_from_slice(checksum.as_bytes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calc_checksum(self) -> Result<usize, Error> {
|
||||
let out = self.to_bytes()?;
|
||||
let mut checksum = 0;
|
||||
for i in out {
|
||||
checksum += i as usize;
|
||||
}
|
||||
Ok(checksum)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_username_for_uid<'a>(uid: u32) -> Result<&'a str, std::str::Utf8Error> {
|
||||
let user = unsafe {
|
||||
let pw = libc::getpwuid(uid);
|
||||
let name = (*pw).pw_name;
|
||||
CStr::from_ptr(name)
|
||||
};
|
||||
user.to_str()
|
||||
}
|
||||
|
||||
pub fn get_groupname_for_gid<'a>(gid: u32) -> Result<&'a str, std::str::Utf8Error> {
|
||||
let group = unsafe {
|
||||
let gr = libc::getgrgid(gid);
|
||||
let name = (*gr).gr_name;
|
||||
CStr::from_ptr(name)
|
||||
};
|
||||
group.to_str()
|
||||
}
|
134
tar/src/lib.rs
134
tar/src/lib.rs
@ -1,134 +0,0 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufReader, Write},
|
||||
};
|
||||
|
||||
mod error;
|
||||
mod header;
|
||||
mod node;
|
||||
pub use {
|
||||
error::Error,
|
||||
header::{FileType, Header, Owner},
|
||||
node::Node,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Archive {
|
||||
pub nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
impl Archive {
|
||||
pub fn to_vec(self) -> Result<Vec<u8>, Error> {
|
||||
let mut buf = vec![];
|
||||
for node in self.nodes {
|
||||
buf.extend(node.to_vec()?);
|
||||
}
|
||||
buf.write_all(&[0; 9216])?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Write out a vector of `TarNodes` to a file or something that implements ``std::io::Write`` and ``std::io::Copy``.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use std::fs::File;
|
||||
/// use tar::tar::TarFile;
|
||||
///
|
||||
/// let data = Archive::new("test/1.txt".to_string()).unwrap();
|
||||
///
|
||||
/// let out = File::create("test/2.tar".to_string()).unwrap();
|
||||
/// data.write(&out).unwrap();
|
||||
/// ```
|
||||
pub fn write<T: io::Write + Copy>(self, mut input: T) -> Result<usize, Error> {
|
||||
let mut written = 0;
|
||||
for f in self.nodes.clone() {
|
||||
written += f.write(input)?;
|
||||
}
|
||||
|
||||
/* Complete the write with 18 blocks of 512 ``0x00`` bytes per the specification */
|
||||
if !self.nodes.is_empty() {
|
||||
input.write_all(&[0; 9216])?;
|
||||
written += 9216;
|
||||
}
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
/// Create a new `TarFile` struct and initialize it with a `filename` file. This will read in the file to
|
||||
/// the `TarFile` struct as a `TarNode`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tar::Archive;
|
||||
///
|
||||
/// let data = Archive::new("test/1.txt".to_string()).unwrap();
|
||||
/// ```
|
||||
pub fn new(filename: &str) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
nodes: vec![Node::read_file_to_tar(filename)?],
|
||||
})
|
||||
}
|
||||
|
||||
/// Append another file to the `TarFile.file` vector. This adds a file to the internal representation of the tar file.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tar::Archive;
|
||||
///
|
||||
/// let mut data = Archive::new("test/1.txt".to_string()).unwrap();
|
||||
/// data.append("test/1.txt".to_string()).unwrap();
|
||||
/// ```
|
||||
pub fn append(&mut self, filename: &str) -> Result<(), Error> {
|
||||
self.nodes.push(Node::read_file_to_tar(filename)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Open and load an external tar file into the internal `TarFile` struct. This parses and loads up all the files
|
||||
/// contained within the external tar file.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use tar::Archive;
|
||||
///
|
||||
/// Archive::open("test/1.tar".to_string()).unwrap();
|
||||
/// ```
|
||||
pub fn open(filename: String) -> Result<Self, Error> {
|
||||
let file = File::open(&filename)?;
|
||||
let mut reader = BufReader::new(file);
|
||||
let mut out = Self {
|
||||
nodes: Vec::<Node>::new(),
|
||||
};
|
||||
|
||||
while let Ok(t) = Node::read(&mut reader) {
|
||||
out.nodes.push(t);
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Remove the first file from the Tar that matches the filename and path.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use minitar::tar::TarFile;
|
||||
///
|
||||
/// let mut data = TarFile::new("test/1.tar".to_string()).unwrap();
|
||||
/// data.remove("test/1.tar".to_string()).unwrap();
|
||||
/// ```
|
||||
pub fn remove(&mut self, filename: String) -> Result<bool, Error> {
|
||||
let mut name = [0u8; 100];
|
||||
name[..filename.len()].copy_from_slice(filename.as_bytes());
|
||||
if let Some(i) = &self.nodes.iter().position(|x| x.header.fname == name) {
|
||||
self.nodes.remove(*i);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
}
|
137
tar/src/node.rs
137
tar/src/node.rs
@ -1,137 +0,0 @@
|
||||
use crate::{header::Owner, Error, FileType, Header};
|
||||
use deku::prelude::*;
|
||||
use std::{
|
||||
fs::{File, Metadata},
|
||||
io::{self, BufReader},
|
||||
str,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Node {
|
||||
pub header: Header,
|
||||
pub data: Vec<[u8; 512]>,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn to_vec(self) -> Result<Vec<u8>, DekuError> {
|
||||
let mut buf = self.header.to_bytes()?;
|
||||
for block in self.data {
|
||||
buf.extend(block.to_vec());
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Write out a single file within the tar to a file or something with a ``std::io::Write`` trait.
|
||||
pub fn write<T: io::Write>(self, mut input: T) -> Result<usize, Error> {
|
||||
input.write_all(&self.header.to_bytes()?)?;
|
||||
let mut written = 512;
|
||||
for d in self.data {
|
||||
input.write_all(&d)?;
|
||||
written += d.len();
|
||||
}
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
/// Read a TarNode in from a file or something with a ``std::io::Read`` trait.
|
||||
pub fn read<T: io::Read>(mut input: T) -> Result<Self, Error> {
|
||||
let mut h = vec![0u8; 512];
|
||||
input.read_exact(&mut h)?;
|
||||
|
||||
let (_, header) = Header::from_bytes((&h, 0))?;
|
||||
if header == Header::default() {
|
||||
return Err(Error::EndOfTar);
|
||||
}
|
||||
if !header.validate_magic() {
|
||||
return Err(Error::InvalidMagic);
|
||||
}
|
||||
if !header.validate_checksum()? {
|
||||
return Err(Error::InvalidChecksum);
|
||||
}
|
||||
|
||||
let chunks = (oct_to_dec(&header.size)? / 512) + 1;
|
||||
Ok(Node {
|
||||
header,
|
||||
data: Node::chunk_file(&mut input, Some(chunks))?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Open and read a file from the ``filename`` argument to a TarNode.
|
||||
pub fn read_file_to_tar(filename: &str) -> Result<Self, Error> {
|
||||
let header = Header::new(filename)?;
|
||||
if header.link_indicator[0] != FileType::Normal as u8 {
|
||||
return Ok(Node {
|
||||
header,
|
||||
data: Vec::<[u8; 512]>::new(),
|
||||
});
|
||||
}
|
||||
|
||||
let file = File::open(filename)?;
|
||||
let mut reader = BufReader::new(file);
|
||||
Ok(Node {
|
||||
header,
|
||||
data: Node::chunk_file(&mut reader, None)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a Node from in memory data, given the filename and metadata
|
||||
pub fn read_data_to_tar(
|
||||
data: &[u8],
|
||||
filename: &str,
|
||||
meta: &Metadata,
|
||||
owner: Option<Owner>,
|
||||
) -> Result<Self, Error> {
|
||||
let header = Header::new_from_meta(filename, meta, owner)?;
|
||||
if header.link_indicator[0] != FileType::Normal as u8 {
|
||||
return Ok(Node {
|
||||
header,
|
||||
data: Vec::<[u8; 512]>::new(),
|
||||
});
|
||||
}
|
||||
let mut reader = BufReader::new(data);
|
||||
Ok(Node {
|
||||
header,
|
||||
data: Node::chunk_file(&mut reader, None)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Read in and split a file into ``512`` byte chunks.
|
||||
fn chunk_file<T: std::io::Read>(
|
||||
file: &mut T,
|
||||
max_chunks: Option<usize>,
|
||||
) -> Result<Vec<[u8; 512]>, Error> {
|
||||
/* Extract the file data from the tar file */
|
||||
let mut out = Vec::<[u8; 512]>::new();
|
||||
let mut n = if let Some(max) = max_chunks {
|
||||
max
|
||||
} else {
|
||||
usize::MAX
|
||||
};
|
||||
|
||||
/* Carve out 512 bytes at a time */
|
||||
let mut buf: [u8; 512] = [0; 512];
|
||||
loop {
|
||||
let len = file.read(&mut buf)?;
|
||||
|
||||
n -= 1;
|
||||
|
||||
/* If read len == 0, we've hit the EOF */
|
||||
if len == 0 || n == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Save this chunk */
|
||||
out.push(buf);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
fn oct_to_dec(input: &[u8]) -> Result<usize, Error> {
|
||||
/* Convert the &[u8] to string and remove the null byte */
|
||||
let mut s = str::from_utf8(input)?.to_string();
|
||||
s.pop();
|
||||
|
||||
/* Convert to usize from octal */
|
||||
Ok(usize::from_str_radix(&s, 8)?)
|
||||
}
|
Loading…
Reference in New Issue
Block a user