Compare commits
No commits in common. "25ea7ed6524b0e80bcc97c28923e7bd38c501f57" and "ebbe3d82d6adcd64e4b216fd5aaeed6021484759" have entirely different histories.
25ea7ed652
...
ebbe3d82d6
64 changed files with 483 additions and 1144 deletions
|
@ -10,13 +10,11 @@ license = "MIT OR Apache-2.0"
|
||||||
repository = "https://forgejo.fireturlte.net/lazy-supplements"
|
repository = "https://forgejo.fireturlte.net/lazy-supplements"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
chrono = "0.4.41"
|
|
||||||
ciborium = "0.2.2"
|
ciborium = "0.2.2"
|
||||||
clap = { version = "4.5.38", features = ["derive"] }
|
clap = { version = "4.5.38", features = ["derive"] }
|
||||||
dioxus = { version = "0.6.0", features = [] }
|
dioxus = { version = "0.6.0", features = [] }
|
||||||
lazy-supplements-core.path = "lazy-supplements-core"
|
lazy-supplements-core.path = "lazy-supplements-core"
|
||||||
libp2p = { version = "0.55.0", features = ["macros", "mdns", "noise", "ping", "tcp", "tokio", "yamux" ] }
|
libp2p = { version = "0.55.0", features = ["macros", "mdns", "noise", "ping", "tcp", "tokio", "yamux" ] }
|
||||||
sea-orm = { version = "1.1.11", features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros", "with-chrono", "with-uuid"] }
|
|
||||||
sea-orm-migration = { version = "1.1.0", features = ["runtime-tokio-rustls", "sqlx-postgres"] }
|
sea-orm-migration = { version = "1.1.0", features = ["runtime-tokio-rustls", "sqlx-postgres"] }
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
|
|
|
@ -6,9 +6,7 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap.workspace = true
|
|
||||||
dioxus.workspace = true
|
dioxus.workspace = true
|
||||||
lazy-supplements-desktop.path = "../../lazy-supplements-desktop"
|
|
||||||
lazy-supplements-examples-core.path = "../core"
|
lazy-supplements-examples-core.path = "../core"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
use clap::{Parser, Subcommand};
|
|
||||||
use lazy_supplements_desktop::cli::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
pub struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: CliCommand
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
|
||||||
pub enum CliCommand {
|
|
||||||
Config(ConfigCommandArgs),
|
|
||||||
Device(DeviceCommandArgs),
|
|
||||||
Log(LogCommandArgs),
|
|
||||||
Server(ServerCommandArgs),
|
|
||||||
}
|
|
|
@ -1,5 +1,3 @@
|
||||||
mod cli;
|
|
||||||
mod ipc;
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus::launch(lazy_supplements_examples_core::ui::plain::App);
|
dioxus::launch(lazy_supplements_examples_core::ui::plain::App);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,23 +8,18 @@ repository.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
desktop = ["dep:clap", "macros"]
|
desktop = ["dep:clap"]
|
||||||
mobile = ["macros"]
|
test = ["dep:tempfile"]
|
||||||
macros = ["dep:lazy-supplements-macros"]
|
|
||||||
test = ["dep:tempfile", "macros"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
chrono.workspace = true
|
chrono = "0.4.41"
|
||||||
chrono-tz = "0.10.3"
|
chrono-tz = "0.10.3"
|
||||||
ciborium.workspace = true
|
ciborium.workspace = true
|
||||||
clap = {workspace = true, optional = true}
|
clap = {workspace = true, optional = true}
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
lazy-supplements-macros = { path = "../lazy-supplements-macros", optional = true }
|
|
||||||
libp2p.workspace = true
|
libp2p.workspace = true
|
||||||
libp2p-core = { version = "0.43.0", features = ["serde"] }
|
sea-orm = { version = "1.1.11", features = ["sqlx-sqlite", "runtime-tokio-native-tls", "macros", "with-chrono", "with-uuid"] }
|
||||||
libp2p-identity = { version = "0.2.11", features = ["ed25519", "peerid", "rand", "serde"] }
|
|
||||||
sea-orm.workspace = true
|
|
||||||
sea-orm-migration.workspace = true
|
sea-orm-migration.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
tempfile = { version = "3.20.0", optional = true }
|
tempfile = { version = "3.20.0", optional = true }
|
||||||
|
|
|
@ -10,11 +10,11 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::data::value::{MultiaddrValue, PeerIdValue};
|
use crate::data::value::{MultiaddrValue, PeerIdValue};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||||
#[sea_orm(table_name = "peer")]
|
#[sea_orm(table_name = "peer")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key)]
|
#[sea_orm(primary_key)]
|
||||||
pub id: Uuid,
|
pub id: u32,
|
||||||
#[sea_orm(indexed)]
|
#[sea_orm(indexed)]
|
||||||
pub created_at: DateTimeUtc,
|
pub created_at: DateTimeUtc,
|
||||||
#[sea_orm(indexed)]
|
#[sea_orm(indexed)]
|
||||||
|
|
|
@ -42,7 +42,7 @@ impl TableMigration for Peer {
|
||||||
Table::create()
|
Table::create()
|
||||||
.table(Self::Table)
|
.table(Self::Table)
|
||||||
.if_not_exists()
|
.if_not_exists()
|
||||||
.col(pk_uuid(Self::Id))
|
.col(pk_auto(Self::Id))
|
||||||
.col(string_len(Self::PeerId, 255))
|
.col(string_len(Self::PeerId, 255))
|
||||||
.col(timestamp(Self::CreatedAt))
|
.col(timestamp(Self::CreatedAt))
|
||||||
.col(timestamp(Self::UpdatedAt))
|
.col(timestamp(Self::UpdatedAt))
|
||||||
|
|
|
@ -2,10 +2,4 @@
|
||||||
pub enum ConfigError {
|
pub enum ConfigError {
|
||||||
#[error("missing config: {0}")]
|
#[error("missing config: {0}")]
|
||||||
MissingConfig(String),
|
MissingConfig(String),
|
||||||
#[error("Io error: {0}")]
|
|
||||||
Io(#[from] std::io::Error),
|
|
||||||
#[error("Toml Deserialization Error")]
|
|
||||||
TomlDerialization(#[from] toml::de::Error),
|
|
||||||
#[error("Toml Serialization Error")]
|
|
||||||
TomlSerialization(#[from] toml::ser::Error),
|
|
||||||
}
|
}
|
|
@ -3,31 +3,32 @@ mod storage;
|
||||||
mod p2p;
|
mod p2p;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use crate::{utils::{emptiable::Emptiable, mergeable::Mergeable}};
|
use crate::error::Error;
|
||||||
pub use error::ConfigError;
|
pub use error::ConfigError;
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
||||||
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}};
|
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}};
|
||||||
pub use storage::{StorageConfig, PartialStorageConfig};
|
pub use storage::{StorageConfig, PartialStorageConfig};
|
||||||
pub use p2p::{P2pConfig, PartialP2pConfig};
|
pub use p2p::{P2pConfig, PartialP2pConfig};
|
||||||
|
pub trait PartialConfig: Serialize + Sized + DeserializeOwned
|
||||||
|
{
|
||||||
|
|
||||||
pub trait Config: TryFrom<Self::PartialConfig>{
|
fn default() -> Self;
|
||||||
type PartialConfig: PartialConfig<Config = Self>;
|
fn empty() -> Self;
|
||||||
}
|
fn merge(&mut self, other: Self);
|
||||||
pub trait PartialConfig: Emptiable + From<Self::Config> + Mergeable {
|
|
||||||
type Config: Config<PartialConfig = Self>;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait BaseConfig: DeserializeOwned + Serialize {
|
|
||||||
fn new() -> Self;
|
|
||||||
fn from_toml(s: &str) -> Result<Self, toml::de::Error> {
|
fn from_toml(s: &str) -> Result<Self, toml::de::Error> {
|
||||||
toml::from_str(s)
|
toml::from_str(s)
|
||||||
}
|
}
|
||||||
fn into_toml(&self) -> Result<String, toml::ser::Error> {
|
fn into_toml(&self) -> Result<String, toml::ser::Error> {
|
||||||
toml::to_string(self)
|
toml::to_string(self)
|
||||||
}
|
}
|
||||||
async fn read_or_create<T>(path: T) -> Result<Self, ConfigError>
|
fn is_empty(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ConfigRoot: DeserializeOwned + Serialize {
|
||||||
|
fn new() -> Self;
|
||||||
|
|
||||||
|
async fn read_or_create<T>(path: T) -> Result<Self, Error>
|
||||||
where
|
where
|
||||||
T: AsRef<Path>
|
T: AsRef<Path>
|
||||||
{
|
{
|
||||||
|
@ -36,7 +37,7 @@ pub trait BaseConfig: DeserializeOwned + Serialize {
|
||||||
}
|
}
|
||||||
Self::read_from(&path).await
|
Self::read_from(&path).await
|
||||||
}
|
}
|
||||||
async fn read_from<T>(path:T) -> Result<Self, ConfigError>
|
async fn read_from<T>(path:T) -> Result<Self, Error>
|
||||||
where
|
where
|
||||||
T: AsRef<Path>
|
T: AsRef<Path>
|
||||||
{
|
{
|
||||||
|
@ -46,7 +47,7 @@ pub trait BaseConfig: DeserializeOwned + Serialize {
|
||||||
let config: Self = toml::from_str(&content)?;
|
let config: Self = toml::from_str(&content)?;
|
||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
async fn write_to<T>(&self, path:T) -> Result<(), ConfigError>
|
async fn write_to<T>(&self, path:T) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
T: AsRef<Path>
|
T: AsRef<Path>
|
||||||
{
|
{
|
||||||
|
@ -66,7 +67,7 @@ pub trait BaseConfig: DeserializeOwned + Serialize {
|
||||||
mod tests {
|
mod tests {
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{tests::test_toml_serialize_deserialize, utils::{emptiable::Emptiable, mergeable::Mergeable}};
|
use crate::tests::test_toml_serialize_deserialize;
|
||||||
|
|
||||||
use super::{p2p::{P2pConfig, PartialP2pConfig}, PartialConfig};
|
use super::{p2p::{P2pConfig, PartialP2pConfig}, PartialConfig};
|
||||||
|
|
||||||
|
@ -76,14 +77,13 @@ mod tests {
|
||||||
p2p: Option<PartialP2pConfig>
|
p2p: Option<PartialP2pConfig>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TestConfig {
|
impl PartialConfig for TestConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
p2p: Some(PartialP2pConfig::default()),
|
p2p: Some(PartialP2pConfig::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl Emptiable for TestConfig {
|
|
||||||
fn empty() -> Self {
|
fn empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
p2p: None,
|
p2p: None,
|
||||||
|
@ -93,8 +93,7 @@ mod tests {
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
self.p2p.is_none()
|
self.p2p.is_none()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
impl Mergeable for TestConfig {
|
|
||||||
fn merge(&mut self, other: Self) {
|
fn merge(&mut self, other: Self) {
|
||||||
if let Some(p2p) = other.p2p {
|
if let Some(p2p) = other.p2p {
|
||||||
self.p2p = Some(p2p);
|
self.p2p = Some(p2p);
|
||||||
|
|
|
@ -3,8 +3,7 @@ use std::{net::{IpAddr, Ipv4Addr}, ops, path::{Path, PathBuf}};
|
||||||
use base64::{prelude::BASE64_STANDARD, Engine};
|
use base64::{prelude::BASE64_STANDARD, Engine};
|
||||||
#[cfg(feature="desktop")]
|
#[cfg(feature="desktop")]
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use futures::StreamExt;
|
use libp2p::{identity::{self, DecodingError, Keypair}, noise, ping, tcp, yamux, Swarm};
|
||||||
use libp2p::{identity::{self, DecodingError, Keypair}, noise, ping, swarm::SwarmEvent, tcp, yamux, Swarm};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}};
|
use tokio::{fs::File, io::{AsyncReadExt, AsyncWriteExt}};
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
@ -12,7 +11,7 @@ use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::PartialConfig,
|
config::PartialConfig,
|
||||||
error::Error, p2p, utils::{emptiable::Emptiable, mergeable::Mergeable}
|
error::Error, p2p
|
||||||
};
|
};
|
||||||
|
|
||||||
static DEFAULT_P2P_LISTEN_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))];
|
static DEFAULT_P2P_LISTEN_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))];
|
||||||
|
@ -31,7 +30,7 @@ fn base64_to_keypair(base64: &str) -> Result<Keypair, Error> {
|
||||||
Ok(Keypair::from_protobuf_encoding(&vec)?)
|
Ok(Keypair::from_protobuf_encoding(&vec)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize,)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct P2pConfig {
|
pub struct P2pConfig {
|
||||||
#[serde(with = "keypair_parser")]
|
#[serde(with = "keypair_parser")]
|
||||||
pub secret: Keypair,
|
pub secret: Keypair,
|
||||||
|
@ -40,7 +39,7 @@ pub struct P2pConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl P2pConfig {
|
impl P2pConfig {
|
||||||
async fn try_into_swarm (self) -> Result<Swarm<p2p::Behaviour>, Error> {
|
pub async fn try_into_swarm (self) -> Result<Swarm<p2p::Behaviour>, Error> {
|
||||||
let mut swarm = libp2p::SwarmBuilder::with_existing_identity(self.secret)
|
let mut swarm = libp2p::SwarmBuilder::with_existing_identity(self.secret)
|
||||||
.with_tokio()
|
.with_tokio()
|
||||||
.with_tcp(
|
.with_tcp(
|
||||||
|
@ -53,22 +52,6 @@ impl P2pConfig {
|
||||||
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
|
swarm.listen_on("/ip4/0.0.0.0/tcp/0".parse()?)?;
|
||||||
Ok(swarm)
|
Ok(swarm)
|
||||||
}
|
}
|
||||||
pub async fn launch_swarm(self) -> Result<(), Error>{
|
|
||||||
let mut swarm = self.try_into_swarm().await?;
|
|
||||||
loop{
|
|
||||||
let swarm_event = swarm.select_next_some().await;
|
|
||||||
tokio::spawn(async move{
|
|
||||||
match swarm_event {
|
|
||||||
SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {address:?}"),
|
|
||||||
SwarmEvent::Behaviour(event) => {
|
|
||||||
println!("{event:?}");
|
|
||||||
event.run().await;
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<PartialP2pConfig> for P2pConfig {
|
impl TryFrom<PartialP2pConfig> for P2pConfig {
|
||||||
|
@ -113,10 +96,44 @@ pub struct PartialP2pConfig {
|
||||||
pub port: Option<u16>,
|
pub port: Option<u16>,
|
||||||
}
|
}
|
||||||
impl PartialP2pConfig {
|
impl PartialP2pConfig {
|
||||||
|
|
||||||
pub fn with_new_secret(mut self) -> Self {
|
pub fn with_new_secret(mut self) -> Self {
|
||||||
self.secret = Some(keypair_to_base64(&Keypair::generate_ed25519()));
|
self.secret = Some(keypair_to_base64(&Keypair::generate_ed25519()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
pub async fn read_or_create<T>(path: T) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
T: AsRef<Path>
|
||||||
|
{
|
||||||
|
if !path.as_ref().exists() {
|
||||||
|
Self::empty().write_to(&path).await?;
|
||||||
|
}
|
||||||
|
Self::read_from(&path).await
|
||||||
|
}
|
||||||
|
pub async fn read_from<T>(path:T) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
T: AsRef<Path>
|
||||||
|
{
|
||||||
|
let mut file = File::open(path.as_ref()).await?;
|
||||||
|
let mut content = String::new();
|
||||||
|
file.read_to_string(&mut content).await?;
|
||||||
|
let config: Self = toml::from_str(&content)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
pub async fn write_to<T>(&self, path:T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: AsRef<Path>
|
||||||
|
{
|
||||||
|
if !path.as_ref().exists() {
|
||||||
|
if let Some(x) = path.as_ref().parent() {
|
||||||
|
std::fs::create_dir_all(x)?;
|
||||||
|
};
|
||||||
|
let _ = File::create(&path).await?;
|
||||||
|
}
|
||||||
|
let mut file = File::create(&path).await?;
|
||||||
|
file.write_all(toml::to_string(self)?.as_bytes()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<P2pConfig> for PartialP2pConfig {
|
impl From<P2pConfig> for PartialP2pConfig {
|
||||||
|
@ -129,7 +146,29 @@ impl From<P2pConfig> for PartialP2pConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PartialP2pConfig {
|
impl PartialConfig for PartialP2pConfig {
|
||||||
|
fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
secret: None,
|
||||||
|
listen_ips: None,
|
||||||
|
port: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.secret.is_none() && self.listen_ips.is_none() && self.port.is_none()
|
||||||
|
}
|
||||||
|
fn merge(&mut self, another: Self) {
|
||||||
|
if let Some(x) = another.secret {
|
||||||
|
self.secret = Some(x);
|
||||||
|
};
|
||||||
|
if let Some(x) = another.listen_ips {
|
||||||
|
self.listen_ips = Some(x);
|
||||||
|
};
|
||||||
|
if let Some(x) = another.port {
|
||||||
|
self.port = Some(x);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
secret: None,
|
secret: None,
|
||||||
|
@ -139,33 +178,7 @@ impl Default for PartialP2pConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Emptiable for PartialP2pConfig {
|
|
||||||
fn empty() -> Self {
|
|
||||||
Self{
|
|
||||||
secret: None,
|
|
||||||
listen_ips: None,
|
|
||||||
port: None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.secret.is_none() && self.listen_ips.is_none() && self.port.is_none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mergeable for PartialP2pConfig {
|
|
||||||
fn merge(&mut self, mut other: Self) {
|
|
||||||
if let Some(x) = other.secret.take() {
|
|
||||||
let _ = self.secret.insert(x);
|
|
||||||
};
|
|
||||||
if let Some(x) = other.listen_ips.take() {
|
|
||||||
let _ = self.listen_ips.insert(x);
|
|
||||||
};
|
|
||||||
if let Some(x) = other.port.take() {
|
|
||||||
let _ = self.port.insert(x);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -5,7 +5,7 @@ use clap::Args;
|
||||||
|
|
||||||
#[cfg(any(test, feature="test"))]
|
#[cfg(any(test, feature="test"))]
|
||||||
use tempfile::tempdir;
|
use tempfile::tempdir;
|
||||||
use crate::{config::{ConfigError, PartialConfig}, utils::{emptiable::Emptiable, mergeable::Mergeable}};
|
use crate::{config::{ConfigError, PartialConfig}};
|
||||||
use libp2p::mdns::Config;
|
use libp2p::mdns::Config;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -67,25 +67,25 @@ impl From<StorageConfig> for PartialStorageConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Emptiable for PartialStorageConfig {
|
impl PartialConfig for PartialStorageConfig {
|
||||||
fn empty() -> Self {
|
fn empty() -> Self {
|
||||||
Self {
|
Self{
|
||||||
data_directory: None,
|
data_directory: None,
|
||||||
cache_directory: None
|
cache_directory: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
self.data_directory.is_none() && self.cache_directory.is_none()
|
self.data_directory.is_none() && self.cache_directory.is_none()
|
||||||
}
|
}
|
||||||
}
|
fn default() -> Self {
|
||||||
impl Mergeable for PartialStorageConfig {
|
todo!()
|
||||||
fn merge(&mut self, mut other: Self) {
|
}
|
||||||
if let Some(x) = other.data_directory.take() {
|
fn merge(&mut self, other: Self) {
|
||||||
let _ = self.data_directory.insert(x);
|
if let Some(x) = other.data_directory {
|
||||||
};
|
self.data_directory = Some(x);
|
||||||
if let Some(x) = other.cache_directory.take() {
|
}
|
||||||
let _ = self.cache_directory.insert(x);
|
if let Some(x) = other.cache_directory {
|
||||||
};
|
self.cache_directory = Some(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
mod trusted_node;
|
mod trusted_peer;
|
||||||
mod record_deletion;
|
mod record_deletion;
|
||||||
|
|
||||||
pub use trusted_node::{
|
pub use trusted_peer::{
|
||||||
ActiveModel as TrustedNodeActiveModel,
|
ActiveModel as TrustedPeerActiveModel,
|
||||||
Column as TrustedNodeColumn,
|
Column as TrustedPeerColumn,
|
||||||
Entity as TrustedNodeEntity,
|
Entity as TrustedPeerEntity,
|
||||||
Model as TrustedNodeModel,
|
Model as TrustedPeerModel,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use record_deletion::{
|
pub use record_deletion::{
|
||||||
|
|
|
@ -4,21 +4,15 @@ use sea_orm::entity::{
|
||||||
prelude::*
|
prelude::*
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::data::syncable::*;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||||
#[cfg_attr(feature="macros", derive(SyncableModel))]
|
|
||||||
#[sea_orm(table_name = "record_deletion")]
|
#[sea_orm(table_name = "record_deletion")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
#[cfg_attr(feature="macros", syncable(id))]
|
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
#[sea_orm(indexed)]
|
#[sea_orm(indexed)]
|
||||||
#[cfg_attr(feature="macros", syncable(timestamp))]
|
|
||||||
pub created_at: DateTimeUtc,
|
pub created_at: DateTimeUtc,
|
||||||
#[cfg_attr(feature="macros", syncable(author_id))]
|
|
||||||
pub created_by: Uuid,
|
|
||||||
pub table_name: String,
|
pub table_name: String,
|
||||||
pub record_id: Uuid,
|
pub record_id: Uuid,
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::data::value::PeerIdValue;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
|
||||||
#[sea_orm(table_name = "trusted_node")]
|
#[sea_orm(table_name = "trusted_peer")]
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
#[sea_orm(primary_key, auto_increment = false)]
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
|
@ -8,20 +8,20 @@ pub struct Migration;
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl MigrationTrait for Migration {
|
impl MigrationTrait for Migration {
|
||||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
TrustedNode::up(manager).await?;
|
TrustedPeer::up(manager).await?;
|
||||||
RecordDeletion::up(manager).await?;
|
RecordDeletion::up(manager).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
TrustedNode::down(manager).await?;
|
TrustedPeer::down(manager).await?;
|
||||||
RecordDeletion::down(manager).await?;
|
RecordDeletion::down(manager).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(DeriveIden)]
|
#[derive(DeriveIden)]
|
||||||
enum TrustedNode {
|
enum TrustedPeer {
|
||||||
Table,
|
Table,
|
||||||
Id,
|
Id,
|
||||||
CreatedAt,
|
CreatedAt,
|
||||||
|
@ -33,7 +33,7 @@ enum TrustedNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl TableMigration for TrustedNode {
|
impl TableMigration for TrustedPeer {
|
||||||
async fn up<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> {
|
async fn up<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> {
|
||||||
manager.create_table(
|
manager.create_table(
|
||||||
Table::create()
|
Table::create()
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
use sea_orm::{prelude::*, query::*, sea_query::SimpleExpr, *};
|
use sea_orm::{*, prelude::*, query::*};
|
||||||
#[cfg(feature="macros")]
|
|
||||||
pub use lazy_supplements_macros::SyncableModel;
|
|
||||||
pub trait SyncableModel: ModelTrait<Entity = Self::SyncableEntity> {
|
pub trait SyncableModel: ModelTrait<Entity = Self::SyncableEntity> {
|
||||||
type SyncableEntity: SyncableEntity<SyncableModel = Self>;
|
type SyncableEntity: SyncableEntity<SyncableModel = Self>;
|
||||||
fn get_timestamp(&self) -> DateTimeUtc;
|
fn get_updated_at(&self) -> DateTimeUtc;
|
||||||
fn get_id(&self) -> Uuid;
|
fn get_uuid(&self) -> Uuid;
|
||||||
fn get_author_id(&self) -> Uuid;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SyncableEntity: EntityTrait<
|
pub trait SyncableEntity: EntityTrait<
|
||||||
|
@ -17,17 +15,9 @@ pub trait SyncableEntity: EntityTrait<
|
||||||
type SyncableActiveModel: SyncableActiveModel<SyncableEntity= Self>;
|
type SyncableActiveModel: SyncableActiveModel<SyncableEntity= Self>;
|
||||||
type SyncableColumn: SyncableColumn;
|
type SyncableColumn: SyncableColumn;
|
||||||
|
|
||||||
async fn get_updated(from: DateTimeUtc, db: &DatabaseConnection) -> Result<Vec<<Self as EntityTrait>::Model>, SyncableError> {
|
async fn get_updated_after(date: DateTimeUtc, db: &DatabaseConnection) -> Result<Vec<<Self as EntityTrait>::Model>, SyncableError> {
|
||||||
let result: Vec<Self::SyncableModel> = <Self as EntityTrait>::find()
|
let result: Vec<Self::SyncableModel> = <Self as EntityTrait>::find()
|
||||||
.filter(Self::SyncableColumn::timestamp_after(from))
|
.filter(Self::SyncableColumn::updated_at().gte(date))
|
||||||
.all(db)
|
|
||||||
.await.unwrap();
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
async fn get_updated_by(author: Uuid, from: DateTimeUtc, db: &DatabaseConnection) -> Result<Vec<<Self as EntityTrait>::Model>, SyncableError> {
|
|
||||||
let result: Vec<Self::SyncableModel> = <Self as EntityTrait>::find()
|
|
||||||
.filter(Self::SyncableColumn::timestamp_after(from))
|
|
||||||
.filter(Self::SyncableColumn::author_id_eq(author))
|
|
||||||
.all(db)
|
.all(db)
|
||||||
.await.unwrap();
|
.await.unwrap();
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
@ -40,18 +30,15 @@ pub trait SyncableEntity: EntityTrait<
|
||||||
pub trait SyncableActiveModel: ActiveModelTrait<Entity = Self::SyncableEntity> {
|
pub trait SyncableActiveModel: ActiveModelTrait<Entity = Self::SyncableEntity> {
|
||||||
|
|
||||||
type SyncableEntity: SyncableEntity<SyncableActiveModel = Self>;
|
type SyncableEntity: SyncableEntity<SyncableActiveModel = Self>;
|
||||||
fn get_id(&self) -> Option<Uuid>;
|
fn get_uuid(&self) -> Option<Uuid>;
|
||||||
fn get_timestamp(&self) -> Option<DateTimeUtc>;
|
fn get_updated_at(&self) -> Option<DateTimeUtc>;
|
||||||
fn get_author_id(&self) -> Option<Uuid>;
|
|
||||||
fn try_merge(&mut self, other: <Self::SyncableEntity as SyncableEntity>::SyncableModel) -> Result<(), SyncableError> {
|
fn try_merge(&mut self, other: <Self::SyncableEntity as SyncableEntity>::SyncableModel) -> Result<(), SyncableError> {
|
||||||
if self.get_id().ok_or(SyncableError::MissingField("uuid"))? != other.get_id() {
|
if self.get_uuid().ok_or(SyncableError::MissingField("uuid"))? != other.get_uuid() {
|
||||||
return Err(SyncableError::MismatchUuid)
|
return Err(SyncableError::MismatchUuid)
|
||||||
}
|
}
|
||||||
if self.get_timestamp().ok_or(SyncableError::MissingField("updated_at"))? < other.get_timestamp() {
|
if self.get_updated_at().ok_or(SyncableError::MissingField("updated_at"))? < other.get_updated_at() {
|
||||||
for column in <<<Self as ActiveModelTrait>::Entity as EntityTrait>::Column as Iterable>::iter() {
|
for column in <<<Self as ActiveModelTrait>::Entity as EntityTrait>::Column as Iterable>::iter() {
|
||||||
if column.should_synced(){
|
self.take(column).set_if_not_equals(other.get(column));
|
||||||
self.take(column).set_if_not_equals(other.get(column));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -60,12 +47,10 @@ pub trait SyncableActiveModel: ActiveModelTrait<Entity = Self::SyncableEntity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SyncableColumn: ColumnTrait {
|
pub trait SyncableColumn: ColumnTrait {
|
||||||
fn is_id(&self) -> bool;
|
fn is_uuid(&self) -> bool;
|
||||||
fn is_timestamp(&self) -> bool;
|
fn is_updated_at(&self) -> bool;
|
||||||
fn should_synced(&self) -> bool;
|
fn updated_at() -> Self;
|
||||||
fn timestamp_after(from: DateTimeUtc) -> SimpleExpr;
|
fn should_not_sync(&self);
|
||||||
fn author_id_eq(author_id: Uuid) -> SimpleExpr;
|
|
||||||
fn is_author_id(&self) -> bool;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,9 @@ use sea_orm_migration::MigratorTrait;
|
||||||
use tokio::sync::{OnceCell, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
use tokio::sync::{OnceCell, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||||
|
|
||||||
mod peers;
|
mod peers;
|
||||||
pub use peers::*;
|
pub use peers::PEERS;
|
||||||
mod config;
|
mod config;
|
||||||
pub use config::*;
|
pub use config::STORAGE_CONFIG;
|
||||||
mod database_connection;
|
mod database_connection;
|
||||||
pub use database_connection::*;
|
pub use database_connection::*;
|
||||||
use uuid::{ContextV7, Timestamp, Uuid};
|
use uuid::{ContextV7, Timestamp, Uuid};
|
||||||
|
|
|
@ -9,4 +9,3 @@ pub mod migration;
|
||||||
pub mod p2p;
|
pub mod p2p;
|
||||||
#[cfg(any(test, feature="test"))]
|
#[cfg(any(test, feature="test"))]
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
pub mod utils;
|
|
||||||
|
|
15
lazy-supplements-core/src/message.rs
Normal file
15
lazy-supplements-core/src/message.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
|
||||||
|
pub trait Message: DeserializeOwned + Sized + Serialize {
|
||||||
|
fn into_writer<W: std::io::Write>(&self, writer: W) -> Result<(), ciborium::ser::Error<std::io::Error>> {
|
||||||
|
ciborium::into_writer(self, writer)
|
||||||
|
}
|
||||||
|
fn into_vec_u8(&self) -> Result<Vec<u8>, ciborium::ser::Error<std::io::Error>> {
|
||||||
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
|
self.into_writer(&mut buf)?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
fn from_reader<R: std::io::Read>(reader: R) -> Result<Self, ciborium::de::Error<std::io::Error>> {
|
||||||
|
ciborium::from_reader(reader)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,52 +0,0 @@
|
||||||
mod node;
|
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::{utils::async_convert::{AsyncTryFrom, AsyncTryInto}, error::Error};
|
|
||||||
|
|
||||||
pub trait Message: DeserializeOwned + Sized + Serialize {
|
|
||||||
fn into_writer<W: std::io::Write>(&self, writer: W) -> Result<(), ciborium::ser::Error<std::io::Error>> {
|
|
||||||
ciborium::into_writer(self, writer)
|
|
||||||
}
|
|
||||||
fn into_vec_u8(&self) -> Result<Vec<u8>, ciborium::ser::Error<std::io::Error>> {
|
|
||||||
let mut buf: Vec<u8> = Vec::new();
|
|
||||||
self.into_writer(&mut buf)?;
|
|
||||||
Ok(buf)
|
|
||||||
}
|
|
||||||
fn from_reader<R: std::io::Read>(reader: R) -> Result<Self, ciborium::de::Error<std::io::Error>> {
|
|
||||||
ciborium::from_reader(reader)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Request<T>: Into<T> + From<T> + AsyncTryInto<Self::Response>
|
|
||||||
where T: Message {
|
|
||||||
type Response: Response<T, Request = Self>;
|
|
||||||
async fn send_p2p(self) -> Result<Self::Response, Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Response<T>: Into<T> + From<T> + AsyncTryFrom<Self::Request>
|
|
||||||
where T: Message{
|
|
||||||
type Request: Request<T, Response = Self>;
|
|
||||||
async fn from_request_with_local(req: Self::Request) -> Result<Self,Error>;
|
|
||||||
async fn from_request_with_p2p(req: Self::Request) -> Result<Self, Error> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait FromDatabase {
|
|
||||||
async fn from_storage();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub trait P2pRequest<T>: Into<T> + From<T>
|
|
||||||
where T: Message {
|
|
||||||
type P2pResponse: P2pResponse<T, P2pRequest = Self>;
|
|
||||||
async fn send_p2p(&self) -> Result<Self::P2pResponse, crate::p2p::error::P2pError>{
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub trait P2pResponse<T>: Into<T> + From<T> + AsyncTryFrom<(Self::P2pRequest)>
|
|
||||||
where T: Message {
|
|
||||||
type P2pRequest: P2pRequest<T, P2pResponse = Self>;
|
|
||||||
async fn try_from_p2p_request(source: Self::P2pRequest) -> Result<Self, crate::p2p::error::P2pError>;
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct ListTrustedNodeRequest;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
pub struct ListTrustedNodeResponse {
|
|
||||||
node: Vec<crate::data::entity::TrustedNodeModel>
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum P2pError {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod error;
|
|
||||||
use libp2p::{ identity::Keypair, mdns, ping, swarm};
|
use libp2p::{ identity::Keypair, mdns, ping, swarm};
|
||||||
use sea_orm::{ActiveModelTrait, ActiveValue::Set, ColumnTrait, EntityTrait, QueryFilter};
|
use sea_orm::{ActiveModelTrait, ActiveValue::Set, ColumnTrait, EntityTrait, QueryFilter};
|
||||||
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
pub trait AsyncFrom<T> {
|
|
||||||
async fn async_from(source: T) -> Self;
|
|
||||||
}
|
|
||||||
pub trait AsyncInto<T> {
|
|
||||||
async fn async_into(self) -> T;
|
|
||||||
}
|
|
||||||
impl<T, U> AsyncInto<T> for U
|
|
||||||
where T: AsyncFrom<U> {
|
|
||||||
async fn async_into(self) -> T {
|
|
||||||
T::async_from(self).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait AsyncTryFrom<T>: Sized {
|
|
||||||
type Error: Sized;
|
|
||||||
async fn async_try_from(source: T) -> Result<Self, Self::Error>;
|
|
||||||
}
|
|
||||||
pub trait AsyncTryInto<T>: Sized{
|
|
||||||
type Error: Sized;
|
|
||||||
async fn async_try_into(self) -> Result<T, Self::Error>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> AsyncTryInto<T> for U
|
|
||||||
where T: AsyncTryFrom<U> {
|
|
||||||
type Error = <T as AsyncTryFrom<U>>::Error;
|
|
||||||
async fn async_try_into(self) -> Result<T, Self::Error> {
|
|
||||||
T::async_try_from(self).await
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
#[cfg(feature="macros")]
|
|
||||||
pub use lazy_supplements_macros::Emptiable;
|
|
||||||
|
|
||||||
pub trait Emptiable{
|
|
||||||
fn empty() -> Self;
|
|
||||||
fn is_empty(&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Emptiable for Vec<T> {
|
|
||||||
fn empty() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Emptiable for Option<T> {
|
|
||||||
fn empty() -> Self {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.is_none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Emptiable for String {
|
|
||||||
fn empty() -> Self {
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, U> Emptiable for HashMap<T, U> {
|
|
||||||
fn empty() -> Self {
|
|
||||||
HashMap::new()
|
|
||||||
}
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Emptiable for HashSet<T> {
|
|
||||||
fn empty() -> Self {
|
|
||||||
HashSet::new()
|
|
||||||
}
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
#[cfg(feature="macros")]
|
|
||||||
pub use lazy_supplements_macros::Mergeable;
|
|
||||||
pub trait Mergeable: Sized {
|
|
||||||
fn merge(&mut self, other: Self);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Mergeable for Option<T> {
|
|
||||||
fn merge(&mut self, mut other: Self) {
|
|
||||||
match other.take() {
|
|
||||||
Some(x) => {
|
|
||||||
let _ = self.insert(x);
|
|
||||||
},
|
|
||||||
None => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
pub mod async_convert;
|
|
||||||
pub mod emptiable;
|
|
||||||
pub mod mergeable;
|
|
||||||
pub mod runnable;
|
|
|
@ -1,6 +0,0 @@
|
||||||
#[cfg(feature="macros")]
|
|
||||||
pub use lazy_supplements_macros::Runnable;
|
|
||||||
|
|
||||||
pub trait Runnable {
|
|
||||||
async fn run(self);
|
|
||||||
}
|
|
|
@ -16,15 +16,10 @@ clap.workspace = true
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
lazy-supplements-core = { workspace = true, features = ["desktop"] }
|
lazy-supplements-core = { workspace = true, features = ["desktop"] }
|
||||||
libp2p.workspace = true
|
libp2p.workspace = true
|
||||||
prost = "0.14.1"
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tonic = "0.14.0"
|
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
lazy-supplements-core = {workspace = true, features = ["test"]}
|
lazy-supplements-core = {workspace = true, features = ["test"]}
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
tonic-prost-build = "0.14.0"
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
tonic_prost_build::compile_protos("proto/lazy_supplements.proto")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
package lazy_supplements;
|
|
||||||
|
|
||||||
enum PeerListOrderBy {
|
|
||||||
CREATED_AT = 0;
|
|
||||||
UPDATED_AT = 1;
|
|
||||||
PEER_ID = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
service CachedPeerService {
|
|
||||||
rpc List(CachedPeerListRequest) returns (CachedPeerListResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
message CachedPeerListRequest {
|
|
||||||
uint32 start = 1;
|
|
||||||
uint32 count = 2;
|
|
||||||
PeerListOrderBy order_by = 3;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
message CachedPeer {
|
|
||||||
string peer_id = 1;
|
|
||||||
repeated string multi_addresss = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CachedPeerListResponse {
|
|
||||||
repeated CachedPeer peers = 1;
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
use std::{net::IpAddr, path::PathBuf};
|
|
||||||
|
|
||||||
use clap::Args;
|
|
||||||
use lazy_supplements_core::config::{BaseConfig, ConfigError};
|
|
||||||
use crate::config::{PartialP2pConfig, PartialStorageConfig};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
config::DesktopBaseConfig,
|
|
||||||
error::Error,
|
|
||||||
global::DEFAULT_CONFIG_FILE_PATH
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Args, Clone, Debug)]
|
|
||||||
pub struct ConfigArgs {
|
|
||||||
#[arg(short = 'c', long = "config")]
|
|
||||||
pub file_path: Option<PathBuf>,
|
|
||||||
#[arg(skip)]
|
|
||||||
pub file_content: Option<DesktopBaseConfig>,
|
|
||||||
#[command(flatten)]
|
|
||||||
pub args: DesktopBaseConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl ConfigArgs {
|
|
||||||
pub fn get_file_path_or_default(&self) -> PathBuf {
|
|
||||||
self.file_path.clone().unwrap_or((*DEFAULT_CONFIG_FILE_PATH).clone())
|
|
||||||
}
|
|
||||||
pub async fn get_or_read_file_content(&mut self) -> &mut DesktopBaseConfig {
|
|
||||||
self.file_content.get_or_insert(
|
|
||||||
DesktopBaseConfig::read_from(self.get_file_path_or_default()).await.unwrap()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
use clap::Args;
|
|
||||||
use libp2p::{Multiaddr, PeerId};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(Args, Clone, Debug)]
|
|
||||||
#[group(multiple = false, required = true)]
|
|
||||||
pub struct DeviceArgs {
|
|
||||||
device_number: Option<u32>,
|
|
||||||
device_id: Option<Uuid>,
|
|
||||||
peer_id: Option<PeerId>,
|
|
||||||
multiaddr: Option<Multiaddr>,
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
mod config;
|
|
||||||
mod device;
|
|
||||||
mod peer;
|
|
||||||
|
|
||||||
pub use config::ConfigArgs;
|
|
||||||
pub use device::DeviceArgs;
|
|
||||||
pub use peer::PeerArgs;
|
|
|
@ -1,10 +0,0 @@
|
||||||
use clap::Args;
|
|
||||||
use libp2p::{Multiaddr, PeerId};
|
|
||||||
|
|
||||||
#[derive(Args, Clone, Debug)]
|
|
||||||
#[group(multiple = false, required = true)]
|
|
||||||
pub struct PeerArgs {
|
|
||||||
cache_number: Option<u32>,
|
|
||||||
peer_id: Option<PeerId>,
|
|
||||||
multiaddr: Option<Multiaddr>,
|
|
||||||
}
|
|
38
lazy-supplements-desktop/src/cli/config.rs
Normal file
38
lazy-supplements-desktop/src/cli/config.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
use std::{net::IpAddr, path::PathBuf};
|
||||||
|
|
||||||
|
use clap::Args;
|
||||||
|
use lazy_supplements_core::config::{PartialConfig, PartialCoreConfig};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{config::{desktop::PartialDesktopConfig, CoreConfig}, error::Error, global::{DEFAULT_CONFIG_FILE_PATH, DEFAULT_PARTIAL_CORE_CONFIG,}};
|
||||||
|
|
||||||
|
#[derive(Args, Clone, Debug)]
|
||||||
|
pub struct ConfigArgs {
|
||||||
|
#[arg(long)]
|
||||||
|
pub config: Option<PathBuf>,
|
||||||
|
#[command(flatten)]
|
||||||
|
pub core_config: PartialCoreConfig,
|
||||||
|
#[command(flatten)]
|
||||||
|
pub desktop_config: PartialDesktopConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl ConfigArgs {
|
||||||
|
pub fn get_config_path_or_default(&self) -> PathBuf {
|
||||||
|
if let Some(x) = self.config.as_ref() {
|
||||||
|
x.clone()
|
||||||
|
} else {
|
||||||
|
DEFAULT_CONFIG_FILE_PATH.to_path_buf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub async fn try_into_partial_core_config(self) -> Result<PartialCoreConfig, Error> {
|
||||||
|
let mut config = PartialCoreConfig::read_from(self.get_config_path_or_default()).await?;
|
||||||
|
config.merge(self.core_config.into());
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
pub async fn try_into_core_config(self) -> Result<CoreConfig, Error> {
|
||||||
|
let mut config = DEFAULT_PARTIAL_CORE_CONFIG.clone();
|
||||||
|
config.merge(self.try_into_partial_core_config().await?);
|
||||||
|
config.try_into()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
use clap::Args;
|
|
||||||
use crate::utils::runnable::Runnable;
|
|
||||||
|
|
||||||
use crate::cli::ConfigArgs;
|
|
||||||
|
|
||||||
use crate::cli::PeerArgs;
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
|
||||||
pub struct DeviceAddCommandArgs {
|
|
||||||
#[command(flatten)]
|
|
||||||
peer: PeerArgs,
|
|
||||||
#[arg(short, long)]
|
|
||||||
passcode: Option<String>,
|
|
||||||
#[command(flatten)]
|
|
||||||
config: ConfigArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Runnable for DeviceAddCommandArgs {
|
|
||||||
async fn run(self) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
use clap::Args;
|
|
||||||
use crate::utils::runnable::Runnable;
|
|
||||||
use crate::cli::{ConfigArgs, RunnableCommand};
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
|
||||||
pub struct DeviceListCommandArgs{
|
|
||||||
#[command(flatten)]
|
|
||||||
config: ConfigArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Runnable for DeviceListCommandArgs {
|
|
||||||
async fn run(self) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
mod add;
|
|
||||||
mod list;
|
|
||||||
mod ping;
|
|
||||||
mod remove;
|
|
||||||
mod scan;
|
|
||||||
|
|
||||||
pub use add::DeviceAddCommandArgs;
|
|
||||||
use crate::utils::runnable::Runnable;
|
|
||||||
use libp2p::{Multiaddr, PeerId};
|
|
||||||
pub use list::DeviceListCommandArgs;
|
|
||||||
pub use ping::DevicePingCommandArgs;
|
|
||||||
pub use remove::DeviceRemoveCommandArgs;
|
|
||||||
pub use scan::DeviceScanCommandArgs;
|
|
||||||
|
|
||||||
use clap::{Args, Parser, Subcommand};
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Args, Runnable)]
|
|
||||||
pub struct DeviceCommandArgs {
|
|
||||||
#[command(subcommand)]
|
|
||||||
#[runnable]
|
|
||||||
pub command: DeviceSubcommand
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Subcommand, Runnable)]
|
|
||||||
pub enum DeviceSubcommand {
|
|
||||||
Add(DeviceAddCommandArgs),
|
|
||||||
List(DeviceListCommandArgs),
|
|
||||||
Ping(DevicePingCommandArgs),
|
|
||||||
Remove(DeviceRemoveCommandArgs),
|
|
||||||
Scan(DeviceScanCommandArgs),
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
use clap::Args;
|
|
||||||
use crate::utils::runnable::Runnable;
|
|
||||||
use crate::cli::{ConfigArgs, PeerArgs};
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
|
||||||
pub struct DevicePingCommandArgs{
|
|
||||||
#[command(flatten)]
|
|
||||||
peer: PeerArgs,
|
|
||||||
#[command(flatten)]
|
|
||||||
config: ConfigArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Runnable for DevicePingCommandArgs {
|
|
||||||
async fn run(self) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
use clap::Args;
|
|
||||||
use crate::utils::runnable::Runnable;
|
|
||||||
use crate::cli::{ConfigArgs, DeviceArgs, RunnableCommand};
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
|
||||||
pub struct DeviceRemoveCommandArgs{
|
|
||||||
#[command(flatten)]
|
|
||||||
device: DeviceArgs,
|
|
||||||
#[command(flatten)]
|
|
||||||
config: ConfigArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Runnable for DeviceRemoveCommandArgs {
|
|
||||||
async fn run(self) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
use clap::Args;
|
|
||||||
use crate::utils::runnable::Runnable;
|
|
||||||
use crate::cli::{ConfigArgs, RunnableCommand};
|
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
|
||||||
pub struct DeviceScanCommandArgs{
|
|
||||||
#[command(flatten)]
|
|
||||||
config: ConfigArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Runnable for DeviceScanCommandArgs {
|
|
||||||
async fn run(self) {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
#[derive(Args, Debug)]
|
|
||||||
pub struct LogsCommandArgs {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,13 +1,9 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
mod args;
|
mod config;
|
||||||
mod device;
|
mod node;
|
||||||
mod server;
|
mod server;
|
||||||
|
|
||||||
pub use args::*;
|
pub use config::ConfigArgs;
|
||||||
pub use device::*;
|
pub use node::{ NodeArgs, NodeCommand, PeerArgs , ConsoleNodeArgs};
|
||||||
pub use server::*;
|
pub use server::ServerArgs;
|
||||||
|
|
||||||
pub trait RunnableCommand {
|
|
||||||
async fn run(self);
|
|
||||||
}
|
|
81
lazy-supplements-desktop/src/cli/node.rs
Normal file
81
lazy-supplements-desktop/src/cli/node.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
use std::{net::IpAddr, ops::Mul, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
|
use clap::{Args, Parser, Subcommand};
|
||||||
|
use libp2p::{
|
||||||
|
multiaddr::Protocol, noise, ping, swarm::SwarmEvent, tcp, yamux, Multiaddr, PeerId
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{cli::ServerArgs, error::Error};
|
||||||
|
|
||||||
|
use super::ConfigArgs;
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
pub struct NodeArgs {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: NodeCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub struct ConsoleNodeArgs {
|
||||||
|
#[command(flatten)]
|
||||||
|
pub args: NodeArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeArgs {
|
||||||
|
pub async fn run(self) -> Result<(), Error> {
|
||||||
|
println!("{self:?}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct PeerArgs {
|
||||||
|
#[arg(value_parser = clap::value_parser!(PeerArg))]
|
||||||
|
pub peer: PeerArg,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum PeerArg {
|
||||||
|
Addr(Multiaddr),
|
||||||
|
Id(PeerId),
|
||||||
|
Number(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for PeerArg {
|
||||||
|
type Err = String;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if let Ok(x) = s.parse::<Multiaddr>() {
|
||||||
|
Ok(Self::Addr(x))
|
||||||
|
} else if let Ok(x) = s.parse::<PeerId>() {
|
||||||
|
Ok(Self::Id(x))
|
||||||
|
} else if let Ok(x) = s.parse::<u32>() {
|
||||||
|
Ok(Self::Number(x))
|
||||||
|
} else {
|
||||||
|
Err(format!("Invalid value: {s}").to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Args, Debug)]
|
||||||
|
pub struct NodeJoinArgs {
|
||||||
|
#[command(flatten)]
|
||||||
|
pub peer: PeerArgs,
|
||||||
|
pub pass: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
pub enum NodeCommand {
|
||||||
|
Add(PeerArgs),
|
||||||
|
Ping(PeerArgs),
|
||||||
|
Join(PeerArgs),
|
||||||
|
List,
|
||||||
|
Delete(PeerArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl PeerArgs {
|
||||||
|
pub async fn run(self) -> Result<(), Error> {
|
||||||
|
println!("{self:?}");
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,18 @@
|
||||||
use clap::Args;
|
use clap::Args;
|
||||||
use lazy_supplements_core::utils::runnable::Runnable;
|
|
||||||
use libp2p::{noise, ping, swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, Swarm};
|
use libp2p::{noise, ping, swarm::{NetworkBehaviour, SwarmEvent}, tcp, yamux, Swarm};
|
||||||
|
|
||||||
use crate::{error::Error, global::P2P_CONFIG};
|
use crate::{error::Error, global::GLOBAL};
|
||||||
|
|
||||||
use super::ConfigArgs;
|
use super::ConfigArgs;
|
||||||
|
|
||||||
#[derive(Args, Debug)]
|
#[derive(Args, Debug)]
|
||||||
pub struct ServerCommandArgs {
|
pub struct ServerArgs {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
config: ConfigArgs,
|
config: ConfigArgs,
|
||||||
}
|
}
|
||||||
impl Runnable for ServerCommandArgs {
|
impl ServerArgs {
|
||||||
async fn run(self) {
|
pub async fn start_server(self) -> Result<(), Error>{
|
||||||
P2P_CONFIG.get_and_unwrap().clone().launch_swarm();
|
let _ = crate::global::GLOBAL.get_or_init_core_config(self.config.try_into_core_config().await?).await;
|
||||||
|
GLOBAL.launch_swarm().await
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,48 +1,15 @@
|
||||||
pub mod rpc;
|
|
||||||
|
|
||||||
use clap::Args;
|
|
||||||
pub use lazy_supplements_core::config::*;
|
|
||||||
|
|
||||||
use lazy_supplements_core::utils::{emptiable::Emptiable, mergeable::Mergeable};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub use rpc::*;
|
pub mod unix;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub use windows::*;
|
pub mod windows;
|
||||||
|
pub mod desktop;
|
||||||
|
pub use lazy_supplements_core::config::*;
|
||||||
|
|
||||||
#[derive(Args, Clone, Debug, Deserialize, Emptiable, Mergeable, Serialize)]
|
|
||||||
pub struct DesktopBaseConfig {
|
|
||||||
#[command(flatten)]
|
|
||||||
p2p: PartialP2pConfig,
|
|
||||||
#[command(flatten)]
|
|
||||||
storage: PartialStorageConfig,
|
|
||||||
#[command(flatten)]
|
|
||||||
rpc: PartialRpcConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BaseConfig for DesktopBaseConfig {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
p2p : PartialP2pConfig::empty().with_new_secret(),
|
|
||||||
storage: PartialStorageConfig::empty(),
|
|
||||||
rpc: PartialRpcConfig::empty().with_unused_port(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<PartialP2pConfig> for &DesktopBaseConfig {
|
#[cfg(unix)]
|
||||||
fn into(self) -> PartialP2pConfig {
|
pub use unix::*;
|
||||||
self.p2p.clone()
|
|
||||||
}
|
#[cfg(windows)]
|
||||||
}
|
pub use windows::*;
|
||||||
impl Into<PartialStorageConfig> for &DesktopBaseConfig {
|
|
||||||
fn into(self) -> PartialStorageConfig {
|
|
||||||
self.storage.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Into<PartialRpcConfig> for &DesktopBaseConfig {
|
|
||||||
fn into(self) -> PartialRpcConfig {
|
|
||||||
self.rpc.clone()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
use std::{net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener}, path::PathBuf};
|
|
||||||
use clap::Args;
|
|
||||||
use lazy_supplements_core::{config::PartialConfig, utils::{emptiable::Emptiable, mergeable::Mergeable}};
|
|
||||||
use libp2p::mdns::Config;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::config::error::ConfigError;
|
|
||||||
|
|
||||||
|
|
||||||
pub struct RpcConfig {
|
|
||||||
pub listen_address: IpAddr,
|
|
||||||
pub port: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<PartialRpcConfig> for RpcConfig {
|
|
||||||
type Error = ConfigError;
|
|
||||||
fn try_from(config: PartialRpcConfig) -> Result<Self, Self::Error> {
|
|
||||||
Ok(Self{
|
|
||||||
listen_address: config.listen_address.ok_or(ConfigError::MissingConfig("listen_address".to_string()))?,
|
|
||||||
port: config.port.ok_or(ConfigError::MissingConfig("port".to_string()))?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args, Clone, Debug, Deserialize, Emptiable, Mergeable, Serialize)]
|
|
||||||
pub struct PartialRpcConfig {
|
|
||||||
pub listen_address: Option<IpAddr>,
|
|
||||||
pub port: Option<u16>,
|
|
||||||
}
|
|
||||||
impl PartialRpcConfig {
|
|
||||||
pub fn with_unused_port(mut self) -> Self {
|
|
||||||
let listneer = if let Some(x) = self.listen_address {
|
|
||||||
TcpListener::bind(SocketAddr::new(x,0)).unwrap()
|
|
||||||
} else {
|
|
||||||
TcpListener::bind("127.0.0.1:0").unwrap()
|
|
||||||
};
|
|
||||||
self.port = Some(listneer.local_addr().unwrap().port());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PartialRpcConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self{
|
|
||||||
listen_address: Some(IpAddr::V4(Ipv4Addr::LOCALHOST)),
|
|
||||||
port: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RpcConfig> for PartialRpcConfig {
|
|
||||||
fn from(source: RpcConfig) -> Self {
|
|
||||||
Self {
|
|
||||||
listen_address: Some(source.listen_address),
|
|
||||||
port: Some(source.port),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
48
lazy-supplements-desktop/src/config/unix.rs
Normal file
48
lazy-supplements-desktop/src/config/unix.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use clap::Args;
|
||||||
|
use lazy_supplements_core::config::PartialConfig;
|
||||||
|
use libp2p::mdns::Config;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::config::error::ConfigError;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct UnixConfig {
|
||||||
|
pub socket_path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<PartialUnixConfig> for UnixConfig {
|
||||||
|
type Error = ConfigError;
|
||||||
|
fn try_from(config: PartialUnixConfig) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self{
|
||||||
|
socket_path: config.socket_path.ok_or(ConfigError::MissingConfig("socket_path".to_string()))?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct PartialUnixConfig {
|
||||||
|
pub socket_path: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UnixConfig> for PartialUnixConfig {
|
||||||
|
fn from(source: UnixConfig) -> Self {
|
||||||
|
Self {
|
||||||
|
socket_path: Some(source.socket_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialConfig for PartialUnixConfig {
|
||||||
|
fn empty() -> Self {
|
||||||
|
Self { socket_path: None }
|
||||||
|
}
|
||||||
|
fn default() -> Self {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn merge(&mut self, other: Self) {
|
||||||
|
if let Some(x) = other.socket_path {
|
||||||
|
self.socket_path = Some(x);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
3
lazy-supplements-desktop/src/config/windows.rs
Normal file
3
lazy-supplements-desktop/src/config/windows.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub struct WindowsConfig {
|
||||||
|
pub pipe_name: String
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
use std::{path::PathBuf, sync::LazyLock};
|
use std::{path::PathBuf, sync::LazyLock};
|
||||||
|
|
||||||
|
use lazy_supplements_core::config::PartialCoreConfig;
|
||||||
pub use lazy_supplements_core::global::*;
|
pub use lazy_supplements_core::global::*;
|
||||||
|
|
||||||
pub static DEFAULT_DATA_DIR_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
|
pub static DEFAULT_DATA_DIR_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
|
||||||
|
@ -27,3 +28,123 @@ pub static DEFAULT_CONFIG_FILE_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
|
||||||
pub static DEFAULT_DATABASE_FILE_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
|
pub static DEFAULT_DATABASE_FILE_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
|
||||||
DEFAULT_DATA_DIR_PATH.join(&*DEFAULT_DATABASE_FILE_NAME)
|
DEFAULT_DATA_DIR_PATH.join(&*DEFAULT_DATABASE_FILE_NAME)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
pub static DEFAULT_PARTIAL_CORE_CONFIG: LazyLock<PartialCoreConfig> = LazyLock::new(|| {
|
||||||
|
PartialCoreConfig {
|
||||||
|
secret: None,
|
||||||
|
listen_ips: Some(DEFAULT_LISTEN_IPS.to_vec()),
|
||||||
|
port: Some(0),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
pub struct Global {
|
||||||
|
pub p2p_config: OnceCell<P2pConfig>,
|
||||||
|
pub main_database: OnceCell<DatabaseConnection>,
|
||||||
|
pub cache_database: OnceCell<DatabaseConnection>,
|
||||||
|
pub peers: OnceCell<RwLock<HashMap<PeerId, Multiaddr>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Global {
|
||||||
|
pub fn get_p2p_config(&self) -> Option<&P2pConfig> {
|
||||||
|
self.p2p_config.get()
|
||||||
|
}
|
||||||
|
pub async fn get_or_init_p2p_config(&self, config: P2pConfig) -> &P2pConfig {
|
||||||
|
self.p2p_config.get_or_init(|| async {config}).await
|
||||||
|
}
|
||||||
|
pub async fn get_or_init_peers(&self) -> &RwLock<HashMap<PeerId, Multiaddr>> {
|
||||||
|
self.peers.get_or_init(|| async {
|
||||||
|
RwLock::new(HashMap::new())
|
||||||
|
}).await
|
||||||
|
}
|
||||||
|
pub async fn read_peers(&self) -> tokio::sync::RwLockReadGuard<'_, HashMap<PeerId, Multiaddr>>{
|
||||||
|
self.get_or_init_peers().await.read().await
|
||||||
|
}
|
||||||
|
pub async fn write_peers(&self) -> tokio::sync::RwLockWriteGuard<'_, HashMap<PeerId, Multiaddr>>{
|
||||||
|
self.get_or_init_peers().await.write().await
|
||||||
|
}
|
||||||
|
pub async fn launch_swarm(&self) -> Result<(), Error> {
|
||||||
|
let mut swarm = self.get_p2p_config().unwrap().clone().try_into_swarm().await?;
|
||||||
|
loop{
|
||||||
|
let swarm_event = swarm.select_next_some().await;
|
||||||
|
tokio::spawn(async move{
|
||||||
|
match swarm_event {
|
||||||
|
SwarmEvent::NewListenAddr { address, .. } => println!("Listening on {address:?}"),
|
||||||
|
SwarmEvent::Behaviour(event) => {
|
||||||
|
println!("{event:?}");
|
||||||
|
event.run().await;
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalDatabase for Global {
|
||||||
|
fn get_main_database(&self) -> Option<&DatabaseConnection> {
|
||||||
|
self.main_database.get()
|
||||||
|
}
|
||||||
|
async fn get_or_try_init_main_database<T, U>(&self, path: T, _: U) -> Result<&DatabaseConnection, Error>
|
||||||
|
where
|
||||||
|
T: AsRef<Path>,
|
||||||
|
U: MigratorTrait,
|
||||||
|
{
|
||||||
|
let url = "sqlite://".to_string() + path.as_ref().to_str().unwrap() + "?mode=rwc";
|
||||||
|
|
||||||
|
Ok(self.main_database.get_or_try_init(|| async {
|
||||||
|
let db = Database::connect(&url).await?;
|
||||||
|
U::up(&db, None).await?;
|
||||||
|
Ok::<DatabaseConnection, DbErr>(db)
|
||||||
|
}).await?)
|
||||||
|
}
|
||||||
|
fn get_cache_database(&self) -> Option<&DatabaseConnection> {
|
||||||
|
self.cache_database.get()
|
||||||
|
}
|
||||||
|
async fn get_or_try_init_cache_database<T, U>(&self, path: T, _: U) -> Result<&DatabaseConnection, Error>
|
||||||
|
where
|
||||||
|
T: AsRef<Path>,
|
||||||
|
U: MigratorTrait,
|
||||||
|
{
|
||||||
|
let url = "sqlite://".to_string() + path.as_ref().to_str().unwrap() + "?mode=rwc";
|
||||||
|
|
||||||
|
Ok(self.cache_database.get_or_try_init(|| async {
|
||||||
|
let db = Database::connect(&url).await?;
|
||||||
|
U::up(&db, None).await?;
|
||||||
|
Ok::<DatabaseConnection, DbErr>(db)
|
||||||
|
}).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub use tests::{get_or_init_temporary_main_database, get_or_init_temporary_cache_database};
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
use sea_orm_migration::MigratorTrait;
|
||||||
|
|
||||||
|
use crate::{global::GLOBAL, cache::migration::CacheMigrator, data::migration::MainMigrator};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub async fn get_or_init_temporary_main_database() -> &'static DatabaseConnection {
|
||||||
|
GLOBAL.get_or_try_init_temporary_main_database(MainMigrator).await.unwrap()
|
||||||
|
}
|
||||||
|
pub async fn get_or_init_temporary_cache_database() -> &'static DatabaseConnection {
|
||||||
|
GLOBAL.get_or_try_init_temporary_cache_database(CacheMigrator).await.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn connect_main_database () {
|
||||||
|
let db = get_or_init_temporary_main_database().await;
|
||||||
|
assert!(db.ping().await.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn connect_cache_database () {
|
||||||
|
let db = get_or_init_temporary_cache_database().await;
|
||||||
|
assert!(db.ping().await.is_ok());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
mod response;
|
mod response;
|
||||||
mod request;
|
mod request;
|
||||||
pub use response::*;
|
pub use response::*;
|
||||||
pub use request::*;
|
pub use request::*;
|
|
@ -2,7 +2,6 @@ pub mod cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod global;
|
pub mod global;
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
pub mod utils;
|
|
||||||
pub use lazy_supplements_core::{
|
pub use lazy_supplements_core::{
|
||||||
cache,
|
cache,
|
||||||
data,
|
data,
|
||||||
|
|
27
lazy-supplements-desktop/src/main.rs
Normal file
27
lazy-supplements-desktop/src/main.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
use lazy_supplements_desktop::{cli::{ConfigArgs, NodeArgs, NodeCommand, ServerArgs}, global::{Global, GLOBAL}, *};
|
||||||
|
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Command,
|
||||||
|
#[command(flatten)]
|
||||||
|
pub config: ConfigArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
enum Command {
|
||||||
|
Node(NodeArgs),
|
||||||
|
Server(ServerArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let _ = GLOBAL.get_or_init_core_config(cli.config.try_into_core_config().await.unwrap()).await;
|
||||||
|
match cli.command {
|
||||||
|
Command::Node(x) => x.run().await.unwrap(),
|
||||||
|
Command::Server(x) => x.start_server().await.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +0,0 @@
|
||||||
pub use lazy_supplements_core::utils::*;
|
|
|
@ -1,23 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "lazy-supplements-macros"
|
|
||||||
edition.workspace = true
|
|
||||||
version.workspace = true
|
|
||||||
description.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
proc-macro = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
heck = "0.5.0"
|
|
||||||
proc-macro2 = "1.0.95"
|
|
||||||
quote = "1.0.40"
|
|
||||||
syn = { version = "2.0.104", features = ["full"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
chrono.workspace = true
|
|
||||||
lazy-supplements-core.workspace = true
|
|
||||||
sea-orm.workspace = true
|
|
||||||
tokio.workspace = true
|
|
||||||
uuid.workspace = true
|
|
|
@ -1,67 +0,0 @@
|
||||||
use syn::{DataEnum, DataStruct, Fields, FieldsNamed, Ident, Type};
|
|
||||||
|
|
||||||
fn extract_fields_named_from_data_struct(data: &DataStruct) -> &FieldsNamed {
|
|
||||||
match data.fields {
|
|
||||||
Fields::Named(ref fields) => fields,
|
|
||||||
_ => panic!("all fields must be named.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_idents_from_fields_named_with_attribute<'a>(fields: &'a FieldsNamed, attribute: &'static str) -> Vec<&'a Ident>{
|
|
||||||
fields.named.iter()
|
|
||||||
.filter_map(|field| {
|
|
||||||
field.attrs.iter()
|
|
||||||
.find_map(|attr| {
|
|
||||||
if attr.path().is_ident(attribute) {
|
|
||||||
field.ident.as_ref()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_idents_and_types_from_data_struct_with_attribute<'a>(data: &'a DataStruct, attribute: &'static str) -> Vec<(Ident, Type)>{
|
|
||||||
let fields = extract_fields_named_from_data_struct(data);
|
|
||||||
fields.named.iter().filter_map(|field| {
|
|
||||||
field.attrs.iter()
|
|
||||||
.find_map(|attr| {
|
|
||||||
if attr.path().is_ident(attribute) {
|
|
||||||
Some((field.ident.clone().unwrap(), field.ty.clone()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_idents_and_types_from_data_struct<'a>(data: &'a DataStruct) -> Vec<(Ident, Type)>{
|
|
||||||
let fields = extract_fields_named_from_data_struct(data);
|
|
||||||
fields.named.iter().map(|x| {(x.ident.clone().unwrap(), x.ty.clone())}).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unwrap_vec_or_panic<T>(mut source: Vec<T>, msg: &'static str) -> T {
|
|
||||||
if source.len() == 1 {
|
|
||||||
source.pop().unwrap()
|
|
||||||
} else {
|
|
||||||
panic!("{}", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_idents_and_types_from_enum_struct<'a>(data: &'a DataEnum) -> Vec<(Ident, Type)> {
|
|
||||||
data.variants.iter().map(|variant| {
|
|
||||||
let mut fields: Vec<Type> = match &variant.fields {
|
|
||||||
Fields::Unnamed(fields_unnamed) => {
|
|
||||||
fields_unnamed.unnamed.iter().map(|x| {
|
|
||||||
x.ty.clone()
|
|
||||||
}).collect()
|
|
||||||
},
|
|
||||||
_ => panic!("Fields of enum variant must be unnamed!")
|
|
||||||
};
|
|
||||||
if fields.len() == 1 {
|
|
||||||
(variant.ident.clone(), fields.pop().unwrap())
|
|
||||||
} else {
|
|
||||||
panic!("Fields of enum variant must be single!")
|
|
||||||
}
|
|
||||||
}).collect()
|
|
||||||
}
|
|
|
@ -1,236 +0,0 @@
|
||||||
mod derive;
|
|
||||||
|
|
||||||
use heck::ToUpperCamelCase;
|
|
||||||
use proc_macro::{self, TokenStream};
|
|
||||||
use proc_macro2::Span;
|
|
||||||
use quote::{format_ident, quote, ToTokens};
|
|
||||||
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Expr, ExprTuple, Field, Fields, FieldsNamed, Ident};
|
|
||||||
use derive::*;
|
|
||||||
|
|
||||||
#[proc_macro_derive(SyncableModel, attributes(syncable))]
|
|
||||||
pub fn syncable_model(input: TokenStream) -> TokenStream {
|
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
|
||||||
let struct_name = input.ident;
|
|
||||||
assert_eq!(format_ident!("{}", struct_name), "Model");
|
|
||||||
let fields = extract_fields(&input.data);
|
|
||||||
let id_snake = extract_unique_field_ident(&fields, "id");
|
|
||||||
let id_camel = Ident::new(&id_snake.to_string().to_upper_camel_case(), Span::call_site());
|
|
||||||
let timestamp_snake = extract_unique_field_ident(&fields, "timestamp");
|
|
||||||
let timestamp_camel = Ident::new(×tamp_snake.to_string().to_upper_camel_case(), Span::call_site());
|
|
||||||
let author_id_snake = extract_unique_field_ident(&fields, "author_id");
|
|
||||||
let author_id_camel = Ident::new(&author_id_snake.to_string().to_upper_camel_case(), Span::call_site());
|
|
||||||
let skips_snake = extract_field_idents(&fields, "skip");
|
|
||||||
let output = quote!{
|
|
||||||
impl SyncableModel for #struct_name {
|
|
||||||
type SyncableEntity = Entity;
|
|
||||||
fn get_id(&self) -> Uuid {
|
|
||||||
self.#id_snake
|
|
||||||
}
|
|
||||||
fn get_timestamp(&self) -> DateTimeUtc {
|
|
||||||
self.#timestamp_snake
|
|
||||||
}
|
|
||||||
fn get_author_id(&self) -> Uuid {
|
|
||||||
self.#author_id_snake
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl SyncableEntity for Entity {
|
|
||||||
type SyncableModel = Model;
|
|
||||||
type SyncableActiveModel = ActiveModel;
|
|
||||||
type SyncableColumn = Column;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SyncableActiveModel for ActiveModel {
|
|
||||||
type SyncableEntity = Entity;
|
|
||||||
fn get_id(&self) -> Option<Uuid> {
|
|
||||||
self.#id_snake.try_as_ref().cloned()
|
|
||||||
}
|
|
||||||
fn get_timestamp(&self) -> Option<DateTimeUtc> {
|
|
||||||
self.#timestamp_snake.try_as_ref().cloned()
|
|
||||||
}
|
|
||||||
fn get_author_id(&self) -> Option<Uuid> {
|
|
||||||
self.#author_id_snake.try_as_ref().cloned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl SyncableColumn for Column {
|
|
||||||
fn is_id(&self) -> bool {
|
|
||||||
matches!(self, Column::#id_camel)
|
|
||||||
}
|
|
||||||
fn is_timestamp(&self) -> bool {
|
|
||||||
matches!(self, Column::#timestamp_camel)
|
|
||||||
}
|
|
||||||
fn is_author_id(&self) -> bool {
|
|
||||||
matches!(self, Column::#author_id_camel)
|
|
||||||
}
|
|
||||||
fn should_synced(&self) -> bool {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
fn timestamp_after(timestamp: DateTimeUtc) -> sea_orm::sea_query::expr::SimpleExpr {
|
|
||||||
Column::#timestamp_camel.gte(timestamp)
|
|
||||||
}
|
|
||||||
fn author_id_eq(author_id: Uuid) -> sea_orm::sea_query::expr::SimpleExpr {
|
|
||||||
Column::#author_id_camel.eq(author_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
output.into()
|
|
||||||
}
|
|
||||||
fn extract_unique_field_ident<'a>(fields: &'a FieldsNamed, attribute_arg: &'static str) -> &'a Ident {
|
|
||||||
let mut fields = extract_field_idents(fields, attribute_arg);
|
|
||||||
if fields.len() == 1 {
|
|
||||||
return fields.pop().unwrap()
|
|
||||||
} else {
|
|
||||||
panic!("Model must need one {} field attribute", attribute_arg);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_field_idents<'a>(fields: &'a FieldsNamed, attribute_arg: &'static str) -> Vec<&'a Ident>{
|
|
||||||
fields.named.iter()
|
|
||||||
.filter_map(|field| {
|
|
||||||
field.attrs.iter()
|
|
||||||
.find_map(|attr| {
|
|
||||||
if attr.path().is_ident("syncable") {
|
|
||||||
let args: Expr = attr.parse_args().unwrap();
|
|
||||||
|
|
||||||
match args {
|
|
||||||
|
|
||||||
Expr::Tuple(arg_tupple) => {
|
|
||||||
|
|
||||||
arg_tupple.elems.iter()
|
|
||||||
.find_map(|arg| {
|
|
||||||
if let Expr::Path(arg_path) = arg {
|
|
||||||
if arg_path.path.is_ident(attribute_arg) {
|
|
||||||
Some(field.ident.as_ref().unwrap())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
Expr::Path(arg_path) => {
|
|
||||||
if arg_path.path.is_ident(attribute_arg) {
|
|
||||||
Some(field.ident.as_ref().unwrap())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn extract_fields(data: &Data) -> &FieldsNamed {
|
|
||||||
match *data {
|
|
||||||
Data::Struct(ref data) => match data.fields {
|
|
||||||
Fields::Named(ref fields) => fields,
|
|
||||||
_ => panic!("all fields must be named.")
|
|
||||||
},
|
|
||||||
_ => panic!("struct expected, but got other item."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_derive(Emptiable)]
|
|
||||||
pub fn emptiable(input: TokenStream) -> TokenStream {
|
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
|
||||||
let type_ident = input.ident;
|
|
||||||
match input.data {
|
|
||||||
Data::Struct(ref data) => {
|
|
||||||
let field_idents = extract_idents_and_types_from_data_struct(data);
|
|
||||||
let is_empty_iter = field_idents.iter().map(|(ident, type_name)| {
|
|
||||||
quote!{
|
|
||||||
<#type_name as Emptiable>::is_empty(&self.#ident)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let empty_iter = field_idents.iter().map(|(ident, type_name)| {
|
|
||||||
quote!{
|
|
||||||
#ident: <#type_name as Emptiable>::empty(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
quote!{
|
|
||||||
impl Emptiable for #type_ident {
|
|
||||||
fn empty() -> Self {
|
|
||||||
Self {
|
|
||||||
#(#empty_iter)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
#(#is_empty_iter)&&*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.into()
|
|
||||||
}
|
|
||||||
_ => panic!("struct or expected, but got other type.")
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_derive(Mergeable)]
|
|
||||||
pub fn mergeable(input: TokenStream) -> TokenStream {
|
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
|
||||||
let type_ident = input.ident;
|
|
||||||
match input.data {
|
|
||||||
Data::Struct(ref data) => {
|
|
||||||
let field_idents = extract_idents_and_types_from_data_struct(data);
|
|
||||||
let merge_iter = field_idents.iter().map(|(ident, type_name)| {
|
|
||||||
quote!{
|
|
||||||
<#type_name as Mergeable>::merge(&mut self.#ident, other.#ident);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
quote!{
|
|
||||||
impl Mergeable for #type_ident {
|
|
||||||
fn merge(&mut self, mut other: Self){
|
|
||||||
#(#merge_iter)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.into()
|
|
||||||
}
|
|
||||||
_ => panic!("struct expected, but got other type.")
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[proc_macro_derive(Runnable, attributes(runnable))]
|
|
||||||
pub fn runnable(input: TokenStream) -> TokenStream {
|
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
|
||||||
let type_ident = input.ident;
|
|
||||||
match input.data {
|
|
||||||
Data::Struct(ref data) => {
|
|
||||||
let mut idents = extract_idents_and_types_from_data_struct_with_attribute(data, "runnable");
|
|
||||||
let (field_ident, field_type) = unwrap_vec_or_panic(idents, "Runnable struct must have one field with runnable attribute");
|
|
||||||
|
|
||||||
quote!{
|
|
||||||
impl Runnable for #type_ident {
|
|
||||||
async fn run(self) {
|
|
||||||
<#field_type as Runnable>::run(self.#field_ident).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.into()
|
|
||||||
}
|
|
||||||
Data::Enum(ref variants) => {
|
|
||||||
let quote_vec = extract_idents_and_types_from_enum_struct(&variants);
|
|
||||||
let quote_iter = quote_vec.iter().map(|(variant_ident, variant_type)|{
|
|
||||||
quote!{
|
|
||||||
Self::#variant_ident(x) => <#variant_type as Runnable>::run(x).await,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
quote!{
|
|
||||||
impl Runnable for #type_ident {
|
|
||||||
async fn run(self) {
|
|
||||||
match self {
|
|
||||||
#(#quote_iter)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.into()
|
|
||||||
|
|
||||||
},
|
|
||||||
_ => panic!("struct or enum expected, but got union.")
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
|
|
||||||
use lazy_supplements_core::utils::emptiable::Emptiable;
|
|
||||||
use lazy_supplements_macros::Emptiable;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Emptiable)]
|
|
||||||
struct EmptiableStruct{
|
|
||||||
vec: Vec<u8>,
|
|
||||||
text: String,
|
|
||||||
map: HashMap<u8, u8>,
|
|
||||||
set: HashSet<u8>,
|
|
||||||
opt: Option<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
fn test() {
|
|
||||||
use std::hash::Hash;
|
|
||||||
let empty = EmptiableStruct::empty();
|
|
||||||
assert_eq!(&empty, &EmptiableStruct{
|
|
||||||
vec: Vec::new(),
|
|
||||||
text: String::new(),
|
|
||||||
map: HashMap::new(),
|
|
||||||
set: HashSet::new(),
|
|
||||||
opt: None,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
|
|
||||||
use lazy_supplements_core::utils::mergeable::Mergeable;
|
|
||||||
use lazy_supplements_macros::Mergeable;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Mergeable)]
|
|
||||||
struct MergeableStruct {
|
|
||||||
opt: Option<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
fn test() {
|
|
||||||
let zero = MergeableStruct{
|
|
||||||
opt: Some(0),
|
|
||||||
};
|
|
||||||
let one = MergeableStruct {
|
|
||||||
opt: Some(1),
|
|
||||||
};
|
|
||||||
let none = MergeableStruct{
|
|
||||||
opt: None,
|
|
||||||
};
|
|
||||||
let mut zero_with_one = zero.clone();
|
|
||||||
zero_with_one.merge(one.clone());
|
|
||||||
let mut none_with_zero = none.clone();
|
|
||||||
none_with_zero.merge(zero.clone());
|
|
||||||
let mut zero_with_none = zero.clone();
|
|
||||||
zero_with_none.merge(none.clone());
|
|
||||||
assert_eq!(zero_with_one.clone(), one.clone());
|
|
||||||
assert_eq!(none_with_zero, zero.clone());
|
|
||||||
assert_eq!(zero_with_none, zero.clone());
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
use lazy_supplements_core::utils::runnable::Runnable;
|
|
||||||
use lazy_supplements_macros::Runnable;
|
|
||||||
|
|
||||||
struct RunnableStruct1;
|
|
||||||
|
|
||||||
impl Runnable for RunnableStruct1 {
|
|
||||||
async fn run(self) {
|
|
||||||
print!("Run {}", stringify!(RunnableStruct1::run()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Runnable)]
|
|
||||||
enum RunnableEnum {
|
|
||||||
Struct1(RunnableStruct1),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Runnable)]
|
|
||||||
struct RunnableStruct2 {
|
|
||||||
#[runnable]
|
|
||||||
runnable: RunnableEnum,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test() {
|
|
||||||
let runnable = RunnableStruct2{
|
|
||||||
runnable: RunnableEnum::Struct1(RunnableStruct1)
|
|
||||||
};
|
|
||||||
runnable.run().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
use chrono::Local;
|
|
||||||
use sea_orm::{
|
|
||||||
prelude::*,
|
|
||||||
entity::{
|
|
||||||
*,
|
|
||||||
prelude::*
|
|
||||||
}
|
|
||||||
};
|
|
||||||
use lazy_supplements_core::data::syncable::*;
|
|
||||||
use lazy_supplements_macros::SyncableModel;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, SyncableModel)]
|
|
||||||
#[sea_orm(table_name = "syncable")]
|
|
||||||
pub struct Model {
|
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
|
||||||
#[syncable(id)]
|
|
||||||
pub id: Uuid,
|
|
||||||
#[sea_orm(indexed)]
|
|
||||||
#[syncable(timestamp)]
|
|
||||||
pub created_at: DateTimeUtc,
|
|
||||||
#[syncable(author_id)]
|
|
||||||
pub created_by: Uuid,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)]
|
|
||||||
pub enum Relation{}
|
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_columns() {
|
|
||||||
assert!(Column::Id.is_id());
|
|
||||||
assert!(Column::CreatedAt.is_timestamp());
|
|
||||||
assert!(Column::CreatedBy.is_author_id());
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue