Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added interface to use dspfs #12

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/dspfs/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use crate::fs::hash::Hash;
use crate::user::PublicUser;

use crate::fs::file::SimpleFile;
use anyhow::Result;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeSet, HashSet};
use std::ffi::OsString;
use std::fs::DirEntry;
use std::path::Path;
use std::time::SystemTime;
use uuid::Uuid;

#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash)]
pub struct LocalFile {
name: OsString,
modtime: SystemTime,
is_dir: bool,
file_size: u64,
}

impl LocalFile {
pub fn from_direntry(direntry: DirEntry) -> Result<Self> {
let metadata = direntry.metadata()?;

Ok(Self {
name: direntry.file_name(),
modtime: metadata.modified()?,
is_dir: metadata.is_dir(),
file_size: metadata.len(),
})
}
}

#[async_trait]
pub trait Api {
/// Equivalent of `git init`. Creates a new group with only you in it.
async fn init_group(&self, path: &Path) -> Result<Uuid>;

/// Equivalent of `git add`. Adds a file to the group and makes it visible for others in the group.
async fn add_file(&self, guuid: Uuid, path: &Path) -> Result<SimpleFile>;

/// Bootstraps a group.
async fn join_group(&self, guuid: Uuid, bootstrap_user: &PublicUser) -> Result<()>;

/// Gives a flat list of all files and their hashes.
async fn list_files(&self, guuid: Uuid) -> Result<HashSet<SimpleFile>>;

/// Gets all users present in the group (that we know of).
async fn get_users(&self, guuid: Uuid) -> Result<BTreeSet<PublicUser>>;

/// gives a list of files in your local filesystem, which you can share in the group
async fn get_available_files(&self, guuid: Uuid, path: &Path) -> Result<HashSet<LocalFile>>;

/// gets a certain level of filetree as seen by another user
async fn get_files(
&self,
guuid: Uuid,
user: &PublicUser,
path: &Path,
) -> Result<HashSet<SimpleFile>>;

/// requests a file from other users in the group
async fn request_file(&self, hash: Hash, to: &Path) -> Result<()>;

/// Lists current download/uploads.
async fn status(&self) {
todo!()
}

/// Refreshes internal state.
/// may do any of the following things:
/// * Re-index local files.
/// * Check for new users in the group.
/// * Check for new files from other users.
async fn refresh(&self);

// TODO:
async fn add_folder(&self, _guuid: Uuid, path: &Path) -> Result<()> {
assert!(path.is_dir());
todo!()
}
}
2 changes: 2 additions & 0 deletions src/dspfs/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use ring::pkcs8::Document;
use std::collections::HashMap;
use tokio::net::ToSocketAddrs;

// TODO: Rethink

pub struct DspfsBuilder {}

impl DspfsBuilder {
Expand Down
151 changes: 126 additions & 25 deletions src/dspfs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
use crate::dspfs::builder::DspfsBuilder;
use crate::dspfs::client::Client;
use crate::dspfs::server::{Server, ServerHandle};
use crate::fs::file::File;
use crate::fs::file::SimpleFile;
use crate::fs::group::StoredGroup;
use crate::fs::hash::Hash;
use crate::global_store::{SharedStore, Store};
use crate::user::{PrivateUser, PublicUser};
use anyhow::Result;
use anyhow::{Context, Result};
use api::Api;
use api::LocalFile;
use async_trait::async_trait;
use log::*;
use std::collections::HashMap;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::fs;
use std::mem;
use std::path::Path;
use uuid::Uuid;

pub mod api;
pub mod builder;
pub mod client;
pub mod server;
Expand Down Expand Up @@ -53,36 +61,129 @@ impl<S: Store> Dspfs<S> {

Ok(())
}
}

pub async fn new_group(&mut self, path: impl AsRef<Path>) -> Result<()> {
// 1. create or find folder (mkdir -p)
// a)
// 2. create .dspfs folder inside of that folder
// 2.1: build file tree
// 2.2: schedule index

// b)
// 2. Import the existing .dspfs folder

#[async_trait]
impl<S: Store> Api for Dspfs<S> {
async fn init_group(&self, path: &Path) -> Result<Uuid> {
let group = StoredGroup::new(&path);

if group.dspfs_folder().exists() {
// Existing folder
todo!()
} else {
if !group.dspfs_folder().exists() {
// New folder
fs::create_dir_all(group.dspfs_folder())?;
let uuid = group.uuid;
self.store.write().await.add_group(group)?;

Ok(uuid)
} else {
Err(anyhow::anyhow!("Group already exists"))
}
}

Ok(())
async fn add_file(&self, guuid: Uuid, path: &Path) -> Result<SimpleFile> {
let mut group = self
.store
.read()
.await
.get_group(guuid)?
.context("There is no group with this uuid.")?
.reload(self.store.clone())?;

let us = self.store.read().await.get_self_user()?.context("")?;

let mut location = group.dspfs_root().to_path_buf();
location.push(path);

if !location.is_file() {
return Err(anyhow::anyhow!("Path does not point to a file"));
}

let file = File::new(location).await.context("Indexing file failed")?;

let simple_file = file.simplify();

group
.add_file(&us, file)
.await
.context("Adding file to group failed")?;

Ok(simple_file)
}
}

//
// #[async_trait]
// impl<S: Store> Notify for Dspfs<S> {
// async fn file_added(&mut self, _file: &File) -> Result<()> {
// unimplemented!()
// }
// }
async fn join_group(&self, _guuid: Uuid, _bootstrap_user: &PublicUser) -> Result<()> {
unimplemented!("procrastination is life")
}

async fn list_files(&self, guuid: Uuid) -> Result<HashSet<SimpleFile>> {
let group = self
.store
.read()
.await
.get_group(guuid)?
.context("There is no group with this uuid.")?
.reload(self.store.clone())?;

let f = group.list_files().await?.into_iter().map(|f| f.simplify());

Ok(f.collect())
}

async fn get_users(&self, guuid: Uuid) -> Result<BTreeSet<PublicUser>> {
let group = self
.store
.read()
.await
.get_group(guuid)?
.context("There is no group with this uuid.")?;

Ok(group.users)
}

async fn get_available_files(&self, guuid: Uuid, path: &Path) -> Result<HashSet<LocalFile>> {
let group = self
.store
.read()
.await
.get_group(guuid)?
.context("There is no group with this uuid.")?;

let mut location = group.dspfs_root().to_path_buf();
location.push(path);

let set = location
.read_dir()
.context("Reading directory failed")?
.map(|i| i.map(LocalFile::from_direntry))
.flatten()
.collect::<Result<_>>()?;

Ok(set)
}

async fn get_files(
&self,
guuid: Uuid,
user: &PublicUser,
path: &Path,
) -> Result<HashSet<SimpleFile>> {
let group = self
.store
.read()
.await
.get_group(guuid)?
.context("There is no group with this uuid.")?
.reload(self.store.clone())?;

let _files = group.get_files_from_user(user, path).await;

todo!()
}

async fn request_file(&self, _hash: Hash, _to: &Path) -> Result<()> {
unimplemented!()
}

async fn refresh(&self) {
unimplemented!()
}
}
2 changes: 1 addition & 1 deletion src/dspfs/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ pub mod tests {

// Create group
let mut group = StoredGroup::new(path.clone());
group.users.push(us.public_user().clone());
group.users.insert(us.public_user().clone());
let guuid = group.uuid;

std::fs::create_dir_all(group.dspfs_folder()).unwrap();
Expand Down
30 changes: 26 additions & 4 deletions src/fs/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,23 @@ use crate::fs::hash::{Hash, HashingAlgorithm, BLOCK_HASHING_ALGORITHM};
use crate::user::PublicUser;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::collections::BTreeSet;
use std::path::PathBuf;
use tokio::fs::File as tFile;
use tokio::io::AsyncReadExt;

/// For documentation on fields, refer to the [File] struct
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq, Hash)]
pub struct SimpleFile {
pub path: PathBuf,
pub hash: Hash,
pub users: BTreeSet<PublicUser>,
pub file_size: u64,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
pub struct File {
/// filename/location locally relative to group root.
/// If this variant is None, then this user is guaranteed not to be in the file's user list.
pub path: PathBuf,

/// Hash of the entire file
Expand All @@ -23,14 +31,16 @@ pub struct File {
/// If it does, the file will be recognized as a different file with a different hash
pub(crate) block_size: u64,

pub file_size: u64,

/// Hashes of each block in the file. In the same order as the blocks appear in the file.
blockhashes: Vec<Hash>,

/// People who are likely to have the file. This set is added to whenever
/// we learn that someone has a file, either from them directly or from someone else.
/// Only when we ask this user for the file and it turns out they don't have it anymore,
/// do we remove him from this set.
users: HashSet<PublicUser>,
users: BTreeSet<PublicUser>,
// TODO:
// modtime
}
Expand All @@ -50,7 +60,18 @@ impl File {
hashing_algorithm: BLOCK_HASHING_ALGORITHM,
block_size: block_size(0),
blockhashes: vec![block_hash],
users: HashSet::new(),
users: BTreeSet::new(),
file_size: 0,
}
}

/// TODO: maybe avoid cloning everything here
pub fn simplify(&self) -> SimpleFile {
SimpleFile {
path: self.path.clone(),
hash: self.hash.clone(),
users: self.users.clone(),
file_size: self.file_size,
}
}

Expand All @@ -76,6 +97,7 @@ impl File {
// 5. Create File
Ok(Self {
path,
file_size,
hash: file_hash,
hashing_algorithm: BLOCK_HASHING_ALGORITHM,
block_size,
Expand Down
23 changes: 23 additions & 0 deletions src/fs/group/heed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,29 @@ impl GroupStore for HeedGroupStore {
.context("error getting hash from db")?)
}

fn list_files(&self) -> Result<Vec<File>> {
let rtxn = self.env.read_txn()?;

let files = self
.files
.iter(&rtxn)?
.map(|i| i.map(|(_hash, file)| file))
.flatten()
.collect();

Ok(files)
}

fn get_filetree(&self, user: &PublicUser) -> Result<FileTree> {
let rtxn = self.env.read_txn()?;

Ok(self
.filetrees
.get(&rtxn, user)
.context("error getting filetree from db")?
.context("no filetree found for this user")?)
}

fn delete_file(&mut self, user: &PublicUser, file: &File) -> Result<()> {
let mut wtxn = self.env.write_txn()?;

Expand Down
Loading