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
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
@@ -8,6 +8,8 @@ use ring::pkcs8::Document;
use std::collections::HashMap;
use tokio::net::ToSocketAddrs;

// TODO: Rethink

pub struct DspfsBuilder {}

impl DspfsBuilder {
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;
@@ -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
@@ -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();
30 changes: 26 additions & 4 deletions src/fs/file.rs
Original file line number Diff line number Diff line change
@@ -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
@@ -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
}
@@ -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,
}
}

@@ -76,6 +97,7 @@ impl File {
// 5. Create File
Ok(Self {
path,
file_size,
hash: file_hash,
hashing_algorithm: BLOCK_HASHING_ALGORITHM,
block_size,
23 changes: 23 additions & 0 deletions src/fs/group/heed.rs
Original file line number Diff line number Diff line change
@@ -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()?;

43 changes: 40 additions & 3 deletions src/fs/group/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
mod heed;
mod store;

use crate::fs::file::File;
use crate::fs::file::{File, SimpleFile};
use crate::fs::filetree::FileTree;
use crate::fs::group::heed::HeedGroupStore;
use crate::fs::group::store::{GroupStore, SharedGroupStore};
use crate::fs::hash::Hash;
use crate::global_store::{SharedStore, Store};
use crate::user::PublicUser;
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeSet, HashSet};
use std::io::SeekFrom;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
@@ -28,7 +30,7 @@ use uuid::Uuid;
#[derive(Clone, Serialize, Deserialize)]
pub struct StoredGroup {
pub uuid: Uuid,
pub users: Vec<PublicUser>,
pub users: BTreeSet<PublicUser>,
pub location: PathBuf,
}

@@ -39,7 +41,7 @@ impl StoredGroup {
pub fn new(path: impl AsRef<Path>) -> Self {
Self {
uuid: Uuid::new_v4(),
users: Vec::new(),
users: BTreeSet::new(),
location: path.as_ref().to_path_buf(),
}
}
@@ -53,6 +55,10 @@ impl StoredGroup {
pub fn dspfs_folder(&self) -> PathBuf {
self.location.join(".dspfs")
}

pub fn dspfs_root(&self) -> &Path {
self.location.as_path()
}
}

/// A Group is a structure which represents a folder on your computer which is shared through DSPFS.
@@ -208,6 +214,37 @@ impl<S: Store> Group<S> {

Ok(Some(buffer))
}

pub async fn list_files(&self) -> Result<Vec<File>> {
self.group_store.read().await.list_files()
}

/// Returns all files in the directory pointed to by path, from a certain user.
/// This function only returns the files directly in that folder, and not recursively.
pub async fn get_files_from_user(
&self,
user: &PublicUser,
path: &Path,
) -> Result<HashSet<SimpleFile>> {
let filetree = self.group_store.read().await.get_filetree(user)?;

let dir = filetree.find(path).context("no file exists at this path")?;

Ok(match dir {
FileTree::Leaf { file, .. } => {
let mut hs = HashSet::new();
hs.insert(file.simplify());
hs
}
FileTree::Node { children, .. } => children
.iter()
.map(|node| match node {
FileTree::Leaf { file, .. } => file.simplify(),
FileTree::Node { .. } => todo!(),
})
.collect(),
})
}
}

impl<S> Deref for Group<S> {
7 changes: 7 additions & 0 deletions src/fs/group/store.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::fs::file::File;
use crate::fs::filetree::FileTree;
use crate::fs::hash::Hash;
use crate::user::PublicUser;
use anyhow::Result;
@@ -24,6 +25,12 @@ pub trait GroupStore: Send + Sync {
/// Gets a specific file given a filehash
fn get_file(&self, hash: Hash) -> Result<Option<File>>;

/// Gets the list of all files
fn list_files(&self) -> Result<Vec<File>>;

/// Gets the file tree of a specific user
fn get_filetree(&self, user: &PublicUser) -> Result<FileTree>;

/// Changes a user's file from old to new.
fn update_file(&mut self, user: &PublicUser, old: &File, new: File) -> Result<()> {
self.delete_file(user, old)?;
4 changes: 3 additions & 1 deletion src/user/mod.rs
Original file line number Diff line number Diff line change
@@ -8,7 +8,9 @@ use ring::signature::{Ed25519KeyPair, KeyPair, UnparsedPublicKey, ED25519};
use serde::export::TryFrom;
use zerocopy::{AsBytes, LayoutVerified};

#[derive(serde::Serialize, serde::Deserialize, Clone, Eq, PartialEq, Debug, AsBytes)]
#[derive(
serde::Serialize, serde::Deserialize, Clone, Eq, PartialEq, Debug, AsBytes, Ord, PartialOrd,
)]
#[repr(packed)]
pub struct PublicKey(pub(self) [u8; 32]);

2 changes: 1 addition & 1 deletion src/user/public.rs
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ use std::net::IpAddr;

type SymmetricKey = u8;

#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Ord, PartialOrd)]
pub struct PublicUser {
// ed25519 public key
public_key: PublicKey,