use std::{
collections::HashMap,
convert::TryInto,
sync::{
mpsc::{self, Receiver},
Arc,
},
};
use serde::{Deserialize, Serialize};
use crate::{sync::ThreadPool, BinsyncError, ChunkProvider, Manifest};
use super::ChunkId;
type PackId = u64;
const DEFAULT_PACK_SIZE: usize = 4194304;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Pack {
pub hash: PackId,
pub length: u64,
pub chunks: Vec<ChunkId>,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct RemoteManifest {
pub source: Manifest,
pub packs: Vec<Pack>,
}
impl RemoteManifest {
pub fn from_manifest(manifest: Manifest) -> RemoteManifest {
RemoteManifest::with_pack_size(DEFAULT_PACK_SIZE, manifest)
}
pub fn with_pack_size(size: usize, manifest: Manifest) -> RemoteManifest {
let mut packs = Vec::new();
let mut length = 0;
let mut bytes: Vec<u8> = Vec::new();
let mut chunks: Vec<ChunkId> = Vec::new();
for file_chunk_info in &manifest.files {
for chunk in &file_chunk_info.chunks {
if length + chunk.length > size as u64 {
let digest = md5::compute(bytes);
let hash = u64::from_le_bytes(digest[0..8].try_into().unwrap());
packs.push(Pack {
hash,
length,
chunks,
});
length = 0;
bytes = Vec::new();
chunks = Vec::new();
}
bytes.append(&mut chunk.hash.to_le_bytes().to_vec());
chunks.push(chunk.hash);
length += chunk.length;
}
}
if length > 0 {
let digest = md5::compute(bytes);
let hash = u64::from_le_bytes(digest[0..8].try_into().unwrap());
packs.push(Pack {
hash,
length,
chunks,
});
}
RemoteManifest {
source: manifest,
packs,
}
}
}
struct AsyncDownloader {
pool: ThreadPool,
base_url: String,
client: Arc<reqwest::blocking::Client>,
}
impl AsyncDownloader {
pub fn new(base_url: &str) -> AsyncDownloader {
let pool = ThreadPool::new(1);
let mut base_url = base_url.to_string();
if !base_url.ends_with('/') {
base_url.push('/');
}
let client = reqwest::blocking::Client::new();
AsyncDownloader {
pool,
base_url,
client: Arc::new(client),
}
}
pub fn download_pack(&self, pack_id: PackId) -> Receiver<Option<Vec<u8>>> {
let (sender, receiver) = mpsc::channel();
let url = format!("{}{}.binpack", self.base_url, pack_id);
let client = Arc::clone(&self.client);
self.pool.execute(move || {
println!("Fetching pack {}", url);
let request = client.get(url);
let response = match request.send() {
Ok(response) => response,
Err(_) => {
println!("Failed to send request.");
sender.send(None).unwrap();
return;
}
};
if !response.status().is_success() {
println!("Received failure response from URL {}", response.status());
sender.send(None).unwrap();
return;
}
match response.bytes() {
Ok(data) => sender.send(Some(data.to_vec())).unwrap(),
Err(_) => {
println!("Failed to get data.");
sender.send(None).unwrap();
return;
}
};
});
receiver
}
}
#[derive(Debug)]
struct ChunkPackInfo {
pack_id: PackId,
pack_length: u64,
offset: u64,
length: u64,
}
pub struct RemoteChunkProvider {
chunk_cache: HashMap<ChunkId, Vec<u8>>,
downloader: AsyncDownloader,
chunk_map: HashMap<ChunkId, ChunkPackInfo>,
}
impl RemoteChunkProvider {
pub fn new(
base_url: &str,
manifest: &RemoteManifest,
) -> Result<RemoteChunkProvider, BinsyncError> {
let mut chunk_map = HashMap::new();
let mut chunks = HashMap::new();
for file_chunk_info in &manifest.source.files {
for chunk in &file_chunk_info.chunks {
chunks.insert(chunk.hash, chunk);
}
}
for pack in &manifest.packs {
let mut offset: u64 = 0;
for chunk_id in &pack.chunks {
match chunks.get(chunk_id) {
Some(chunk) => {
chunk_map.insert(
chunk_id.clone(),
ChunkPackInfo {
pack_id: pack.hash,
pack_length: pack.length,
offset,
length: chunk.length,
},
);
offset += chunk.length;
}
None => return Err(BinsyncError::ChunkNotFound(chunk_id.clone())),
}
}
}
Ok(RemoteChunkProvider {
chunk_cache: HashMap::new(),
downloader: AsyncDownloader::new(base_url),
chunk_map,
})
}
}
impl ChunkProvider for RemoteChunkProvider {
fn set_plan(&mut self, _plan: &super::SyncPlan) {
}
fn get_chunk<'a>(&'a mut self, key: &u64) -> Result<&'a [u8], BinsyncError> {
if self.chunk_cache.contains_key(&key) {
return Ok(self.chunk_cache.get(&key).unwrap().as_slice());
}
let pack = self.chunk_map.get(&key);
if pack.is_none() {
return Err(BinsyncError::Unspecified(String::from("Pack not found!")));
}
let pack = pack.unwrap();
match self.downloader.download_pack(pack.pack_id).recv().unwrap() {
Some(data) => {
if data.len() != pack.pack_length as usize {
return Err(BinsyncError::Unspecified(String::from(
"Pack length does not match",
)));
}
for chunk in &self.chunk_map {
let chunk_info = chunk.1;
if chunk_info.pack_id == pack.pack_id {
let start = chunk_info.offset as usize;
let end = (chunk_info.offset + chunk_info.length) as usize;
if data.len() >= end {
self.chunk_cache
.insert(chunk.0.clone(), data[start..end].to_vec());
}
}
}
}
None => {
return Err(BinsyncError::Unspecified(String::from(
"Got none downloading pack",
)));
}
}
if let Some(chunk) = self.chunk_cache.get(key) {
Ok(chunk.as_slice())
} else {
Err(BinsyncError::Unspecified(String::from(
"Could not find chunk after download",
)))
}
}
}