mirror of
https://github.com/Drop-OSS/droplet.git
synced 2026-06-22 04:11:40 +10:00
fix: use droplet-rs crate
This commit is contained in:
Generated
+321
-355
File diff suppressed because it is too large
Load Diff
+9
-16
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "droplet"
|
name = "droplet"
|
||||||
version = "0.7.0"
|
version = "0.3.5"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
description = "Droplet is a `napi.rs` Rust/Node.js package full of high-performance and low-level utils for Drop"
|
description = "Droplet is a `napi.rs` Rust/Node.js package full of high-performance and low-level utils for Drop"
|
||||||
[lib]
|
[lib]
|
||||||
@@ -9,30 +9,23 @@ crate-type = ["cdylib"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
|
# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix
|
||||||
napi = { version = "3.0.0-beta.11", default-features = false, features = ["napi6", "async", "web_stream", "error_anyhow"] }
|
napi = { version = "3.0.0-beta.11", default-features = false, features = [
|
||||||
|
"napi6",
|
||||||
|
"async",
|
||||||
|
"web_stream",
|
||||||
|
"error_anyhow",
|
||||||
|
] }
|
||||||
napi-derive = "3.0.0-beta.11"
|
napi-derive = "3.0.0-beta.11"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
md5 = "0.7.0"
|
md5 = "0.7.0"
|
||||||
time-macros = "0.2.22"
|
|
||||||
time = "0.3.41"
|
|
||||||
webpki = "0.22.4"
|
|
||||||
ring = "0.17.14"
|
|
||||||
tokio = { version = "1.45.1", features = ["fs", "io-util"] }
|
tokio = { version = "1.45.1", features = ["fs", "io-util"] }
|
||||||
tokio-util = { version = "0.7.15", features = ["codec"] }
|
tokio-util = { version = "0.7.15", features = ["codec"] }
|
||||||
dyn-clone = "1.0.20"
|
|
||||||
rhai = "1.22.2"
|
rhai = "1.22.2"
|
||||||
# mlua = { version = "0.11.2", features = ["luajit"] }
|
# mlua = { version = "0.11.2", features = ["luajit"] }
|
||||||
boa_engine = "0.20.0"
|
boa_engine = "0.20.0"
|
||||||
serde_json = "1.0.143"
|
serde_json = "1.0.143"
|
||||||
anyhow = "1.0.99"
|
anyhow = "*"
|
||||||
|
droplet-rs = { git = "https://github.com/Drop-OSS/droplet-rs.git" }
|
||||||
[dependencies.x509-parser]
|
|
||||||
version = "0.17.0"
|
|
||||||
features = ["verify"]
|
|
||||||
|
|
||||||
[dependencies.rcgen]
|
|
||||||
version = "0.13.2"
|
|
||||||
features = ["crypto", "pem", "x509-parser"]
|
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1.0.210"
|
version = "1.0.210"
|
||||||
|
|||||||
Vendored
+1
-2
@@ -1,7 +1,6 @@
|
|||||||
/* auto-generated by NAPI-RS */
|
/* auto-generated by NAPI-RS */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
/**
|
/** * Persistent object so we can cache things between commands
|
||||||
* Persistent object so we can cache things between commands
|
|
||||||
*/
|
*/
|
||||||
export declare class DropletHandler {
|
export declare class DropletHandler {
|
||||||
constructor()
|
constructor()
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@drop-oss/droplet",
|
"name": "@drop-oss/droplet",
|
||||||
"version": "3.5.0",
|
"version": "3.5.1",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"napi": {
|
"napi": {
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
use std::{collections::HashMap, sync::Arc, thread};
|
use std::{collections::HashMap, sync::Arc, thread};
|
||||||
|
|
||||||
|
use droplet_rs::versions::types::VersionBackend;
|
||||||
use napi::{
|
use napi::{
|
||||||
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
|
threadsafe_function::{ThreadsafeFunction, ThreadsafeFunctionCallMode},
|
||||||
Result,
|
Result,
|
||||||
@@ -7,7 +8,7 @@ use napi::{
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::version::{types::VersionBackend, utils::DropletHandler};
|
use crate::version::DropletHandler;
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 1024 * 1024 * 64;
|
const CHUNK_SIZE: usize = 1024 * 1024 * 64;
|
||||||
|
|
||||||
|
|||||||
+6
-106
@@ -1,129 +1,29 @@
|
|||||||
use anyhow::anyhow;
|
|
||||||
use rcgen::{
|
|
||||||
CertificateParams, DistinguishedName, IsCa, KeyPair, KeyUsagePurpose, PublicKeyData,
|
|
||||||
SubjectPublicKeyInfo,
|
|
||||||
};
|
|
||||||
use ring::rand::SystemRandom;
|
|
||||||
use ring::signature::{EcdsaKeyPair, VerificationAlgorithm};
|
|
||||||
use time::{Duration, OffsetDateTime};
|
|
||||||
use x509_parser::parse_x509_certificate;
|
|
||||||
use x509_parser::pem::Pem;
|
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn generate_root_ca() -> anyhow::Result<Vec<String>> {
|
pub fn generate_root_ca() -> anyhow::Result<Vec<String>> {
|
||||||
let mut params = CertificateParams::default();
|
Ok(droplet_rs::ssl::generate_root_ca()?)
|
||||||
|
|
||||||
let mut name = DistinguishedName::new();
|
|
||||||
name.push(rcgen::DnType::CommonName, "Drop Root Server");
|
|
||||||
name.push(rcgen::DnType::OrganizationName, "Drop");
|
|
||||||
|
|
||||||
params.distinguished_name = name;
|
|
||||||
|
|
||||||
params.not_before = OffsetDateTime::now_utc();
|
|
||||||
params.not_after = OffsetDateTime::now_utc()
|
|
||||||
.checked_add(Duration::days(365 * 1000))
|
|
||||||
.ok_or(anyhow!("failed to calculate end date"))?;
|
|
||||||
|
|
||||||
params.is_ca = IsCa::Ca(rcgen::BasicConstraints::Unconstrained);
|
|
||||||
|
|
||||||
params.key_usages = vec![
|
|
||||||
KeyUsagePurpose::CrlSign,
|
|
||||||
KeyUsagePurpose::KeyCertSign,
|
|
||||||
KeyUsagePurpose::DigitalSignature,
|
|
||||||
];
|
|
||||||
|
|
||||||
let key_pair = KeyPair::generate()?;
|
|
||||||
let certificate = CertificateParams::self_signed(params, &key_pair)?;
|
|
||||||
|
|
||||||
// Returns certificate, then private key
|
|
||||||
Ok(vec![certificate.pem(), key_pair.serialize_pem()])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn generate_client_certificate(
|
pub fn generate_client_certificate(
|
||||||
client_id: String,
|
client_id: String,
|
||||||
_client_name: String,
|
client_name: String,
|
||||||
root_ca: String,
|
root_ca: String,
|
||||||
root_ca_private: String,
|
root_ca_private: String,
|
||||||
) -> anyhow::Result<Vec<String>> {
|
) -> anyhow::Result<Vec<String>> {
|
||||||
let root_key_pair = KeyPair::from_pem(&root_ca_private)?;
|
Ok(droplet_rs::ssl::generate_client_certificate(client_id, client_name, root_ca, root_ca_private)?)
|
||||||
let certificate_params = CertificateParams::from_ca_cert_pem(&root_ca)?;
|
|
||||||
let root_ca = CertificateParams::self_signed(certificate_params, &root_key_pair)?;
|
|
||||||
|
|
||||||
let mut params = CertificateParams::default();
|
|
||||||
|
|
||||||
let mut name = DistinguishedName::new();
|
|
||||||
name.push(rcgen::DnType::CommonName, client_id);
|
|
||||||
name.push(rcgen::DnType::OrganizationName, "Drop");
|
|
||||||
params.distinguished_name = name;
|
|
||||||
|
|
||||||
params.key_usages = vec![
|
|
||||||
KeyUsagePurpose::DigitalSignature,
|
|
||||||
KeyUsagePurpose::DataEncipherment,
|
|
||||||
];
|
|
||||||
|
|
||||||
let key_pair = KeyPair::generate_for(&rcgen::PKCS_ECDSA_P384_SHA384)?;
|
|
||||||
let certificate = CertificateParams::signed_by(params, &key_pair, &root_ca, &root_key_pair)?;
|
|
||||||
|
|
||||||
// Returns certificate, then private key
|
|
||||||
Ok(vec![certificate.pem(), key_pair.serialize_pem()])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn verify_client_certificate(client_cert: String, root_ca: String) -> anyhow::Result<bool> {
|
pub fn verify_client_certificate(client_cert: String, root_ca: String) -> anyhow::Result<bool> {
|
||||||
let root_ca = Pem::iter_from_buffer(root_ca.as_bytes())
|
Ok(droplet_rs::ssl::verify_client_certificate(client_cert, root_ca)?)
|
||||||
.next()
|
|
||||||
.ok_or(anyhow!("no certificates in root ca"))??;
|
|
||||||
let root_ca = root_ca.parse_x509()?;
|
|
||||||
|
|
||||||
let client_cert = Pem::iter_from_buffer(client_cert.as_bytes())
|
|
||||||
.next()
|
|
||||||
.ok_or(anyhow!("No client certs in chain."))??;
|
|
||||||
let client_cert = client_cert.parse_x509()?;
|
|
||||||
|
|
||||||
let valid = root_ca
|
|
||||||
.verify_signature(Some(client_cert.public_key()))
|
|
||||||
.is_ok();
|
|
||||||
|
|
||||||
Ok(valid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn sign_nonce(private_key: String, nonce: String) -> anyhow::Result<String> {
|
pub fn sign_nonce(private_key: String, nonce: String) -> anyhow::Result<String> {
|
||||||
let rng = SystemRandom::new();
|
Ok(droplet_rs::ssl::sign_nonce(private_key, nonce)?)
|
||||||
|
|
||||||
let key_pair = KeyPair::from_pem(&private_key)?;
|
|
||||||
|
|
||||||
let key_pair = EcdsaKeyPair::from_pkcs8(
|
|
||||||
&ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING,
|
|
||||||
&key_pair.serialize_der(),
|
|
||||||
&rng,
|
|
||||||
)
|
|
||||||
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
|
|
||||||
|
|
||||||
let signature = key_pair
|
|
||||||
.sign(&rng, nonce.as_bytes())
|
|
||||||
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
|
|
||||||
let hex_signature = hex::encode(signature);
|
|
||||||
|
|
||||||
Ok(hex_signature)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[napi]
|
#[napi]
|
||||||
pub fn verify_nonce(public_cert: String, nonce: String, signature: String) -> anyhow::Result<bool> {
|
pub fn verify_nonce(public_cert: String, nonce: String, signature: String) -> anyhow::Result<bool> {
|
||||||
let (_, pem) = x509_parser::pem::parse_x509_pem(public_cert.as_bytes())?;
|
Ok(droplet_rs::ssl::verify_nonce(public_cert, nonce, signature)?)
|
||||||
let (_, spki) = parse_x509_certificate(&pem.contents)?;
|
|
||||||
let public_key = SubjectPublicKeyInfo::from_der(spki.public_key().raw)?;
|
|
||||||
|
|
||||||
let raw_signature = hex::decode(signature)?;
|
|
||||||
|
|
||||||
let valid = ring::signature::ECDSA_P384_SHA384_FIXED
|
|
||||||
.verify(
|
|
||||||
public_key.der_bytes().into(),
|
|
||||||
nonce.as_bytes().into(),
|
|
||||||
raw_signature[..].into(),
|
|
||||||
)
|
|
||||||
.is_ok();
|
|
||||||
|
|
||||||
Ok(valid)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,60 +6,10 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
use droplet_rs::versions::{create_backend_constructor, types::{ReadToAsyncRead, VersionBackend, VersionFile}};
|
||||||
use napi::{bindgen_prelude::*, sys::napi_value__, tokio_stream::StreamExt};
|
use napi::{bindgen_prelude::*, sys::napi_value__, tokio_stream::StreamExt};
|
||||||
use tokio_util::codec::{BytesCodec, FramedRead};
|
use tokio_util::codec::{BytesCodec, FramedRead};
|
||||||
|
|
||||||
use crate::version::{
|
|
||||||
backends::{
|
|
||||||
PathVersionBackend, ZipVersionBackend, SEVEN_ZIP_INSTALLED, SUPPORTED_FILE_EXTENSIONS,
|
|
||||||
},
|
|
||||||
types::{ReadToAsyncRead, VersionBackend, VersionFile},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append new backends here
|
|
||||||
*/
|
|
||||||
pub fn create_backend_constructor<'a>(
|
|
||||||
path: &Path,
|
|
||||||
) -> Option<Box<dyn FnOnce() -> Result<Box<dyn VersionBackend + Send + 'a>>>> {
|
|
||||||
if !path.exists() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let is_directory = path.is_dir();
|
|
||||||
if is_directory {
|
|
||||||
let base_dir = path.to_path_buf();
|
|
||||||
return Some(Box::new(move || {
|
|
||||||
Ok(Box::new(PathVersionBackend { base_dir }))
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
if *SEVEN_ZIP_INSTALLED {
|
|
||||||
/*
|
|
||||||
Slow 7zip integrity test
|
|
||||||
let mut test = Command::new("7z");
|
|
||||||
test.args(vec!["t", path.to_str().expect("invalid utf path")]);
|
|
||||||
let status = test.status().ok()?;
|
|
||||||
if status.code().unwrap_or(1) == 0 {
|
|
||||||
let buf = path.to_path_buf();
|
|
||||||
return Some(Box::new(move || Ok(Box::new(ZipVersionBackend::new(buf)?))));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// Fast filename-based test
|
|
||||||
if let Some(extension) = path.extension().and_then(|v| v.to_str()) {
|
|
||||||
let supported = SUPPORTED_FILE_EXTENSIONS
|
|
||||||
.iter()
|
|
||||||
.find(|v| ***v == *extension)
|
|
||||||
.is_some();
|
|
||||||
if supported {
|
|
||||||
let buf = path.to_path_buf();
|
|
||||||
return Some(Box::new(move || Ok(Box::new(ZipVersionBackend::new(buf)?))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persistent object so we can cache things between commands
|
* Persistent object so we can cache things between commands
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
#[cfg(unix)]
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
use std::{
|
|
||||||
cell::LazyCell,
|
|
||||||
fs::{self, metadata, File},
|
|
||||||
io::{self, BufRead, BufReader, Read, Seek, SeekFrom, Sink},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
process::{Child, ChildStdout, Command, Stdio},
|
|
||||||
sync::{Arc, LazyLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::anyhow;
|
|
||||||
|
|
||||||
use crate::version::types::{MinimumFileObject, VersionBackend, VersionFile};
|
|
||||||
|
|
||||||
pub fn _list_files(vec: &mut Vec<PathBuf>, path: &Path) -> napi::Result<()> {
|
|
||||||
if metadata(path)?.is_dir() {
|
|
||||||
let paths = fs::read_dir(path)?;
|
|
||||||
for path_result in paths {
|
|
||||||
let full_path = path_result?.path();
|
|
||||||
if metadata(&full_path)?.is_dir() {
|
|
||||||
_list_files(vec, &full_path)?;
|
|
||||||
} else {
|
|
||||||
vec.push(full_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PathVersionBackend {
|
|
||||||
pub base_dir: PathBuf,
|
|
||||||
}
|
|
||||||
impl VersionBackend for PathVersionBackend {
|
|
||||||
fn list_files(&mut self) -> anyhow::Result<Vec<VersionFile>> {
|
|
||||||
let mut vec = Vec::new();
|
|
||||||
_list_files(&mut vec, &self.base_dir)?;
|
|
||||||
|
|
||||||
let mut results = Vec::new();
|
|
||||||
|
|
||||||
for pathbuf in vec.iter() {
|
|
||||||
let relative = pathbuf.strip_prefix(self.base_dir.clone())?;
|
|
||||||
|
|
||||||
results.push(
|
|
||||||
self.peek_file(
|
|
||||||
relative
|
|
||||||
.to_str()
|
|
||||||
.ok_or(napi::Error::from_reason("Could not parse path"))?
|
|
||||||
.to_owned(),
|
|
||||||
)?,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reader(
|
|
||||||
&mut self,
|
|
||||||
file: &VersionFile,
|
|
||||||
start: u64,
|
|
||||||
end: u64,
|
|
||||||
) -> anyhow::Result<Box<dyn MinimumFileObject + 'static>> {
|
|
||||||
let mut file = File::open(self.base_dir.join(file.relative_filename.clone()))?;
|
|
||||||
|
|
||||||
if start != 0 {
|
|
||||||
file.seek(SeekFrom::Start(start))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if end != 0 {
|
|
||||||
return Ok(Box::new(file.take(end - start)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Box::new(file))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn peek_file(&mut self, sub_path: String) -> anyhow::Result<VersionFile> {
|
|
||||||
let pathbuf = self.base_dir.join(sub_path.clone());
|
|
||||||
if !pathbuf.exists() {
|
|
||||||
return Err(anyhow!("Path doesn't exist."));
|
|
||||||
};
|
|
||||||
|
|
||||||
let file = File::open(pathbuf.clone())?;
|
|
||||||
let metadata = file.try_clone()?.metadata()?;
|
|
||||||
let permission_object = metadata.permissions();
|
|
||||||
let permissions = {
|
|
||||||
let perm: u32;
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
perm = permission_object.mode();
|
|
||||||
}
|
|
||||||
#[cfg(not(target_family = "unix"))]
|
|
||||||
{
|
|
||||||
perm = 0
|
|
||||||
}
|
|
||||||
perm
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(VersionFile {
|
|
||||||
relative_filename: sub_path,
|
|
||||||
permission: permissions,
|
|
||||||
size: metadata.len(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn require_whole_files(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static SEVEN_ZIP_INSTALLED: LazyLock<bool> =
|
|
||||||
LazyLock::new(|| Command::new("7z").output().is_ok());
|
|
||||||
// https://7-zip.opensource.jp/chm/general/formats.htm
|
|
||||||
// Intentionally repeated some because it's a trivial cost and it's easier to directly copy from the docs above
|
|
||||||
pub const SUPPORTED_FILE_EXTENSIONS: [&str; 89] = [
|
|
||||||
"7z", "bz2", "bzip2", "tbz2", "tbz", "gz", "gzip", "tgz", "tar", "wim", "swm", "esd", "xz",
|
|
||||||
"txz", "zip", "zipx", "jar", "xpi", "odt", "ods", "docx", "xlsx", "epub", "apm", "ar", "a",
|
|
||||||
"deb", "lib", "arj", "cab", "chm", "chw", "chi", "chq", "msi", "msp", "doc", "xls", "ppt",
|
|
||||||
"cpio", "cramfs", "dmg", "ext", "ext2", "ext3", "ext4", "img", "fat", "img", "hfs", "hfsx",
|
|
||||||
"hxs", "hxr", "hxq", "hxw", "lit", "ihex", "iso", "img", "lzh", "lha", "lzma", "mbr", "mslz",
|
|
||||||
"mub", "nsis", "ntfs", "img", "mbr", "rar", "r00", "rpm", "ppmd", "qcow", "qcow2", "qcow2c",
|
|
||||||
"squashfs", "udf", "iso", "img", "scap", "uefif", "vdi", "vhd", "vmdk", "xar", "pkg", "z", "taz",
|
|
||||||
];
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ZipVersionBackend {
|
|
||||||
path: String,
|
|
||||||
}
|
|
||||||
impl ZipVersionBackend {
|
|
||||||
pub fn new(path: PathBuf) -> anyhow::Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
path: path.to_str().expect("invalid utf path").to_owned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ZipFileWrapper {
|
|
||||||
command: Child,
|
|
||||||
reader: BufReader<ChildStdout>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ZipFileWrapper {
|
|
||||||
pub fn new(mut command: Child) -> Self {
|
|
||||||
let stdout = command
|
|
||||||
.stdout
|
|
||||||
.take()
|
|
||||||
.expect("failed to access stdout of 7z");
|
|
||||||
let reader = BufReader::new(stdout);
|
|
||||||
ZipFileWrapper { command, reader }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This read implemention is a result of debugging hell
|
|
||||||
* It should probably be replaced with a .take() call.
|
|
||||||
*/
|
|
||||||
impl Read for ZipFileWrapper {
|
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
||||||
self.reader.read(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for ZipFileWrapper {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.command.wait().expect("failed to wait for 7z exit");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VersionBackend for ZipVersionBackend {
|
|
||||||
fn list_files(&mut self) -> anyhow::Result<Vec<VersionFile>> {
|
|
||||||
let mut list_command = Command::new("7z");
|
|
||||||
list_command.args(vec!["l", "-ba", &self.path]);
|
|
||||||
let result = list_command.output()?;
|
|
||||||
if !result.status.success() {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"failed to list files: code {:?}",
|
|
||||||
result.status.code()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let raw_result = String::from_utf8(result.stdout)?;
|
|
||||||
let files = raw_result
|
|
||||||
.split("\n")
|
|
||||||
.filter(|v| v.len() > 0)
|
|
||||||
.map(|v| v.split(" ").filter(|v| v.len() > 0));
|
|
||||||
let mut results = Vec::new();
|
|
||||||
|
|
||||||
for file in files {
|
|
||||||
let values = file.collect::<Vec<&str>>();
|
|
||||||
let mut iter = values.iter();
|
|
||||||
let (date, time, attrs, size, compress, name) = (
|
|
||||||
iter.next().expect("failed to read date"),
|
|
||||||
iter.next().expect("failed to read time"),
|
|
||||||
iter.next().expect("failed to read attrs"),
|
|
||||||
iter.next().expect("failed to read size"),
|
|
||||||
iter.next().expect("failed to read compress"),
|
|
||||||
iter.collect::<Vec<&&str>>(),
|
|
||||||
);
|
|
||||||
if attrs.starts_with("D") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
results.push(VersionFile {
|
|
||||||
relative_filename: name
|
|
||||||
.into_iter()
|
|
||||||
.map(|v| *v)
|
|
||||||
.fold(String::new(), |a, b| a + b + " ")
|
|
||||||
.trim_end()
|
|
||||||
.to_owned(),
|
|
||||||
permission: 0o744, // owner r/w/x, everyone else, read
|
|
||||||
size: size.parse().unwrap(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(results)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reader(
|
|
||||||
&mut self,
|
|
||||||
file: &VersionFile,
|
|
||||||
start: u64,
|
|
||||||
end: u64,
|
|
||||||
) -> anyhow::Result<Box<dyn MinimumFileObject + '_>> {
|
|
||||||
let mut read_command = Command::new("7z");
|
|
||||||
read_command.args(vec!["e", "-so", &self.path, &file.relative_filename]);
|
|
||||||
let output = read_command
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.spawn()
|
|
||||||
.expect("failed to spawn 7z");
|
|
||||||
Ok(Box::new(ZipFileWrapper::new(output)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn peek_file(&mut self, sub_path: String) -> anyhow::Result<VersionFile> {
|
|
||||||
let files = self.list_files()?;
|
|
||||||
let file = files
|
|
||||||
.iter()
|
|
||||||
.find(|v| v.relative_filename == sub_path)
|
|
||||||
.expect("file not found");
|
|
||||||
|
|
||||||
Ok(file.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn require_whole_files(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
pub mod utils;
|
|
||||||
pub mod types;
|
|
||||||
pub mod backends;
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
use std::{fmt::Debug, io::Read};
|
|
||||||
|
|
||||||
use dyn_clone::DynClone;
|
|
||||||
use tokio::io::{self, AsyncRead};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct VersionFile {
|
|
||||||
pub relative_filename: String,
|
|
||||||
pub permission: u32,
|
|
||||||
pub size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait MinimumFileObject: Read + Send {}
|
|
||||||
impl<T: Read + Send> MinimumFileObject for T {}
|
|
||||||
|
|
||||||
// Intentionally not a generic, because of types in read_file
|
|
||||||
pub struct ReadToAsyncRead<'a> {
|
|
||||||
pub inner: Box<dyn Read + Send + 'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const ASYNC_READ_BUFFER_SIZE: usize = 8128;
|
|
||||||
|
|
||||||
impl<'a> AsyncRead for ReadToAsyncRead<'a> {
|
|
||||||
fn poll_read(
|
|
||||||
mut self: std::pin::Pin<&mut Self>,
|
|
||||||
_cx: &mut std::task::Context<'_>,
|
|
||||||
buf: &mut tokio::io::ReadBuf<'_>,
|
|
||||||
) -> std::task::Poll<io::Result<()>> {
|
|
||||||
let mut read_buf = [0u8; ASYNC_READ_BUFFER_SIZE];
|
|
||||||
let read_size = ASYNC_READ_BUFFER_SIZE.min(buf.remaining());
|
|
||||||
match self.inner.read(&mut read_buf[0..read_size]) {
|
|
||||||
Ok(read) => {
|
|
||||||
buf.put_slice(&read_buf[0..read]);
|
|
||||||
std::task::Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
std::task::Poll::Ready(Err(err))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait VersionBackend: DynClone {
|
|
||||||
fn require_whole_files(&self) -> bool;
|
|
||||||
fn list_files(&mut self) -> anyhow::Result<Vec<VersionFile>>;
|
|
||||||
fn peek_file(&mut self, sub_path: String) -> anyhow::Result<VersionFile>;
|
|
||||||
fn reader(
|
|
||||||
&mut self,
|
|
||||||
file: &VersionFile,
|
|
||||||
start: u64,
|
|
||||||
end: u64,
|
|
||||||
) -> anyhow::Result<Box<dyn MinimumFileObject + '_>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
dyn_clone::clone_trait_object!(VersionBackend);
|
|
||||||
Reference in New Issue
Block a user