|
|
@ -6,6 +6,8 @@ use tokio::fs;
|
|
|
|
use tokio::fs::{File, OpenOptions};
|
|
|
|
use tokio::fs::{File, OpenOptions};
|
|
|
|
use tokio::io::{AsyncRead, AsyncReadExt, BufReader};
|
|
|
|
use tokio::io::{AsyncRead, AsyncReadExt, BufReader};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static STORE_BASE: Base = Base::Base32Lower;
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct FileHashStore {
|
|
|
|
pub struct FileHashStore {
|
|
|
|
path: PathBuf,
|
|
|
|
path: PathBuf,
|
|
|
@ -17,33 +19,47 @@ impl FileHashStore {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Adds a file that can be read to the hash store and returns the resulting hash identifier
|
|
|
|
/// Adds a file that can be read to the hash store and returns the resulting hash identifier
|
|
|
|
pub async fn add_file<R: AsyncRead + Unpin>(&self, mut reader: R) -> RepoResult<String> {
|
|
|
|
pub async fn add_file<R: AsyncRead + Unpin>(
|
|
|
|
|
|
|
|
&self,
|
|
|
|
|
|
|
|
mut reader: R,
|
|
|
|
|
|
|
|
extension: Option<&str>,
|
|
|
|
|
|
|
|
) -> RepoResult<String> {
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
reader.read_to_end(&mut buf).await?;
|
|
|
|
reader.read_to_end(&mut buf).await?;
|
|
|
|
let hash: Vec<u8> = Code::Sha2_256.digest(&buf).to_bytes();
|
|
|
|
let hash: Vec<u8> = Code::Sha2_256.digest(&buf).to_bytes();
|
|
|
|
let hash: String = multibase::encode(Base::Base64, &hash);
|
|
|
|
let hash: String = multibase::encode(STORE_BASE, &hash);
|
|
|
|
let folder_path = self.hash_to_folder_path(&hash);
|
|
|
|
let folder_path = self.hash_to_folder_path(&hash);
|
|
|
|
|
|
|
|
|
|
|
|
if !folder_path.exists() {
|
|
|
|
if !folder_path.exists() {
|
|
|
|
fs::create_dir(folder_path).await?;
|
|
|
|
fs::create_dir(folder_path).await?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let file_path = self.hash_to_file_path(&hash);
|
|
|
|
let mut file_path = self.hash_to_file_path(&hash);
|
|
|
|
|
|
|
|
if let Some(extension) = extension {
|
|
|
|
|
|
|
|
file_path.set_extension(extension);
|
|
|
|
|
|
|
|
}
|
|
|
|
fs::write(file_path, buf).await?;
|
|
|
|
fs::write(file_path, buf).await?;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(hash)
|
|
|
|
Ok(hash)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns the file by hash
|
|
|
|
/// Returns the file extension and a reader for the file by hash
|
|
|
|
pub async fn get_file(&self, mut hash: String) -> RepoResult<BufReader<File>> {
|
|
|
|
pub async fn get_file(
|
|
|
|
|
|
|
|
&self,
|
|
|
|
|
|
|
|
mut hash: String,
|
|
|
|
|
|
|
|
) -> RepoResult<(Option<String>, BufReader<File>)> {
|
|
|
|
let (base, data) = multibase::decode(&hash)?;
|
|
|
|
let (base, data) = multibase::decode(&hash)?;
|
|
|
|
if base != Base::Base64 {
|
|
|
|
if base != STORE_BASE {
|
|
|
|
hash = multibase::encode(Base::Base64, data);
|
|
|
|
hash = multibase::encode(STORE_BASE, data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let file_path = self.hash_to_file_path(&hash);
|
|
|
|
let file_path = self.hash_to_file_path(&hash);
|
|
|
|
|
|
|
|
let extension = file_path
|
|
|
|
|
|
|
|
.extension()
|
|
|
|
|
|
|
|
.and_then(|s| s.to_str())
|
|
|
|
|
|
|
|
.map(|s| s.to_string());
|
|
|
|
let file = OpenOptions::new().read(true).open(file_path).await?;
|
|
|
|
let file = OpenOptions::new().read(true).open(file_path).await?;
|
|
|
|
let reader = BufReader::new(file);
|
|
|
|
let reader = BufReader::new(file);
|
|
|
|
|
|
|
|
|
|
|
|
Ok(reader)
|
|
|
|
Ok((extension, reader))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn hash_to_file_path(&self, hash: &str) -> PathBuf {
|
|
|
|
fn hash_to_file_path(&self, hash: &str) -> PathBuf {
|
|
|
|