large rewrite to make mize run on every platform
This commit is contained in:
597
Cargo.lock
generated
597
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
51
Cargo.toml
51
Cargo.toml
@@ -3,30 +3,45 @@ name = "mize"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "mize"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "mize"
|
||||
path = "src/platform/os/main.rs"
|
||||
required-features = [ "os-binary" ]
|
||||
|
||||
[features]
|
||||
os-binary = [ "clap", "home", "env_logger", "daemonize", "nix" ]
|
||||
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.4.11", features = ["cargo"] }
|
||||
home = "0.5.9"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.91"
|
||||
axum = { version = "0.6.1", features = ["ws", "headers"] }
|
||||
axum-extra = { version = "0.4.2", features = ["spa"] }
|
||||
axum-macros = "0.3.0"
|
||||
# core dependencies
|
||||
colored = "2.0.0"
|
||||
derive_more = "0.99.17"
|
||||
futures-util = "0.3.24"
|
||||
uuid = "1.3.0"
|
||||
log = "0.4.20"
|
||||
tokio = { version = "1.21.0", features = ["sync"] }
|
||||
cbor = "0.4.1"
|
||||
|
||||
clap = { version = "4.4.11", features = ["cargo"], optional = true }
|
||||
home = { version = "0.5.9", optional = true }
|
||||
env_logger = { version = "0.10.1", optional = true }
|
||||
daemonize = { version = "0.5.0", optional = true }
|
||||
nix = { version = "0.27.1", features = ["signal"], optional = true }
|
||||
|
||||
axum = { version = "0.6.1", features = ["ws", "headers"], optional = true }
|
||||
axum-extra = { version = "0.4.2", features = ["spa"], optional = true }
|
||||
axum-macros = { version = "0.3.0", optional = true }
|
||||
|
||||
#tokio-stream = { version = "0.1.9", optional = true }
|
||||
#derive_more = "0.99.17"
|
||||
#futures-util = "0.3.24"
|
||||
#itertools = "0.10.5"
|
||||
#lazy_static = "1.4.0"
|
||||
#rmp-serde = "1.1.1"
|
||||
#rmpv = { version = "1.0.0", features = ["serde", "with-serde"] }
|
||||
tokio = { version = "1.21.0", features = ["full", "net"] }
|
||||
tokio-stream = "0.1.9"
|
||||
toml = "0.5.10"
|
||||
#toml = "0.5.10"
|
||||
#tower-http = "0.3.5"
|
||||
uuid = "1.3.0"
|
||||
log = "0.4.20"
|
||||
env_logger = "0.10.1"
|
||||
daemonize = "0.5.0"
|
||||
nix = { version = "0.27.1", features = ["signal"] }
|
||||
serde_cbor = "0.11.2"
|
||||
|
||||
|
||||
|
||||
60
flake.lock
generated
60
flake.lock
generated
@@ -1,5 +1,46 @@
|
||||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1715274763,
|
||||
"narHash": "sha256-3Iv1PGHJn9sV3HO4FlOVaaztOxa9uGLfOmUWrH7v7+A=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "27025ab71bdca30e7ed0a16c88fd74c5970fc7f5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1715063087,
|
||||
"narHash": "sha256-cktPkcCmJ2sR0V/FaWEuCWmKuGPbwoMltih/EfF0mXg=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "f8f16c1f2c83bea4e51e6522d988ec8bfcc8420e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
@@ -36,10 +77,29 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1714936835,
|
||||
"narHash": "sha256-M+PpgfRMBfHo8Jb2ou/s3maAZbps0XnuHXQU9Hv9vL0=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "c4618fe14d39992fbbb85c2d6cad028a232c13d2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
|
||||
54
flake.nix
54
flake.nix
@@ -3,15 +3,61 @@
|
||||
# nixpkgs 35.11 still contains rust 1.73
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
fenix = {
|
||||
url = "github:nix-community/fenix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
crane = {
|
||||
url = "github:ipetkov/crane";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
outputs = { flake-utils, nixpkgs, ... }@inputs: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; in {
|
||||
outputs = { self, flake-utils, nixpkgs, fenix, crane, ... }@inputs: flake-utils.lib.eachDefaultSystem (system:
|
||||
|
||||
############################## LET BINDINGS ##############################
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
wasmToolchain = fenix.packages.${system}.combine [
|
||||
fenix.packages.${system}.targets.wasm32-unknown-unknown.stable.toolchain
|
||||
fenix.packages.${system}.complete.toolchain
|
||||
];
|
||||
osToolchain = fenix.packages.${system}.complete.toolchain;
|
||||
wasmCrane = crane.lib.${system}.overrideToolchain wasmToolchain;
|
||||
osCrane = crane.lib.${system}.overrideToolchain osToolchain;
|
||||
|
||||
#packages.default =
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [ cargo rustc ];
|
||||
in {
|
||||
############################## PACKAGES ##############################
|
||||
packages.default = osCrane.buildPackage {
|
||||
src = "${self}";
|
||||
};
|
||||
|
||||
packages = {
|
||||
wasm = wasmCrane.buildPackage {
|
||||
src = "${self}";
|
||||
};
|
||||
};
|
||||
|
||||
############################## DEV SHELLS ##############################
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
wasm-pack #pkg-config openssl #cargo rustc
|
||||
cargo-generate
|
||||
/*
|
||||
(fenix.packages.${system}.complete.withComponents [
|
||||
"cargo"
|
||||
"clippy"
|
||||
"rust-src"
|
||||
"rustc"
|
||||
"rustfmt"
|
||||
])
|
||||
*/
|
||||
(fenix.packages.${system}.combine [ wasmToolchain osToolchain ])
|
||||
];
|
||||
};
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub async fn call(sub_matches: &ArgMatches) {
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub async fn mount(sub_matches: &ArgMatches) {
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub async fn serve(sub_matches: &ArgMatches) {
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub async fn set(sub_matches: &ArgMatches) {
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub async fn print(sub_matches: &ArgMatches) {
|
||||
}
|
||||
@@ -1,18 +1,12 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Display;
|
||||
use std::fmt::format;
|
||||
use std::string::FromUtf8Error;
|
||||
use toml::Value;
|
||||
use colored::Colorize;
|
||||
use std::io;
|
||||
use colored::Colorize;
|
||||
use log::{trace, debug, info, warn, error};
|
||||
|
||||
use serde_json::Value as JsonValue;
|
||||
use toml;
|
||||
|
||||
use crate::instance::Connection;
|
||||
use crate::proto::MizeMessage;
|
||||
|
||||
pub type MizeResult<T> = Result<T, MizeError>;
|
||||
13
src/core/id.rs
Normal file
13
src/core/id.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
use std::hash::Hash;
|
||||
|
||||
|
||||
#[derive(Hash, Eq, PartialEq)]
|
||||
pub struct MizeId {
|
||||
path: Vec<String>
|
||||
}
|
||||
|
||||
impl From<String> for MizeId {
|
||||
fn from(value: String) -> Self {
|
||||
MizeId{path: vec![value]}
|
||||
}
|
||||
}
|
||||
16
src/core/instance/connection.rs
Normal file
16
src/core/instance/connection.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use log::{trace, debug, info, warn, error};
|
||||
use tokio::sync::mpsc::{Sender, Receiver};
|
||||
|
||||
use crate::proto::MizeMessage;
|
||||
use crate::error::{MizeError, MizeResult, IntoMizeResult};
|
||||
|
||||
pub trait Connection {
|
||||
}
|
||||
|
||||
|
||||
//#[derive(Debug)]
|
||||
//pub struct Connection {
|
||||
//tx: Sender<MizeMessage>,
|
||||
//rx: Receiver<MizeMessage>,
|
||||
//}
|
||||
|
||||
71
src/core/instance/mod.rs
Normal file
71
src/core/instance/mod.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs::create_dir;
|
||||
use log::{trace, debug, info, warn, error};
|
||||
use uuid::Uuid;
|
||||
use std::fs::File;
|
||||
use colored::Colorize;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::error::{MizeError, MizeResult, IntoMizeResult, MizeResultTrait};
|
||||
use crate::instance::store::Store;
|
||||
use crate::instance::connection::Connection;
|
||||
use crate::id::MizeId;
|
||||
use crate::instance::subscription::Subscription;
|
||||
use crate::item::{Item, ItemData};
|
||||
use crate::memstore::MemStore;
|
||||
|
||||
pub mod connection;
|
||||
pub mod store;
|
||||
pub mod subscription;
|
||||
|
||||
static MSG_CHANNEL_SIZE: usize = 200;
|
||||
|
||||
/// The Instance type is the heart of the mize system
|
||||
pub struct Instance<S: Store> {
|
||||
// question vec of stores???
|
||||
// would mean replication logic and such things is handeled by the instance
|
||||
// i think it would be better thogh to then implement a ReplicatedStore
|
||||
store: S,
|
||||
peers: Vec<Box<dyn Connection>>,
|
||||
subs: HashMap<MizeId, Subscription>
|
||||
}
|
||||
|
||||
/*
|
||||
pub enum RealmId {
|
||||
Uuid(Uuid),
|
||||
Local(Vec<String>),
|
||||
Tld(Vec<String>),
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
impl<S: Store> Instance<S> {
|
||||
// new() opens a
|
||||
pub async fn new() -> MizeResult<Instance<MemStore>> {
|
||||
trace!("[ {} ] Instance::new()", "CALL".yellow());
|
||||
|
||||
let memstore = MemStore::new();
|
||||
let instance = Instance {store: memstore, peers: Vec::new(), subs: HashMap::new()};
|
||||
|
||||
// platform specific init code
|
||||
// TODO
|
||||
|
||||
return Ok(instance);
|
||||
}
|
||||
|
||||
pub fn new_item(self) -> MizeResult<Item<S>> {
|
||||
let id = self.store.new_id()?;
|
||||
return Ok(Item {id, instance: self});
|
||||
}
|
||||
|
||||
pub fn get<I: Into<MizeId>>(self, id: I) -> MizeResult<Item<S>> {
|
||||
return Ok(Item {id: id.into(), instance: self});
|
||||
}
|
||||
|
||||
pub fn set<I: Into<MizeId>, V: Into<ItemData>>(self, id: I, value: V) -> MizeResult<()> {
|
||||
self.store.set(id.into(), value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
27
src/core/instance/store.rs
Normal file
27
src/core/instance/store.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use crate::error::MizeResult;
|
||||
use crate::item::{Item, ItemData};
|
||||
use crate::id::MizeId;
|
||||
|
||||
pub trait Store {
|
||||
// should have the ability to be multi threaded, if the underlying implementation supports
|
||||
// multithreaded IO operations
|
||||
// for now all refs to a store hold a Mutex but this mutex is part of where the MizeStore trait
|
||||
// is implemented, so that there can be a multithreaded implementation
|
||||
//fn get(self, id: MizeId) -> MizeResult<Item<Self>> where Self: Sized;
|
||||
|
||||
fn set<T: Into<ItemData>>(self, id: MizeId, data: T) -> MizeResult<()>;
|
||||
|
||||
// in the future should implement transactions, ....
|
||||
|
||||
// funcs to do with links, backlinks
|
||||
fn get_links(self, item: Item<Self>) -> MizeResult<Vec<MizeId>> where Self: Sized;
|
||||
|
||||
fn get_backlinks(self, item: Item<Self>) -> MizeResult<Vec<MizeId>> where Self: Sized;
|
||||
|
||||
fn new_id(&self) -> MizeResult<MizeId>;
|
||||
}
|
||||
|
||||
/// references an item that is stored in a store
|
||||
pub trait StoreRef {
|
||||
// do we need this or is this basically an item
|
||||
}
|
||||
3
src/core/instance/subscription.rs
Normal file
3
src/core/instance/subscription.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
pub struct Subscription;
|
||||
@@ -1,14 +1,39 @@
|
||||
use log::{trace, debug, info, warn, error};
|
||||
use std::{collections::HashMap, path::PathBuf, io::Cursor, fmt::Display};
|
||||
use serde_cbor::Value as CborValue;
|
||||
use std::fs::File;
|
||||
use tokio::sync::Mutex;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::error::{MizeError, MizeResult, IntoMizeResult};
|
||||
use crate::itemstore::Itemstore;
|
||||
use crate::instance::Instance;
|
||||
use crate::instance::store::Store;
|
||||
use crate::id::MizeId;
|
||||
use cbor::Cbor as CborValue;
|
||||
|
||||
|
||||
// a item always has to do with a Instance, which takes care of how it is updated
|
||||
pub struct Item<S: Store + Sized> {
|
||||
pub id: MizeId,
|
||||
pub instance: Instance<S>
|
||||
}
|
||||
|
||||
// without an Instance it is not an item, but only the "data of an item"
|
||||
// and this type for now is just an alias to CborValue
|
||||
pub type ItemData = CborValue;
|
||||
|
||||
impl<S: Store> Item<S> {
|
||||
pub fn id(self) -> MizeId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn value_raw(self) -> MizeResult<Vec<u8>> {
|
||||
// this will call from the instance which gets the value from the store
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
#[derive(Debug)]
|
||||
pub enum Item {
|
||||
Cbor(ItemCbor),
|
||||
@@ -233,4 +258,5 @@ impl Item {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
3
src/core/lib.rs
Normal file
3
src/core/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
#![ allow( warnings ) ]
|
||||
|
||||
35
src/core/memstore.rs
Normal file
35
src/core/memstore.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use std::collections::HashMap;
|
||||
use cbor::Cbor as CborValue;
|
||||
|
||||
use crate::id::MizeId;
|
||||
use crate::instance::store::Store;
|
||||
use crate::error::MizeResult;
|
||||
use crate::item::{Item, ItemData};
|
||||
|
||||
pub struct MemStore {
|
||||
map: HashMap<MizeId, CborValue>,
|
||||
next_id: u64,
|
||||
}
|
||||
|
||||
impl Store for MemStore {
|
||||
fn set<T: Into<ItemData>>(mut self, id: MizeId, data: T) -> MizeResult<()> {
|
||||
self.map.insert(id, data.into());
|
||||
return Ok(())
|
||||
}
|
||||
fn get_links(self, item: Item<Self>) -> MizeResult<Vec<MizeId>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
fn get_backlinks(self, item: Item<Self>) -> MizeResult<Vec<MizeId>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
fn new_id(&self) -> MizeResult<MizeId> {
|
||||
let id_string = format!("{}", self.next_id);
|
||||
return Ok(id_string.into());
|
||||
}
|
||||
}
|
||||
|
||||
impl MemStore {
|
||||
pub fn new() -> MemStore {
|
||||
return MemStore { map: HashMap::new(), next_id: 0 };
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
use serde_cbor::Value as CborValue;
|
||||
|
||||
use cbor::Cbor as CborValue;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MizeMessage {
|
||||
0
src/core/types/mod.rs
Normal file
0
src/core/types/mod.rs
Normal file
@@ -1,192 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::fs::create_dir;
|
||||
use colored::Colorize;
|
||||
use log::{trace, debug, info, warn, error};
|
||||
use serde_json::json;
|
||||
use tokio::net::{UnixListener, UnixStream};
|
||||
use uuid::Uuid;
|
||||
use std::fs::File;
|
||||
use std::path::Path;
|
||||
use daemonize::Daemonize;
|
||||
use nix::unistd::Pid;
|
||||
|
||||
use crate::error::{MizeError, MizeResult, IntoMizeResult, MizeResultTrait};
|
||||
use crate::itemstore::{Itemstore, self};
|
||||
use crate::item::{Item, MizeId, ItemRef};
|
||||
|
||||
pub mod peer;
|
||||
pub mod connection;
|
||||
|
||||
pub use connection::Connection;
|
||||
|
||||
static UNIX_SOCKET_CHANNEL_SIZE: usize = 200;
|
||||
static WEB_SOCKET_CHANNEL_SIZE: usize = 200;
|
||||
static TCP_SOCKET_CHANNEL_SIZE: usize = 200;
|
||||
static UPDATE_CHANNEL_SIZE: usize = 200;
|
||||
|
||||
|
||||
/// The Instance type is the heart of the mize system
|
||||
pub struct Instance {
|
||||
store: Itemstore,
|
||||
peers: Vec<Connection>,
|
||||
realm_instance: Option<(RealmId, Vec<Connection>)>,
|
||||
}
|
||||
|
||||
pub enum RealmId {
|
||||
Uuid(Uuid),
|
||||
Local(Vec<String>),
|
||||
Tld(Vec<String>),
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub async fn new(instance_path: PathBuf) -> MizeResult<Instance> {
|
||||
trace!("[ {} ] Instance::new()", "CALL".yellow());
|
||||
trace!("[ {} ] instance_path: {:?}", "ARG".yellow(), instance_path);
|
||||
|
||||
// if path does not exist, create it
|
||||
if instance_path.exists() == false {
|
||||
debug!("instance path \"{}\" does not exist, so creating it", instance_path.clone().display());
|
||||
File::create(&instance_path)
|
||||
.mize_result_msg(format!("Could not create Instance at: {}", &instance_path.display())).critical();
|
||||
}
|
||||
|
||||
// if a daemon is running, return instance with memory store
|
||||
// with realm_instance at that daemon
|
||||
let mut instance = match find_running_ademon(instance_path.clone()).await {
|
||||
Ok(con) => {
|
||||
Instance {
|
||||
store: Itemstore::Memory(itemstore::new_memory_store()?),
|
||||
peers: vec![con],
|
||||
realm_instance: None,
|
||||
}
|
||||
},
|
||||
|
||||
// else open the itemstore
|
||||
Err(err) => {
|
||||
Instance {
|
||||
store: Itemstore::Folder(itemstore::new_folder_store(instance_path)?),
|
||||
peers: vec![],
|
||||
realm_instance: None,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if !instance.store.has_item(MizeId::main(1)).await? {
|
||||
instance.store.create_item_from_json(json!({
|
||||
"item": {
|
||||
"kind": "instance",
|
||||
},
|
||||
"daemon": {
|
||||
"log": "",
|
||||
"pid": "",
|
||||
"item": {
|
||||
"kind": "instance/daemon",
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
return Ok(instance);
|
||||
|
||||
}
|
||||
|
||||
pub async fn start_deamon(mut self) -> MizeResult<()> {
|
||||
trace!("[ {} ] Instance::start_deamon()", "CALL".yellow());
|
||||
// daemonize
|
||||
let stdout = self.get_item(MizeId::main(1).join("daemon").join("log")).await?.value_as_file()?;
|
||||
|
||||
let pid_file = self.get_item(MizeId::main(1).join("daemon").join("pid")).await?.value_as_path()?;
|
||||
|
||||
let daemonize = Daemonize::new()
|
||||
.pid_file(pid_file)
|
||||
//.chown_pid_file(true) // is optional, see `Daemonize` documentation
|
||||
//.working_directory("/tmp") // for default behaviour.
|
||||
//.user("nobody")
|
||||
//.group("daemon") // Group name
|
||||
//.group(2) // or group id.
|
||||
//.umask(0o777) // Set umask, `0o027` by default.
|
||||
.stdout(stdout); // Redirect stdout to `/tmp/daemon.out`.
|
||||
//.privileged_action(|| "Executed before drop privileges");
|
||||
|
||||
daemonize.start().mize_result_msg("Error daemonizing")?;
|
||||
|
||||
info!("Succesfuly Daemonized");
|
||||
|
||||
// listen on unix socket
|
||||
let sock_path = self.get_item(MizeId::main(1).join("daemon").join("sock")).await?.value_as_path()?;
|
||||
let listener = UnixListener::bind(&sock_path)
|
||||
.mize_result_msg(format!("Could not bind to unix Socket at: {}", sock_path.display())).critical();
|
||||
loop {
|
||||
match listener.accept().await {
|
||||
Ok((unix_sock, _addr)) => {
|
||||
self.peers.push(Connection::from_unix(unix_sock))
|
||||
},
|
||||
Err(e) => { MizeError::new().msg("New Peer failed").msg(format!("{}", e)).log(); },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_daemon_pid(self) -> MizeResult<Pid> {
|
||||
trace!("[ {} ] Instance::get_daemon_pid()", "CALL".yellow());
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn daemon_is_running(&self) -> MizeResult<bool> {
|
||||
trace!("[ {} ] Instance::daemon_is_running()", "CALL".yellow());
|
||||
let sock_path = self.get_item(MizeId::main(1).join("daemon").join("sock")).await?.value_as_path()?;
|
||||
match find_running_ademon(sock_path).await {
|
||||
Ok(_) => Ok(true),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// item funcs
|
||||
pub async fn get_item(&self, id: MizeId) -> MizeResult<Item> {
|
||||
self.store.get_item(id).await
|
||||
}
|
||||
pub async fn set_item(&mut self, item: Item) -> MizeResult<()> {
|
||||
self.store.set_item(item).await
|
||||
}
|
||||
pub async fn create_item(&mut self) -> MizeResult<MizeId> {
|
||||
self.store.create_item().await
|
||||
}
|
||||
|
||||
//pub fn update_item(self) -> MizeResult<Item> {
|
||||
//todo!()
|
||||
//}
|
||||
//pub fn on_item_update(self) -> MizeResult<Item> {
|
||||
//todo!()
|
||||
//}
|
||||
}
|
||||
|
||||
async fn find_running_ademon(path: PathBuf) -> MizeResult<Connection> {
|
||||
match UnixStream::connect(path).await {
|
||||
Ok(val) => Ok(Connection::from_unix(val)),
|
||||
Err(err) => Err(MizeError::new().msg("No Daemon found to connect")),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn handle_connection() {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/*
|
||||
fn get_instance_type() -> MizeResult<Item> {
|
||||
|
||||
let mut types_item = Item::new("types");
|
||||
|
||||
// instance
|
||||
let instance_str = include_str!("../../types/mize/instance/item.json");
|
||||
let instance = Item::new("type").load_json(instance_str)?;
|
||||
types_item.set("instance", instance);
|
||||
|
||||
return Ok(types_item);
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
268
src/itemstore.rs
268
src/itemstore.rs
@@ -1,268 +0,0 @@
|
||||
use log::{trace, debug, info, warn, error};
|
||||
use std::fs::{self, File};
|
||||
use std::path::{PathBuf, Path};
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Read, Write};
|
||||
use colored::Colorize;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::item::{Item, MizeId, ItemCbor, ItemRef};
|
||||
use crate::error::{MizeError, MizeResult, IntoMizeResult};
|
||||
|
||||
pub enum Itemstore {
|
||||
Folder(FolderItemstore),
|
||||
Memory(MemoryItemstore),
|
||||
}
|
||||
|
||||
pub struct FolderItemstore {
|
||||
path: PathBuf,
|
||||
next_id: u64,
|
||||
}
|
||||
|
||||
pub struct MemoryItemstore {
|
||||
inner: HashMap<MizeId, Item>,
|
||||
next_id: u64,
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
pub fn new_memory_store() -> MizeResult<MemoryItemstore> {
|
||||
Ok(MemoryItemstore {
|
||||
inner: HashMap::new(),
|
||||
next_id: 100,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_folder_store(path: PathBuf) -> MizeResult<FolderItemstore> {
|
||||
if path.join("store_open").exists() {
|
||||
let pid_str = fs::read_to_string(path.join("store_open"))
|
||||
.mize_result_msg("Could not read the store_open file to check which process has the store opened")?;
|
||||
let pid = pid_str.parse::<u32>()
|
||||
.mize_result_msg("Could not Parse the content of the store_open file into a u32")?;
|
||||
|
||||
if Path::new("/proc").join(format!("{}", pid)).exists() {
|
||||
return Err(MizeError::new().msg(
|
||||
format!("the store at {} is already open by process with pid: {}", path.display(), pid)
|
||||
));
|
||||
} else {
|
||||
warn!("An Instance was not closed properly. (store_open file contained a not running PID)");
|
||||
std::fs::remove_file(path.join("store_open"))
|
||||
.mize_result_msg("Could not remove the store_open file")?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut store_open_file = File::create(path.join("store_open"))
|
||||
.mize_result_msg("Could not create the store_open file")?;
|
||||
|
||||
store_open_file.write_all(format!("{}", std::process::id()).as_bytes())
|
||||
.mize_result_msg("Could not write my pid to the store_open file")?;
|
||||
|
||||
// if no store_next_id exists write it with the default 100
|
||||
let mut next_id: u64 = 100; // as a default
|
||||
if !path.join("store_next_id").exists() {
|
||||
fs::write(path.join("store_next_id"), format!("{}", next_id).as_bytes())
|
||||
.mize_result_msg("Could not write the default next_id to store_next_id")?;
|
||||
} else {
|
||||
let next_id_string = fs::read_to_string(path.join("store_next_id"))
|
||||
.mize_result_msg("Could not read store_next_id of this store")?;
|
||||
|
||||
next_id = next_id_string.parse()
|
||||
.mize_result_msg("Could not parse contents of store_next_id into u64")?;
|
||||
}
|
||||
|
||||
return Ok(FolderItemstore{ path, next_id});
|
||||
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
impl FolderItemstore {
|
||||
pub async fn write_next_id(&self) -> MizeResult<()> {
|
||||
fs::write(self.path.join("store_next_id"), format!("{}", self.next_id).as_bytes())
|
||||
.mize_result_msg(format!("Could not write the next_id: \"{}\" to \"{}\"", self.next_id, self.path.join("store_next_id").display()))?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
impl MemoryItemstore {
|
||||
// item funcs
|
||||
async fn get_item(&self, id: MizeId) -> MizeResult<Item> {
|
||||
match self.inner.get(&id) {
|
||||
None => {
|
||||
Err(MizeError::new().msg(format!("there was no item with id ({}) in this store", id)))
|
||||
},
|
||||
Some(item) => {
|
||||
Ok(Item::Cbor(item.as_cbor()?))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_item(&mut self, item: Item) -> MizeResult<()> {
|
||||
self.inner.insert(item.id()?, item)
|
||||
.ok_or(MizeError::new().msg("Could not set an item in a MemoryStore"))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
async fn create_item(&mut self) -> MizeResult<MizeId> {
|
||||
// advance the next_id
|
||||
self.next_id += 1;
|
||||
return Ok(MizeId::main(self.next_id))
|
||||
}
|
||||
|
||||
//fn update_item(self) -> MizeResult<I>;
|
||||
|
||||
// other funcs
|
||||
async fn close(&self) -> MizeResult<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
async fn next_id(&self) -> u64 {
|
||||
self.next_id
|
||||
}
|
||||
|
||||
async fn has_item(&self, id: MizeId) -> MizeResult<bool> {
|
||||
Ok(self.inner.contains_key(&id))
|
||||
}
|
||||
}
|
||||
|
||||
impl FolderItemstore {
|
||||
// item funcs
|
||||
async fn get_item(&self, id: MizeId) -> MizeResult<Item> {
|
||||
let id_string = format!("{}", id.main);
|
||||
let mut final_path = self.path.join("store");
|
||||
if id_string.len() < 2 {
|
||||
// use path 00 for ids under 10
|
||||
final_path.push("00")
|
||||
} else {
|
||||
// will not panic, because of prior check if string is more than two long
|
||||
final_path.push(id_string[..2].to_owned());
|
||||
};
|
||||
|
||||
return Ok(Item::Descriptor(ItemRef { path: final_path, id}));
|
||||
}
|
||||
|
||||
async fn set_item(&self, item: Item) -> MizeResult<()> {
|
||||
item.write_to_path(self.get_path_of_item(&item.id()?)?)
|
||||
}
|
||||
|
||||
async fn create_item(&mut self) -> MizeResult<MizeId> {
|
||||
// advance the next_id
|
||||
self.next_id += 1;
|
||||
match self.write_next_id().await {
|
||||
// on error revert to old next_id
|
||||
Ok(_) => {},
|
||||
Err(err) => {
|
||||
self.next_id -= 1;
|
||||
return Err(err);
|
||||
},
|
||||
}
|
||||
return Ok(MizeId::main(self.next_id));
|
||||
}
|
||||
|
||||
//fn update_item(self) -> MizeResult<Item>;
|
||||
|
||||
// other funcs
|
||||
async fn close(&self) -> MizeResult<()> {
|
||||
if self.path.join("store_open").exists() {
|
||||
std::fs::remove_file(self.path.join("store_open"))
|
||||
.mize_result_msg("Could not remove the store_open file")?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
async fn next_id(&self) -> u64 {
|
||||
self.next_id
|
||||
}
|
||||
async fn has_item(&self, id: MizeId) -> MizeResult<bool> {
|
||||
Ok(self.get_path_of_item(&id)?.exists())
|
||||
}
|
||||
|
||||
fn get_path_of_item(&self, id: &MizeId) -> MizeResult<PathBuf> {
|
||||
// get path of item
|
||||
let id_string = format!("{}", id.main);
|
||||
let mut final_path = self.path.join("store");
|
||||
if id_string.len() < 2 {
|
||||
// use path 00 for ids under 10
|
||||
final_path.push("00")
|
||||
} else {
|
||||
// will not panic, because of prior check if string is more than two long
|
||||
final_path.push(id_string[..2].to_owned());
|
||||
};
|
||||
return Ok(final_path);
|
||||
}
|
||||
}
|
||||
|
||||
impl Itemstore {
|
||||
// item funcs
|
||||
pub async fn get_item(&self, id: MizeId) -> MizeResult<Item> {
|
||||
trace!("[ {} ] Itemstore::get_item()", "CALL".yellow());
|
||||
trace!("[ {} ] id: {}", "ARG".yellow(), id);
|
||||
|
||||
match self {
|
||||
Itemstore::Folder(val) => val.get_item(id).await,
|
||||
Itemstore::Memory(val) => val.get_item(id).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_item(&mut self, item: Item) -> MizeResult<()> {
|
||||
trace!("[ {} ] Itemstore::set_item()", "CALL".yellow());
|
||||
trace!("[ {} ] item: {:?}", "ARG".yellow(), item);
|
||||
|
||||
match self {
|
||||
Itemstore::Folder(val) => val.set_item(item).await,
|
||||
Itemstore::Memory(val) => val.set_item(item).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_item(&mut self) -> MizeResult<MizeId> {
|
||||
trace!("[ {} ] Itemstore::create_item()", "CALL".yellow());
|
||||
|
||||
match self {
|
||||
Itemstore::Folder(val) => val.create_item().await,
|
||||
Itemstore::Memory(val) => val.create_item().await,
|
||||
}
|
||||
}
|
||||
|
||||
//pub fn update_item(self) -> MizeResult<I>;
|
||||
|
||||
// other funcs
|
||||
pub async fn close(&self) -> MizeResult<()> {
|
||||
trace!("[ {} ] Itemstore::close()", "CALL".yellow());
|
||||
match self {
|
||||
Itemstore::Folder(val) => val.close().await,
|
||||
Itemstore::Memory(val) => val.close().await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn next_id(&self) -> u64 {
|
||||
trace!("[ {} ] Itemstore::next_id()", "CALL".yellow());
|
||||
|
||||
match self {
|
||||
Itemstore::Folder(val) => val.next_id().await,
|
||||
Itemstore::Memory(val) => val.next_id().await,
|
||||
}
|
||||
}
|
||||
pub async fn has_item(&self, id: MizeId) -> MizeResult<bool> {
|
||||
trace!("[ {} ] Itemstore::has_item()", "CAL".yellow());
|
||||
trace!("[ {} ] id: {}", "ARG".yellow(), id);
|
||||
|
||||
let ret = match self {
|
||||
Itemstore::Folder(val) => val.has_item(id).await,
|
||||
Itemstore::Memory(val) => val.has_item(id).await,
|
||||
};
|
||||
|
||||
if let Ok(val) = ret {trace!("[ {} ] Itemstore::has_item(): {}", "RET".yellow(), val)};
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
18
src/lib.rs
18
src/lib.rs
@@ -3,14 +3,18 @@
|
||||
|
||||
static PROTO_VERSION: u8 = 1;
|
||||
|
||||
pub mod core {
|
||||
pub mod memstore;
|
||||
pub mod instance;
|
||||
pub mod item;
|
||||
pub mod id;
|
||||
pub mod proto;
|
||||
pub mod error;
|
||||
pub mod types;
|
||||
}
|
||||
|
||||
pub use core::*;
|
||||
|
||||
// pub mod types;
|
||||
|
||||
pub mod error;
|
||||
mod instance;
|
||||
mod proto;
|
||||
mod item;
|
||||
mod itemstore;
|
||||
|
||||
|
||||
|
||||
|
||||
4
src/platform/os/cli/call.rs
Normal file
4
src/platform/os/cli/call.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub fn call(sub_matches: &ArgMatches) {
|
||||
}
|
||||
@@ -6,11 +6,13 @@ use std::fs::File;
|
||||
use log::{trace, debug, info, warn, error};
|
||||
use nix::sys::signal::{self, Signal};
|
||||
|
||||
use crate::error::{MizeResultTrait, IntoMizeResult, MizeError};
|
||||
use crate::instance::Instance;
|
||||
use mize::error::{MizeResultTrait, IntoMizeResult, MizeError};
|
||||
use mize::instance::Instance;
|
||||
|
||||
|
||||
pub async fn daemon(sub_matches: &ArgMatches) {
|
||||
pub fn daemon(sub_matches: &ArgMatches) {
|
||||
println!("not implemented");
|
||||
/*
|
||||
let instance_path = match sub_matches.get_one::<String>("store") {
|
||||
Some(a) => PathBuf::from(a),
|
||||
None => {
|
||||
@@ -60,4 +62,5 @@ pub async fn daemon(sub_matches: &ArgMatches) {
|
||||
};
|
||||
},
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -3,12 +3,12 @@ use std::path::PathBuf;
|
||||
use clap::ArgMatches;
|
||||
use home::home_dir;
|
||||
|
||||
use crate::error::{MizeResultTrait, IntoMizeResult, MizeError};
|
||||
use crate::instance::Instance;
|
||||
use mize::error::{MizeResultTrait, IntoMizeResult, MizeError};
|
||||
use mize::instance::Instance;
|
||||
|
||||
|
||||
|
||||
pub async fn get(sub_matches: &ArgMatches) {
|
||||
pub fn get(sub_matches: &ArgMatches) {
|
||||
let instance_folder_path = match sub_matches.get_one::<String>("store") {
|
||||
Some(a) => PathBuf::from(a),
|
||||
None => {
|
||||
@@ -18,6 +18,7 @@ pub async fn get(sub_matches: &ArgMatches) {
|
||||
home_dir
|
||||
},
|
||||
};
|
||||
let instance = Instance::new(instance_folder_path);
|
||||
println!("unfinished");
|
||||
//let instance = Instance::new(instance_folder_path);
|
||||
}
|
||||
|
||||
4
src/platform/os/cli/mount.rs
Normal file
4
src/platform/os/cli/mount.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub fn mount(sub_matches: &ArgMatches) {
|
||||
}
|
||||
4
src/platform/os/cli/serve.rs
Normal file
4
src/platform/os/cli/serve.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub fn serve(sub_matches: &ArgMatches) {
|
||||
}
|
||||
4
src/platform/os/cli/set.rs
Normal file
4
src/platform/os/cli/set.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub fn set(sub_matches: &ArgMatches) {
|
||||
}
|
||||
4
src/platform/os/cli/show.rs
Normal file
4
src/platform/os/cli/show.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
use clap::ArgMatches;
|
||||
|
||||
pub fn show(sub_matches: &ArgMatches) {
|
||||
}
|
||||
0
src/platform/os/fsstore.rs
Normal file
0
src/platform/os/fsstore.rs
Normal file
@@ -21,34 +21,23 @@ mod cli {
|
||||
pub mod serve;
|
||||
pub mod get;
|
||||
pub mod set;
|
||||
pub mod show;
|
||||
pub mod mount;
|
||||
pub mod print;
|
||||
pub mod call;
|
||||
|
||||
pub use daemon::daemon;
|
||||
pub use serve::serve;
|
||||
pub use get::get;
|
||||
pub use set::set;
|
||||
pub use show::show;
|
||||
pub use mount::mount;
|
||||
pub use print::print;
|
||||
pub use call::call;
|
||||
}
|
||||
|
||||
mod error;
|
||||
// mod types;
|
||||
mod instance;
|
||||
mod item;
|
||||
mod itemstore;
|
||||
mod proto;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
static APPNAME: &str = "mize";
|
||||
static DEFAULT_LOG_LEVEL: LevelFilter = LevelFilter::Warn;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
fn main() {
|
||||
// welcome to the mize source code
|
||||
// this is the main entry point for platforms with an os (eg: Linux, MacOS, Windows, BSD, ...)
|
||||
// Also let's try to make the most best documented code ever. (eventually) so it's fun to work on
|
||||
@@ -64,25 +53,25 @@ async fn main() {
|
||||
// match command
|
||||
match cli_matches.subcommand() {
|
||||
// mi daemon
|
||||
Some(("daemon", sub_matches)) => cli::daemon(sub_matches).await,
|
||||
Some(("daemon", sub_matches)) => cli::daemon(sub_matches),
|
||||
|
||||
// mi serve
|
||||
Some(("serve", sub_matches)) => cli::serve(sub_matches).await,
|
||||
Some(("serve", sub_matches)) => cli::serve(sub_matches),
|
||||
|
||||
// mi mount
|
||||
Some(("mount", sub_matches)) => cli::mount(sub_matches).await,
|
||||
Some(("mount", sub_matches)) => cli::mount(sub_matches),
|
||||
|
||||
// mi get
|
||||
Some(("get", sub_matches)) => cli::get(sub_matches).await,
|
||||
Some(("get", sub_matches)) => cli::get(sub_matches),
|
||||
|
||||
// mi set
|
||||
Some(("set", sub_matches)) => cli::set(sub_matches).await,
|
||||
Some(("set", sub_matches)) => cli::set(sub_matches),
|
||||
|
||||
// mi sub
|
||||
Some(("sub", sub_matches)) => cli::sub(sub_matches).await,
|
||||
// mi show
|
||||
Some(("show", sub_matches)) => cli::show(sub_matches),
|
||||
|
||||
// mi call
|
||||
Some(("call", sub_matches)) => cli::call(sub_matches).await,
|
||||
Some(("call", sub_matches)) => cli::call(sub_matches),
|
||||
|
||||
// some unknown command passed
|
||||
Some((cmd, sub_matches)) => error!("The subcommand: {} is not known. use --help to list availavle commands", cmd),
|
||||
@@ -330,8 +319,8 @@ fn cli_matches() -> clap::ArgMatches {
|
||||
.arg(Arg::new("id").help("The id or path to set"))
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("sub")
|
||||
.aliases(["su"])
|
||||
Command::new("show")
|
||||
.aliases(["so"])
|
||||
.arg(&store_arg)
|
||||
.arg(Arg::new("id").help("The id or path to sub to and show"))
|
||||
)
|
||||
@@ -1,16 +1,3 @@
|
||||
use log::{trace, debug, info, warn, error};
|
||||
use tokio::{sync::mpsc::{Sender, Receiver}, net::UnixStream};
|
||||
use tokio::net::unix::{OwnedReadHalf, OwnedWriteHalf};
|
||||
use tokio::io::{self, Interest};
|
||||
|
||||
use crate::proto::MizeMessage;
|
||||
use crate::error::{MizeError, MizeResult, IntoMizeResult};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Connection {
|
||||
tx: Sender<MizeMessage>,
|
||||
rx: Receiver<MizeMessage>,
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn from_unix(unix_sock: UnixStream) -> Connection {
|
||||
31
src/test/mod.rs
Normal file
31
src/test/mod.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
use crate::instance::MizeInstance;
|
||||
|
||||
#[test]
|
||||
fn most_minimal_instance_usage() {
|
||||
// so I'm actually starting the effort for a better code base here in the tests
|
||||
// i want to write down how you would use the very important instance type
|
||||
// and then implement it to make it usable in this way
|
||||
|
||||
// how every mize based program to run on an os should create a mize instance
|
||||
// opens the instance the way the user has configured (with env vars, build args, or config
|
||||
// files)
|
||||
// and we just call it "new()" and not "default_system()" so that it is clear that this is the
|
||||
// method that should be used
|
||||
let mize = MizeInstance::new();
|
||||
|
||||
// then the most important things you do with your mize instance is to get/set stuff
|
||||
let name: MizeItem = mize.get("1/namespace");
|
||||
let id = mize.new_item();
|
||||
mize.set(id.join("name"), "test-item");
|
||||
|
||||
// or register update callbacks and update
|
||||
//mize.on_update("5/name", |delta| println!(delta.new_value()));
|
||||
//mize.update("5/name", "other-item")
|
||||
|
||||
// the goal is that also most things about the MizeInstance itself is controlled by
|
||||
// get/set/update -ing values at the 0/ paths
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user