New v0.4.0 website
This commit is contained in:
@@ -21,6 +21,7 @@ target/
|
||||
/target
|
||||
|
||||
perf.data
|
||||
perf.data.old
|
||||
flamegraph.svg
|
||||
*.json
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "libarchive-rust"]
|
||||
path = libarchive-rust
|
||||
url = https://github.com/Drop-OSS/libarchive-rust.git
|
||||
Generated
+416
-159
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ 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"
|
||||
|
||||
[dependencies]
|
||||
droplet_types = { path = "../droplet_types" }
|
||||
hex = "0.4.3"
|
||||
time = "0.3.41"
|
||||
ring = "0.17.14"
|
||||
|
||||
Submodule libraries/droplet/libarchive-rust deleted from fdb73ef2de
@@ -1,13 +1,14 @@
|
||||
#![deny(clippy::all)]
|
||||
#![feature(impl_trait_in_bindings)]
|
||||
|
||||
#![feature(nonpoison_mutex)]
|
||||
#![feature(sync_nonpoison)]
|
||||
pub mod file_utils;
|
||||
pub mod manifest;
|
||||
pub mod ssl;
|
||||
pub mod versions;
|
||||
pub mod vm;
|
||||
|
||||
extern crate libarchive_drop;
|
||||
pub use manifest::{CHUNK_SIZE, MAX_FILE_COUNT};
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests;
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
use droplet_rs::manifest::generate_manifest_rusty;
|
||||
use droplet_rs::manifest::{ManifestWriterFactory, generate_manifest_rusty};
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
struct SinkFactory {}
|
||||
#[async_trait::async_trait]
|
||||
impl ManifestWriterFactory for SinkFactory {
|
||||
type Writer = tokio::io::Sink;
|
||||
async fn create(&self, _id: String) -> anyhow::Result<Self::Writer> {
|
||||
Ok(tokio::io::sink())
|
||||
}
|
||||
|
||||
async fn close(&self, _writer: Self::Writer) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
let mut args = env::args();
|
||||
@@ -17,6 +30,7 @@ pub async fn main() {
|
||||
|message| {
|
||||
println!("{}", message);
|
||||
},
|
||||
Some(&SinkFactory {}),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
||||
+217
-206
@@ -1,162 +1,172 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
mem,
|
||||
path::Path,
|
||||
sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
use std::{collections::HashMap, ops::Not, path::Path};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
use hex::ToHex as _;
|
||||
use humansize::{format_size, BINARY};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest as _, Sha256};
|
||||
use tokio::{
|
||||
io::AsyncReadExt as _,
|
||||
join,
|
||||
sync::{Mutex, Semaphore},
|
||||
task::JoinSet,
|
||||
};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::io::{AsyncReadExt as _, AsyncWrite};
|
||||
use tokio::sync::Semaphore;
|
||||
pub use droplet_types::{ChunkData, FileEntry, Manifest};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct FileEntry {
|
||||
pub filename: String,
|
||||
pub start: usize,
|
||||
pub length: usize,
|
||||
pub permissions: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ChunkData {
|
||||
pub files: Vec<FileEntry>,
|
||||
pub checksum: String,
|
||||
pub iv: [u8; 16],
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Manifest {
|
||||
pub version: String,
|
||||
pub chunks: HashMap<String, ChunkData>,
|
||||
pub size: u64,
|
||||
pub key: [u8; 16],
|
||||
}
|
||||
|
||||
const CHUNK_SIZE: u64 = 1024 * 1024 * 64;
|
||||
const MAX_FILE_COUNT: usize = 512;
|
||||
pub const CHUNK_SIZE: u64 = 1024 * 1024 * 64;
|
||||
pub const MAX_FILE_COUNT: usize = 512;
|
||||
|
||||
use crate::versions::{
|
||||
create_backend_constructor,
|
||||
types::{VersionBackend, VersionFile},
|
||||
};
|
||||
|
||||
pub async fn generate_manifest_rusty<T: Fn(String), V: Fn(f32)>(
|
||||
dir: &Path,
|
||||
progress_sfn: V,
|
||||
log_sfn: T,
|
||||
reader_semaphore: Option<Arc<Semaphore>>,
|
||||
) -> anyhow::Result<Manifest> {
|
||||
let backend =
|
||||
create_backend_constructor(dir).ok_or(anyhow!("Could not create backend for path."))?()?;
|
||||
|
||||
let required_single_file = backend.require_whole_files();
|
||||
#[async_trait]
|
||||
pub trait ManifestWriterFactory: Send + Sync {
|
||||
type Writer: AsyncWrite + Unpin;
|
||||
async fn create(&self, id: String) -> anyhow::Result<Self::Writer>;
|
||||
async fn close(&self, writer: Self::Writer) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
pub async fn generate_manifest_rusty<P, LogFn, ProgFn, Writer>(
|
||||
dir: P,
|
||||
progress_sfn: ProgFn,
|
||||
log_sfn: LogFn,
|
||||
factory: Option<&dyn ManifestWriterFactory<Writer = Writer>>,
|
||||
semaphore: Option<&Semaphore>,
|
||||
) -> anyhow::Result<Manifest>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
LogFn: Fn(String) + Clone,
|
||||
ProgFn: Fn(f32),
|
||||
Writer: AsyncWrite + Unpin,
|
||||
{
|
||||
let backend = create_backend_constructor(dir).ok_or(anyhow!(
|
||||
"Could not create backend for path. Is this structure supported?"
|
||||
))?()?;
|
||||
let mut files = backend.list_files().await?;
|
||||
files.sort_by_key(|b| std::cmp::Reverse(b.size));
|
||||
// Filepath to chunk data
|
||||
let mut chunks: Vec<Vec<(VersionFile, u64, u64)>> = Vec::new();
|
||||
let mut current_chunk: Vec<(VersionFile, u64, u64)> = Vec::new();
|
||||
files.sort_by(|a, b| b.size.cmp(&a.size));
|
||||
|
||||
log_sfn("organizing files into chunks...".to_string());
|
||||
log_sfn("organising files into chunks...".to_string());
|
||||
|
||||
if required_single_file {
|
||||
for version_file in files {
|
||||
if version_file.size >= CHUNK_SIZE {
|
||||
let size = version_file.size;
|
||||
chunks.push(vec![(version_file, 0, size)]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut current_size = current_chunk.iter().map(|v| v.2).sum::<u64>();
|
||||
|
||||
let size = version_file.size;
|
||||
current_chunk.push((version_file, 0, size));
|
||||
|
||||
current_size += size;
|
||||
|
||||
if current_size >= CHUNK_SIZE {
|
||||
// Pop current and add, then reset
|
||||
let new_chunk = std::mem::take(&mut current_chunk);
|
||||
chunks.push(new_chunk);
|
||||
}
|
||||
|
||||
if current_chunk.len() >= MAX_FILE_COUNT {
|
||||
chunks.push(std::mem::take(&mut current_chunk));
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
for version_file in files {
|
||||
if current_chunk.len() >= MAX_FILE_COUNT {
|
||||
chunks.push(std::mem::take(&mut current_chunk));
|
||||
}
|
||||
|
||||
let current_size = current_chunk.iter().map(|v| v.2).sum::<u64>();
|
||||
|
||||
if version_file.size + current_size < CHUNK_SIZE {
|
||||
let size = version_file.size;
|
||||
current_chunk.push((version_file, 0, size));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fill up current chunk
|
||||
let remaining = CHUNK_SIZE - current_size;
|
||||
current_chunk.push((version_file.clone(), 0, remaining));
|
||||
chunks.push(std::mem::take(&mut current_chunk));
|
||||
|
||||
// This is our offset in our current file
|
||||
let mut offset = remaining;
|
||||
while offset < version_file.size {
|
||||
let length = CHUNK_SIZE.min(version_file.size - offset);
|
||||
if length == CHUNK_SIZE {
|
||||
chunks.push(vec![(version_file.clone(), offset, length)]);
|
||||
} else {
|
||||
current_chunk.push((version_file.clone(), offset, length));
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !current_chunk.is_empty() {
|
||||
chunks.push(current_chunk);
|
||||
}
|
||||
let chunks = organise_files(files, backend.require_whole_files());
|
||||
|
||||
log_sfn(format!(
|
||||
"organized into {} chunks, generating checksums...",
|
||||
chunks.len()
|
||||
));
|
||||
let manifest = read_chunks_and_generate_manifest(
|
||||
backend.as_ref(),
|
||||
chunks,
|
||||
progress_sfn,
|
||||
&log_sfn,
|
||||
factory,
|
||||
semaphore,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let manifest: Arc<Mutex<HashMap<String, ChunkData>>> = Arc::new(Mutex::new(HashMap::new()));
|
||||
let total_manifest_length = Arc::new(AtomicU64::new(0));
|
||||
let mut key = [0u8; 16];
|
||||
getrandom::fill(&mut key).map_err(|err| anyhow!("failed to generate key: {:?}", err))?;
|
||||
|
||||
// SAFETY: we .join_all() the futures using this
|
||||
let backend: &'static (dyn VersionBackend + Send + Sync) = unsafe { mem::transmute(&*backend) };
|
||||
let total_manifest_length = manifest
|
||||
.values()
|
||||
.map(|value| value.files.iter().map(|f| f.length as u64).sum::<u64>())
|
||||
.sum::<u64>();
|
||||
|
||||
let mut futures: JoinSet<Result<(), anyhow::Error>> = JoinSet::new();
|
||||
let (send_log, mut recieve_log) = tokio::sync::mpsc::channel(16);
|
||||
let chunks_length = chunks.len();
|
||||
for (index, chunk) in chunks.into_iter().enumerate() {
|
||||
let send_log = send_log.clone();
|
||||
let total_manifest_length = total_manifest_length.clone();
|
||||
let manifest = manifest.clone();
|
||||
let reader_semaphore = reader_semaphore.clone();
|
||||
futures.spawn(async move {
|
||||
let mut read_buf = vec![0u8; 1024 * 1024 * 8];
|
||||
Ok(Manifest {
|
||||
version: "2".to_string(),
|
||||
chunks: manifest,
|
||||
size: total_manifest_length,
|
||||
key,
|
||||
})
|
||||
}
|
||||
|
||||
fn organise_files(
|
||||
files: Vec<VersionFile>,
|
||||
require_whole_files: bool,
|
||||
) -> Vec<Vec<(VersionFile, u64, u64)>> {
|
||||
let mut chunks = Vec::new();
|
||||
let mut current_chunk = Vec::new();
|
||||
|
||||
for version_file in files {
|
||||
if current_chunk.len() >= MAX_FILE_COUNT {
|
||||
// Pop current chunk
|
||||
chunks.push(std::mem::take(&mut current_chunk));
|
||||
println!("Chunks: {}", chunks.len());
|
||||
}
|
||||
let current_chunk_size = current_chunk
|
||||
.iter()
|
||||
.map(|(_, _, length)| *length)
|
||||
.sum::<u64>();
|
||||
let version_file_size = version_file.size;
|
||||
|
||||
if require_whole_files {
|
||||
// If the current chunk is larger than chunk size, there's no point adding
|
||||
// it to the current_chunk. Just push it by itself
|
||||
if version_file_size >= CHUNK_SIZE {
|
||||
chunks.push(vec![(version_file, 0, version_file_size)]);
|
||||
println!("Chunks: {}", chunks.len());
|
||||
continue;
|
||||
}
|
||||
|
||||
current_chunk.push((version_file, 0, version_file_size));
|
||||
if current_chunk_size + version_file_size >= CHUNK_SIZE {
|
||||
// Pop current chunk
|
||||
chunks.push(std::mem::take(&mut current_chunk));
|
||||
println!("Chunks: {}", chunks.len());
|
||||
}
|
||||
} else {
|
||||
// Enough space for it to be put in immediately
|
||||
if version_file_size + current_chunk_size < CHUNK_SIZE {
|
||||
current_chunk.push((version_file, 0, version_file_size));
|
||||
continue;
|
||||
}
|
||||
|
||||
let bytes_free_in_existing_chunk = CHUNK_SIZE - current_chunk_size;
|
||||
current_chunk.push((version_file.clone(), 0, bytes_free_in_existing_chunk));
|
||||
chunks.push(std::mem::take(&mut current_chunk));
|
||||
|
||||
// Loop over remaining data and create sufficient chunks to use it
|
||||
let mut offset = bytes_free_in_existing_chunk;
|
||||
while offset < version_file_size {
|
||||
let length = CHUNK_SIZE.min(version_file_size - offset);
|
||||
if length == CHUNK_SIZE {
|
||||
chunks.push(vec![(version_file.clone(), offset, length)]);
|
||||
println!("Chunks: {}", chunks.len());
|
||||
} else {
|
||||
current_chunk.push((version_file.clone(), offset, length));
|
||||
println!("Chunks: {}", chunks.len());
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
}
|
||||
}
|
||||
if current_chunk.is_empty().not() {
|
||||
chunks.push(current_chunk);
|
||||
println!("Pushed final chunk: {}", chunks.len());
|
||||
}
|
||||
println!("Chunks: {}", chunks.len());
|
||||
chunks
|
||||
}
|
||||
|
||||
async fn read_chunks_and_generate_manifest<LogFn, ProgFn, Writer>(
|
||||
backend: &(dyn VersionBackend + Send + Sync),
|
||||
chunks: Vec<Vec<(VersionFile, u64, u64)>>,
|
||||
progress_sfn: ProgFn,
|
||||
log_sfn: &LogFn,
|
||||
factory: Option<&dyn ManifestWriterFactory<Writer = Writer>>,
|
||||
semaphore: Option<&Semaphore>,
|
||||
) -> anyhow::Result<HashMap<String, ChunkData>>
|
||||
where
|
||||
LogFn: Fn(String),
|
||||
ProgFn: Fn(f32),
|
||||
Writer: AsyncWrite + Unpin,
|
||||
{
|
||||
let total_chunk_count = chunks.len();
|
||||
|
||||
let futures = chunks.into_iter().enumerate().map(|(index, chunk)| {
|
||||
// To make the borrow checker happy
|
||||
async move {
|
||||
let mut read_buf = vec![0; 1024 * 1024 * 64];
|
||||
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
let mut hasher = Sha256::new();
|
||||
@@ -168,91 +178,92 @@ pub async fn generate_manifest_rusty<T: Fn(String), V: Fn(f32)>(
|
||||
checksum: String::new(),
|
||||
iv,
|
||||
};
|
||||
|
||||
let mut chunk_length = 0;
|
||||
|
||||
let mut chunk_length = 0u64;
|
||||
let mut writer = match factory {
|
||||
Some(factory) => Some(factory.create(uuid.clone()).await?),
|
||||
None => None,
|
||||
};
|
||||
for (file, start, length) in chunk {
|
||||
let permit = if let Some(reader_semaphore) = &reader_semaphore {
|
||||
Some(reader_semaphore.acquire().await?)
|
||||
let permit = if let Some(semaphore) = &semaphore {
|
||||
Some(semaphore.acquire().await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut reader = backend.reader(&file, start, start + length).await?;
|
||||
|
||||
let mut total = 0;
|
||||
|
||||
loop {
|
||||
let amount = reader.read(&mut read_buf).await?;
|
||||
if amount == 0 {
|
||||
break;
|
||||
}
|
||||
total += amount;
|
||||
hasher.update(&read_buf[0..amount]);
|
||||
}
|
||||
|
||||
if total as u64 > length {
|
||||
panic!("read too much: target {}, got {}", length, total);
|
||||
}
|
||||
|
||||
chunk_data.files.push(
|
||||
read_and_generate_chunk_file_data(
|
||||
backend,
|
||||
&file,
|
||||
start,
|
||||
length,
|
||||
&mut hasher,
|
||||
&mut read_buf,
|
||||
&mut writer,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
chunk_length += length;
|
||||
|
||||
chunk_data.files.push(FileEntry {
|
||||
filename: file.relative_filename,
|
||||
start: start.try_into().unwrap(),
|
||||
length: length.try_into().unwrap(),
|
||||
permissions: file.permission,
|
||||
});
|
||||
|
||||
drop(permit);
|
||||
}
|
||||
|
||||
send_log
|
||||
.send(format!(
|
||||
"created chunk of size {} ({}b) from {} files (index {})",
|
||||
format_size(chunk_length, BINARY),
|
||||
chunk_length,
|
||||
chunk_data.files.len(),
|
||||
index
|
||||
))
|
||||
.await?;
|
||||
|
||||
total_manifest_length.fetch_add(chunk_length, Ordering::Relaxed);
|
||||
|
||||
if let Some(factory) = factory {
|
||||
factory.close(writer.expect("Failed to get writer")).await?;
|
||||
}
|
||||
let hash: String = hasher.finalize().encode_hex();
|
||||
chunk_data.checksum = hash;
|
||||
{
|
||||
let mut manifest_lock = manifest.lock().await;
|
||||
manifest_lock.insert(uuid, chunk_data);
|
||||
};
|
||||
|
||||
Ok(())
|
||||
});
|
||||
log_sfn(format!(
|
||||
"created chunk of size {} ({}b) from {} files (index {})",
|
||||
format_size(chunk_length, BINARY),
|
||||
chunk_length,
|
||||
chunk_data.files.len(),
|
||||
index
|
||||
));
|
||||
|
||||
Ok::<_, anyhow::Error>((uuid, chunk_data))
|
||||
}
|
||||
});
|
||||
let mut stream = futures::stream::iter(futures)
|
||||
.buffer_unordered(semaphore.map(|s| s.available_permits()).unwrap_or(4))
|
||||
.enumerate();
|
||||
let mut results = HashMap::new();
|
||||
let mut current_progress = 0f32;
|
||||
while let Some((_, res)) = stream.next().await {
|
||||
let (id, data) = res?;
|
||||
current_progress += 1.0;
|
||||
progress_sfn((current_progress / total_chunk_count as f32) * 100.0f32);
|
||||
results.insert(id, data);
|
||||
}
|
||||
drop(send_log);
|
||||
join!(
|
||||
async move {
|
||||
let mut current_progress = 0f32;
|
||||
let total_progress = chunks_length as f32;
|
||||
while let Some(message) = recieve_log.recv().await {
|
||||
log_sfn(message);
|
||||
current_progress += 1.0f32;
|
||||
progress_sfn((current_progress / total_progress) * 100.0f32)
|
||||
}
|
||||
},
|
||||
futures.join_all()
|
||||
);
|
||||
Ok(results)
|
||||
}
|
||||
async fn read_and_generate_chunk_file_data<Writer>(
|
||||
backend: &(dyn VersionBackend + Sync + Send),
|
||||
file: &VersionFile,
|
||||
start: u64,
|
||||
length: u64,
|
||||
hasher: &mut Sha256,
|
||||
read_buf: &mut [u8],
|
||||
writer: &mut Option<Writer>,
|
||||
) -> anyhow::Result<FileEntry>
|
||||
where
|
||||
Writer: AsyncWrite + Unpin,
|
||||
{
|
||||
let mut reader = backend.reader(file, start, start + length).await?;
|
||||
|
||||
let manifest = manifest.lock().await;
|
||||
let manifest = manifest.clone();
|
||||
loop {
|
||||
let amount = reader.read(read_buf).await?;
|
||||
|
||||
let mut key = [0u8; 16];
|
||||
getrandom::fill(&mut key).map_err(|err| anyhow!("failed to generate key: {:?}", err))?;
|
||||
if amount == 0 {
|
||||
break;
|
||||
}
|
||||
if let Some(writer) = writer.as_mut() {
|
||||
writer.write_all(&read_buf[0..amount]).await?;
|
||||
}
|
||||
hasher.update(&read_buf[0..amount]);
|
||||
}
|
||||
|
||||
Ok(Manifest {
|
||||
version: "2".to_string(),
|
||||
chunks: manifest,
|
||||
size: total_manifest_length.fetch_add(0, Ordering::Relaxed),
|
||||
key,
|
||||
Ok(FileEntry {
|
||||
filename: file.relative_filename.clone(),
|
||||
start: start.try_into().unwrap(),
|
||||
length: length.try_into().unwrap(),
|
||||
permissions: file.permission,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ extern crate test_generator;
|
||||
use std::path::Path;
|
||||
|
||||
use test_generator::test_resources;
|
||||
use tokio::io::SimplexStream;
|
||||
|
||||
use crate::manifest::generate_manifest_rusty;
|
||||
use crate::manifest::{generate_manifest_rusty, ManifestWriterFactory};
|
||||
|
||||
#[test_resources("testfiles/**/*.7z")]
|
||||
fn manifest_gen(resource: &str) {
|
||||
@@ -22,6 +23,7 @@ fn manifest_gen(resource: &str) {
|
||||
|message| {
|
||||
println!("({}) {}", filepath.display(), message);
|
||||
},
|
||||
None::<&dyn ManifestWriterFactory<Writer = SimplexStream>>, // Dummy type signature, not actually used
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -28,12 +28,12 @@ impl ZipVersionBackend {
|
||||
}
|
||||
}
|
||||
|
||||
struct ArchiveReader<'a> {
|
||||
struct ArchiveReader {
|
||||
archive: FileReader,
|
||||
prev_block: Option<&'a [u8]>,
|
||||
prev_block: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<'a> AsyncRead for ArchiveReader<'a> {
|
||||
impl AsyncRead for ArchiveReader {
|
||||
fn poll_read(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
@@ -41,9 +41,8 @@ impl<'a> AsyncRead for ArchiveReader<'a> {
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
if let Some(block) = &mut self.prev_block {
|
||||
let to_read = buf.remaining().min(block.len());
|
||||
let result = block.split_off(..to_read);
|
||||
let result = result.unwrap(); // SAFETY: above .min statement
|
||||
buf.put_slice(result);
|
||||
let result = block.split_off(to_read);
|
||||
buf.put_slice(&result);
|
||||
|
||||
// If the block is empty, we can read more
|
||||
if block.is_empty() {
|
||||
@@ -52,27 +51,31 @@ impl<'a> AsyncRead for ArchiveReader<'a> {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
}
|
||||
let block = match self.archive.read_block() {
|
||||
Ok(v) => v,
|
||||
Err(err) => return Poll::Ready(Err(std::io::Error::other(err.to_string()))),
|
||||
};
|
||||
let prev_block_update = {
|
||||
let block = match self.archive.read_block() {
|
||||
Ok(v) => v,
|
||||
Err(err) => return Poll::Ready(Err(std::io::Error::other(err.to_string()))),
|
||||
};
|
||||
|
||||
let mut block = match block {
|
||||
Some(v) => v,
|
||||
None => return Poll::Ready(Ok(())),
|
||||
};
|
||||
let mut block = match block {
|
||||
Some(v) => v,
|
||||
None => return Poll::Ready(Ok(())),
|
||||
};
|
||||
|
||||
let write_amount = buf.remaining().min(block.len());
|
||||
let to_write = block.split_off(..write_amount);
|
||||
let to_write = to_write.unwrap(); // SAFETY: above .min statement
|
||||
buf.put_slice(to_write);
|
||||
let write_amount = buf.remaining().min(block.len());
|
||||
let to_write = block.split_off(..write_amount);
|
||||
let to_write = to_write.unwrap(); // SAFETY: above .min statement
|
||||
buf.put_slice(to_write);
|
||||
|
||||
if !block.is_empty() {
|
||||
#[cfg(debug_assertions)]
|
||||
if self.prev_block.is_some() {
|
||||
panic!("replacing prev_block while it contains data")
|
||||
if !block.is_empty() {
|
||||
Some(block[buf.remaining()..].to_vec())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.prev_block.replace(&block[buf.remaining()..]);
|
||||
};
|
||||
|
||||
if let Some(prev_block) = prev_block_update {
|
||||
self.prev_block.replace(prev_block);
|
||||
}
|
||||
|
||||
Poll::Ready(Ok(()))
|
||||
|
||||
@@ -5,10 +5,11 @@ use std::{
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::versions::{
|
||||
archive_backend::ZipVersionBackend, path_backend::PathVersionBackend, types::VersionBackend,
|
||||
};
|
||||
use crate::versions::{path_backend::PathVersionBackend, types::VersionBackend};
|
||||
|
||||
use crate::versions::archive_backend::ZipVersionBackend;
|
||||
|
||||
// libarchive backend is Linux-only for now
|
||||
pub mod archive_backend;
|
||||
pub mod path_backend;
|
||||
|
||||
@@ -33,10 +34,13 @@ const SUPPORTED_FILE_EXTENSIONS: [&str; 11] = [
|
||||
];
|
||||
|
||||
pub mod types;
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn create_backend_constructor<'a>(
|
||||
path: &Path,
|
||||
) -> Option<Box<dyn FnOnce() -> Result<Box<dyn VersionBackend + Send + Sync + 'a>>>> {
|
||||
pub fn create_backend_constructor<'a, P>(
|
||||
path: P,
|
||||
) -> Option<Box<dyn FnOnce() -> Result<Box<dyn VersionBackend + Send + Sync + 'a>>>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let path = path.as_ref();
|
||||
if !path.exists() {
|
||||
return None;
|
||||
}
|
||||
@@ -49,8 +53,7 @@ pub fn create_backend_constructor<'a>(
|
||||
}));
|
||||
};
|
||||
|
||||
let file_extension = path.extension().and_then(|v| v.to_str())?;
|
||||
|
||||
let file_extension = path.extension().map(|v| v.to_str()).flatten()?;
|
||||
if SUPPORTED_FILE_EXTENSIONS.contains(&file_extension) {
|
||||
let buf = path.to_path_buf();
|
||||
return Some(Box::new(move || Ok(Box::new(ZipVersionBackend::new(buf)?))));
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
|
||||
perf.data
|
||||
perf.data.old
|
||||
flamegraph.svg
|
||||
*.json
|
||||
|
||||
.direnv
|
||||
Generated
+75
@@ -0,0 +1,75 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "droplet_types"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "droplet_types"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "*", features = ["derive"] }
|
||||
@@ -0,0 +1,6 @@
|
||||
# droplet_types
|
||||
|
||||
Shared types between the cross-platform client and the droplet-rs crate.
|
||||
|
||||
|
||||
Split off from droplet-rs because of cross-compiling issues with the desktop client, and there's no need to compile the entirety of droplet-rs if we're only using a few types.
|
||||
@@ -0,0 +1,27 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct FileEntry {
|
||||
pub filename: String,
|
||||
pub start: usize,
|
||||
pub length: usize, // TODO: Replace with u64 for 32 bit clients
|
||||
pub permissions: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct ChunkData {
|
||||
pub files: Vec<FileEntry>,
|
||||
pub checksum: String,
|
||||
pub iv: [u8; 16],
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Manifest {
|
||||
pub version: String,
|
||||
pub chunks: HashMap<String, ChunkData>,
|
||||
pub size: u64,
|
||||
pub key: [u8; 16],
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ name = "libarchive-drop"
|
||||
version = "0.1.1"
|
||||
authors = ["Jamie Winsor <reset@chef.io>", "Drop OSS"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/Drop-OSS/libarchive-rust"
|
||||
repository = "https://lab.droposs.org/drop-oss/drop/-/tree/develop/libraries/libarchive"
|
||||
description = "A safe Rust API for authoring and extracting archives with libarchive"
|
||||
keywords = ["libarchive", "archive", "tar", "zip"]
|
||||
|
||||
[dependencies]
|
||||
libc = ">= 0.2.0"
|
||||
libarchive3-sys = "0.1"
|
||||
libarchive3-sys = "0.1.2"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
extern crate libarchive;
|
||||
extern crate libarchive_drop;
|
||||
|
||||
pub mod util;
|
||||
|
||||
use libarchive::archive::{self, ReadFilter, ReadFormat};
|
||||
use libarchive::reader::{self, Reader};
|
||||
use libarchive::writer;
|
||||
use libarchive_drop::archive::{self, ReadFilter, ReadFormat};
|
||||
use libarchive_drop::reader::{self, Reader};
|
||||
use libarchive_drop::writer;
|
||||
use std::fs::File;
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user