1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//! The TBF, short for 'tag-based filesystem', is a new way of storing files.
//!
//! Optimized for human recall and easy searching, tag-based storage reduces the need
//! for complex storage trees. Instead, every file has a unique machine ID, as well as
//! various tagged metadata, which can be used to find any set of files at any time.
//!
//! The overall storage system works like this:
//! - Files are added to the network, and automatically assigned various metadata tags
//! - The user is free to add new tags, which may be part of a tag 'group'
//! - Alternatively, the user can use a unique ID to access a file
//!
//! The system is defined as a trait, with various implementations able to use their own backing
//! implementations. This could be an existing standard filesystem, a SQL database, or just
//! in-memory maps.

#![warn(
    missing_docs,
    elided_lifetimes_in_paths,
    explicit_outlives_requirements,
    missing_abi,
    noop_method_call,
    pointer_structural_match,
    semicolon_in_expressions_from_macros,
    unused_import_braces,
    unused_lifetimes,
    clippy::cargo,
    clippy::missing_panics_doc,
    clippy::doc_markdown,
    clippy::ptr_as_ptr,
    clippy::cloned_instead_of_copied,
    clippy::unreadable_literal
)]
#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;

#[cfg(feature = "dfs")]
mod dfs;
#[cfg(feature = "imfs")]
mod imfs;
mod pattern;

#[cfg(feature = "dfs")]
pub use dfs::{DirectoryBackedFs, Error as DfsError};
#[cfg(feature = "imfs")]
pub use imfs::{Error as ImfsError, InMemoryFs};

pub use pattern::{TagPattern, TagPredicate};

use alloc::boxed::Box;
use alloc::collections::BTreeSet;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::convert::TryFrom;

/// A trait representing an implementation of a tag-based filesystem.
pub trait FileSystem {
    /// The error type to use with this filesystem.
    type Error;

    // Add/Remove/Edit files

    /// Add a new file with the given data and tags
    fn add_file<I>(&self, data: &[u8], tags: I) -> Result<FileId, Self::Error>
    where
        I: IntoIterator<Item = Tag>;

    /// Edit an existing file, altering the data or tags
    fn edit_file<I>(
        &self,
        id: FileId,
        data: Option<&[u8]>,
        tags: Option<I>,
    ) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = Tag>;

    /// Remove an existing file
    fn remove_file(&self, id: FileId) -> Result<(), Self::Error>;

    // Lookup files

    /// Search for files matching a given tag pattern
    fn search_tags<P>(&self, tags: P) -> Result<Vec<FileId>, Self::Error>
    where
        P: TagPattern;

    /// Get info about an existing file
    fn get_info(&self, id: FileId) -> Result<FileInfo, Self::Error>;
}

/// Represents the ID of a file. Most numbers simply represent a unique file, however,
/// the values 0-255 are reserved for special usage.
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct FileId(u64);

impl TryFrom<u64> for FileId {
    type Error = ();

    fn try_from(val: u64) -> Result<Self, Self::Error> {
        if val <= 255 {
            Err(())
        } else {
            Ok(FileId(val))
        }
    }
}

/// The group associated with a tag. Many tags will be part of the 'default'
/// group, but there can be any number of custom groups.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum Group {
    /// The default group
    Default,
    /// A group with a custom name
    Custom(String),
}

impl Group {
    /// Get the custom group associated with a given string
    pub fn custom(group: &str) -> Group {
        Group::Custom(group.to_string())
    }
}

impl Default for Group {
    fn default() -> Self {
        Group::Default
    }
}

/// A file tag, with a name and optionally a tag group
#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Tag {
    group: Group,
    name: String,
}

impl Tag {
    /// Create a new tag with both a group and tag name
    pub fn new(group: Group, name: &str) -> Tag {
        Tag {
            group,
            name: name.to_string(),
        }
    }

    /// Create a tag with a name in the default group
    pub fn name(name: &str) -> Tag {
        Tag {
            group: Group::Default,
            name: name.to_string(),
        }
    }
}

/// Combined info about a file
#[derive(Debug)]
pub struct FileInfo {
    id: FileId,
    tags: BTreeSet<Tag>,
    data: Box<[u8]>,
}

impl FileInfo {
    /// Get the ID of this file
    pub fn id(&self) -> FileId {
        self.id
    }

    /// Get the tags associated with this file
    pub fn tags(&self) -> &BTreeSet<Tag> {
        &self.tags
    }

    /// Get the raw data associated with this file
    pub fn data(&self) -> &[u8] {
        &*self.data
    }
}