Add global traits
This commit is contained in:
parent
66be78dabf
commit
a80b9bcdf1
12 changed files with 320 additions and 208 deletions
|
@ -1,22 +1,31 @@
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod core;
|
mod storage;
|
||||||
|
mod p2p;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
pub use core::{ CoreConfig, PartialCoreConfig };
|
|
||||||
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 p2p::{P2pConfig, PartialP2pConfig};
|
||||||
|
pub trait PartialConfig: Serialize + Sized + DeserializeOwned
|
||||||
|
{
|
||||||
|
|
||||||
pub trait PartialConfig<T>: From<T>
|
|
||||||
where T: TryFrom<Self> {
|
|
||||||
fn default() -> Self;
|
fn default() -> Self;
|
||||||
fn empty() -> Self;
|
fn empty() -> Self;
|
||||||
fn merge(&mut self, other: Self);
|
fn merge(&mut self, other: Self);
|
||||||
|
fn from_toml(s: &str) -> Result<Self, toml::de::Error> {
|
||||||
|
toml::from_str(s)
|
||||||
|
}
|
||||||
|
fn into_toml(&self) -> Result<String, toml::ser::Error> {
|
||||||
|
toml::to_string(self)
|
||||||
|
}
|
||||||
|
fn is_empty(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ConfigFile: DeserializeOwned + Serialize {
|
pub trait ConfigRoot: DeserializeOwned + Serialize {
|
||||||
fn new() -> Self;
|
fn new() -> Self;
|
||||||
|
|
||||||
async fn read_or_create<T>(path: T) -> Result<Self, Error>
|
async fn read_or_create<T>(path: T) -> Result<Self, Error>
|
||||||
|
@ -53,3 +62,51 @@ pub trait ConfigFile: DeserializeOwned + Serialize {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::tests::test_toml_serialize_deserialize;
|
||||||
|
|
||||||
|
use super::{p2p::{P2pConfig, PartialP2pConfig}, PartialConfig};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
pub struct TestConfig {
|
||||||
|
|
||||||
|
p2p: Option<PartialP2pConfig>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialConfig for TestConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
p2p: Some(PartialP2pConfig::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty() -> Self {
|
||||||
|
Self {
|
||||||
|
p2p: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.p2p.is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge(&mut self, other: Self) {
|
||||||
|
if let Some(p2p) = other.p2p {
|
||||||
|
self.p2p = Some(p2p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_p2p_config_serialize_deserialize() {
|
||||||
|
test_toml_serialize_deserialize(TestConfig::empty());
|
||||||
|
test_toml_serialize_deserialize(TestConfig::default());
|
||||||
|
assert_eq!(TestConfig::empty(), toml::from_str("").unwrap());
|
||||||
|
assert_eq!("", &toml::to_string(&TestConfig::empty()).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{net::IpAddr, ops, path::{Path, PathBuf}};
|
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")]
|
||||||
|
@ -14,6 +14,9 @@ use crate::{
|
||||||
error::Error, p2p
|
error::Error, p2p
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static DEFAULT_P2P_LISTEN_IPS: &[IpAddr] = &[IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0))];
|
||||||
|
static DEFAULT_P2P_PORT: u16 = 0;
|
||||||
|
|
||||||
fn keypair_to_base64(keypair: &Keypair) -> String {
|
fn keypair_to_base64(keypair: &Keypair) -> String {
|
||||||
let vec = match keypair.to_protobuf_encoding() {
|
let vec = match keypair.to_protobuf_encoding() {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
|
@ -28,14 +31,14 @@ fn base64_to_keypair(base64: &str) -> Result<Keypair, Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct CoreConfig {
|
pub struct P2pConfig {
|
||||||
#[serde(with = "keypair_parser")]
|
#[serde(with = "keypair_parser")]
|
||||||
pub secret: Keypair,
|
pub secret: Keypair,
|
||||||
pub listen_ips: Vec<IpAddr>,
|
pub listen_ips: Vec<IpAddr>,
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoreConfig {
|
impl P2pConfig {
|
||||||
pub 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()
|
||||||
|
@ -51,10 +54,10 @@ impl CoreConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<PartialCoreConfig> for CoreConfig {
|
impl TryFrom<PartialP2pConfig> for P2pConfig {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
fn try_from(raw: PartialCoreConfig) -> Result<CoreConfig, Self::Error> {
|
fn try_from(raw: PartialP2pConfig) -> Result<P2pConfig, Self::Error> {
|
||||||
Ok(CoreConfig {
|
Ok(P2pConfig {
|
||||||
secret: base64_to_keypair(&raw.secret.ok_or(Error::MissingConfig("secret"))?)?,
|
secret: base64_to_keypair(&raw.secret.ok_or(Error::MissingConfig("secret"))?)?,
|
||||||
listen_ips: raw.listen_ips.ok_or(Error::MissingConfig("listen_ips"))?,
|
listen_ips: raw.listen_ips.ok_or(Error::MissingConfig("listen_ips"))?,
|
||||||
port: raw.port.ok_or(Error::MissingConfig("port"))?
|
port: raw.port.ok_or(Error::MissingConfig("port"))?
|
||||||
|
@ -83,8 +86,8 @@ mod keypair_parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature="desktop",derive(Args))]
|
#[cfg_attr(feature="desktop",derive(Args))]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
pub struct PartialCoreConfig {
|
pub struct PartialP2pConfig {
|
||||||
#[cfg_attr(feature="desktop",arg(long))]
|
#[cfg_attr(feature="desktop",arg(long))]
|
||||||
pub secret: Option<String>,
|
pub secret: Option<String>,
|
||||||
#[cfg_attr(feature="desktop",arg(long))]
|
#[cfg_attr(feature="desktop",arg(long))]
|
||||||
|
@ -92,7 +95,7 @@ pub struct PartialCoreConfig {
|
||||||
#[cfg_attr(feature="desktop",arg(long))]
|
#[cfg_attr(feature="desktop",arg(long))]
|
||||||
pub port: Option<u16>,
|
pub port: Option<u16>,
|
||||||
}
|
}
|
||||||
impl PartialCoreConfig {
|
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()));
|
||||||
|
@ -133,8 +136,8 @@ impl PartialCoreConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CoreConfig> for PartialCoreConfig {
|
impl From<P2pConfig> for PartialP2pConfig {
|
||||||
fn from(config: CoreConfig) -> Self {
|
fn from(config: P2pConfig) -> Self {
|
||||||
Self {
|
Self {
|
||||||
secret: Some(keypair_to_base64(&config.secret)),
|
secret: Some(keypair_to_base64(&config.secret)),
|
||||||
listen_ips: Some(config.listen_ips),
|
listen_ips: Some(config.listen_ips),
|
||||||
|
@ -142,7 +145,8 @@ impl From<CoreConfig> for PartialCoreConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl PartialConfig<CoreConfig> for PartialCoreConfig {
|
|
||||||
|
impl PartialConfig for PartialP2pConfig {
|
||||||
fn empty() -> Self {
|
fn empty() -> Self {
|
||||||
Self {
|
Self {
|
||||||
secret: None,
|
secret: None,
|
||||||
|
@ -150,6 +154,9 @@ impl PartialConfig<CoreConfig> for PartialCoreConfig {
|
||||||
port: 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) {
|
fn merge(&mut self, another: Self) {
|
||||||
if let Some(x) = another.secret {
|
if let Some(x) = another.secret {
|
||||||
self.secret = Some(x);
|
self.secret = Some(x);
|
||||||
|
@ -163,14 +170,22 @@ impl PartialConfig<CoreConfig> for PartialCoreConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
todo!()
|
Self {
|
||||||
|
secret: None,
|
||||||
|
listen_ips: Some(Vec::from(DEFAULT_P2P_LISTEN_IPS)),
|
||||||
|
port: Some(DEFAULT_P2P_PORT),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use libp2p::identity;
|
use libp2p::identity;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::{config::PartialConfig, tests::test_toml_serialize_deserialize};
|
||||||
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -180,4 +195,9 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(keypair.public(), keypair2.public());
|
assert_eq!(keypair.public(), keypair2.public());
|
||||||
}
|
}
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_p2p_config_serialize_deserialize() {
|
||||||
|
test_toml_serialize_deserialize(PartialP2pConfig::empty());
|
||||||
|
test_toml_serialize_deserialize(PartialP2pConfig::default());
|
||||||
|
}
|
||||||
}
|
}
|
91
lazy-supplements-core/src/config/storage.rs
Normal file
91
lazy-supplements-core/src/config/storage.rs
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[cfg(feature="desktop")]
|
||||||
|
use clap::Args;
|
||||||
|
use crate::config::{ConfigError, PartialConfig};
|
||||||
|
use libp2p::mdns::Config;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
static DATA_DATABASE_NAME: &str = "data.sqlite";
|
||||||
|
static CACHE_DATABASE_NAME: &str = "cache.sqlite";
|
||||||
|
|
||||||
|
#[cfg(any(test, feature="test"))]
|
||||||
|
static TEST_DATA_DATABASE_PATH: std::sync::LazyLock<tempfile::TempPath> = std::sync::LazyLock::new(|| {
|
||||||
|
let mut temp_path = tempfile::NamedTempFile::new().unwrap().into_temp_path();
|
||||||
|
temp_path.disable_cleanup(true);
|
||||||
|
println!("{}", temp_path.as_os_str().to_str().unwrap());
|
||||||
|
temp_path
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StorageConfig {
|
||||||
|
pub data_directory: PathBuf,
|
||||||
|
pub cache_directory: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorageConfig {
|
||||||
|
#[cfg(any(test, feature="test"))]
|
||||||
|
pub fn new_test() -> Self {
|
||||||
|
let mut temp_path = tempfile::NamedTempFile::new().unwrap().into_temp_path().keep().unwrap();
|
||||||
|
Self { data_directory: temp_path.clone(), cache_directory: temp_path }
|
||||||
|
}
|
||||||
|
pub fn get_data_database_path(&self) -> PathBuf{
|
||||||
|
self.data_directory.join(DATA_DATABASE_NAME)
|
||||||
|
}
|
||||||
|
pub fn get_cache_database_path(&self) -> PathBuf {
|
||||||
|
self.cache_directory.join(CACHE_DATABASE_NAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<PartialStorageConfig> for StorageConfig {
|
||||||
|
type Error = ConfigError;
|
||||||
|
|
||||||
|
fn try_from(value: PartialStorageConfig) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
data_directory: value.data_directory.ok_or(ConfigError::MissingConfig("data_directory".to_string()))?,
|
||||||
|
cache_directory: value.cache_directory.ok_or(ConfigError::MissingConfig("cache_directory".to_string()))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg_attr(feature="desktop", derive(Args))]
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct PartialStorageConfig {
|
||||||
|
#[cfg_attr(feature="desktop", arg(long))]
|
||||||
|
pub data_directory: Option<PathBuf>,
|
||||||
|
#[cfg_attr(feature="desktop", arg(long))]
|
||||||
|
pub cache_directory: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StorageConfig> for PartialStorageConfig {
|
||||||
|
fn from(config: StorageConfig) -> PartialStorageConfig {
|
||||||
|
Self {
|
||||||
|
data_directory: Some(config.data_directory),
|
||||||
|
cache_directory: Some(config.cache_directory),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialConfig for PartialStorageConfig {
|
||||||
|
fn empty() -> Self {
|
||||||
|
Self{
|
||||||
|
data_directory: None,
|
||||||
|
cache_directory: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.data_directory.is_none() && self.cache_directory.is_none()
|
||||||
|
}
|
||||||
|
fn default() -> Self {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn merge(&mut self, other: Self) {
|
||||||
|
if let Some(x) = other.data_directory {
|
||||||
|
self.data_directory = Some(x);
|
||||||
|
}
|
||||||
|
if let Some(x) = other.cache_directory {
|
||||||
|
self.cache_directory = Some(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,64 +0,0 @@
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use sea_orm::{ConnectOptions, Database, DbErr, DatabaseConnection};
|
|
||||||
use sea_orm_migration::MigratorTrait;
|
|
||||||
use crate::error::Error;
|
|
||||||
use tokio::sync::OnceCell;
|
|
||||||
|
|
||||||
use super::Global;
|
|
||||||
|
|
||||||
#[cfg(any(test, feature="test"))]
|
|
||||||
pub static TEST_MAIN_DATABASE_URL: std::sync::LazyLock<tempfile::TempPath> = std::sync::LazyLock::new(|| {
|
|
||||||
let mut temp_path = tempfile::NamedTempFile::new().unwrap().into_temp_path();
|
|
||||||
temp_path.disable_cleanup(true);
|
|
||||||
println!("{}", temp_path.as_os_str().to_str().unwrap());
|
|
||||||
temp_path
|
|
||||||
});
|
|
||||||
#[cfg(any(test, feature="test"))]
|
|
||||||
pub static TEST_CACHE_DATABASE_URL: std::sync::LazyLock<tempfile::TempPath> = std::sync::LazyLock::new(|| {
|
|
||||||
let mut temp_path = tempfile::NamedTempFile::new().unwrap().into_temp_path();
|
|
||||||
temp_path.disable_cleanup(true);
|
|
||||||
println!("{}", temp_path.as_os_str().to_str().unwrap());
|
|
||||||
temp_path
|
|
||||||
});
|
|
||||||
|
|
||||||
pub trait GlobalDatabase {
|
|
||||||
fn get_main_database(&self) -> Option<&DatabaseConnection>;
|
|
||||||
async fn get_or_try_init_main_database<T, U>(&self, path: T, _: U) -> Result<&DatabaseConnection, Error>
|
|
||||||
where
|
|
||||||
T: AsRef<Path>,
|
|
||||||
U: MigratorTrait
|
|
||||||
;
|
|
||||||
fn get_unwrapped_main_database(&self) -> &DatabaseConnection {
|
|
||||||
match self.get_main_database() {
|
|
||||||
Some(x) => x,
|
|
||||||
None => unreachable!("Error: global main database is not initialized!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn get_cache_database(&self) -> Option<&DatabaseConnection>;
|
|
||||||
async fn get_or_try_init_cache_database<T, U>(&self, path: T, _: U) -> Result<&DatabaseConnection, Error>
|
|
||||||
where
|
|
||||||
T: AsRef<Path>,
|
|
||||||
U: MigratorTrait
|
|
||||||
;
|
|
||||||
fn get_unwrapped_cache_database(&self) -> &DatabaseConnection {
|
|
||||||
match self.get_cache_database() {
|
|
||||||
Some(x) => x,
|
|
||||||
None => unreachable!("Error: global main database is not initialized!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(any(test, feature="test"))]
|
|
||||||
async fn get_or_try_init_temporary_main_database<T>(&self, migrator: T) -> Result<&DatabaseConnection, Error>
|
|
||||||
where
|
|
||||||
T: MigratorTrait,
|
|
||||||
{
|
|
||||||
self.get_or_try_init_main_database(&*TEST_MAIN_DATABASE_URL, migrator).await
|
|
||||||
}
|
|
||||||
#[cfg(any(test, feature="test"))]
|
|
||||||
async fn get_or_try_init_temporary_cache_database<T>(&self, migrator: T) -> Result<&DatabaseConnection, Error>
|
|
||||||
where
|
|
||||||
T: MigratorTrait,
|
|
||||||
{
|
|
||||||
self.get_or_try_init_cache_database(&*TEST_CACHE_DATABASE_URL, migrator).await
|
|
||||||
}
|
|
||||||
}
|
|
32
lazy-supplements-core/src/global/database_connection.rs
Normal file
32
lazy-supplements-core/src/global/database_connection.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use sea_orm::{ConnectOptions, Database, DbErr, DatabaseConnection};
|
||||||
|
use sea_orm_migration::MigratorTrait;
|
||||||
|
use crate::error::Error;
|
||||||
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
|
use super::storage_config::GlobalStorageConfig;
|
||||||
|
|
||||||
|
static UNINITIALIZED_MESSAGE: &str = "global database connection uninitialized!";
|
||||||
|
|
||||||
|
pub trait GlobalDatabaseConnection: GlobalStorageConfig {
|
||||||
|
fn get_data_database_connection_as_once_cell(&'static self) -> &'static OnceCell<DatabaseConnection>;
|
||||||
|
fn get_data_database_connection(&'static self) -> Option<&'static DatabaseConnection> {
|
||||||
|
self.get_data_database_connection_as_once_cell().get()
|
||||||
|
}
|
||||||
|
fn get_and_unwrap_data_database_connection(&'static self) -> &'static DatabaseConnection {
|
||||||
|
self.get_data_database_connection().expect(UNINITIALIZED_MESSAGE)
|
||||||
|
}
|
||||||
|
async fn get_or_try_init_data_database_connection<T>(&'static self, _: T) -> Result<&DatabaseConnection, Error>
|
||||||
|
where
|
||||||
|
T: MigratorTrait
|
||||||
|
{
|
||||||
|
let url = "sqlite://".to_string() + self.get_and_unwrap_storage_config().get_data_database_path().to_str().unwrap() + "?mode=rwc";
|
||||||
|
Ok(self.get_data_database_connection_as_once_cell().get_or_try_init(|| async {
|
||||||
|
let db = Database::connect(&url).await?;
|
||||||
|
T::up(&db, None).await?;
|
||||||
|
Ok::<DatabaseConnection, DbErr>(db)
|
||||||
|
}).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,14 +1,17 @@
|
||||||
use std::{collections::HashMap, net::{IpAddr, Ipv4Addr}, path::{Path, PathBuf}, sync::LazyLock};
|
use std::{collections::HashMap, net::{IpAddr, Ipv4Addr}, path::{Path, PathBuf}, sync::LazyLock};
|
||||||
|
|
||||||
use crate::{config::{CoreConfig, PartialCoreConfig}, error::Error};
|
use crate::{config::{P2pConfig, PartialP2pConfig, StorageConfig}, error::Error};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use libp2p::{swarm::SwarmEvent, Multiaddr, PeerId};
|
use libp2p::{swarm::SwarmEvent, Multiaddr, PeerId};
|
||||||
use sea_orm::{prelude::*, Database};
|
use sea_orm::{prelude::*, Database};
|
||||||
use sea_orm_migration::MigratorTrait;
|
use sea_orm_migration::MigratorTrait;
|
||||||
use tokio::sync::{OnceCell, RwLock};
|
use tokio::sync::{OnceCell, RwLock};
|
||||||
|
|
||||||
mod database;
|
mod peers;
|
||||||
use database::GlobalDatabase;
|
pub use peers::GlobalPeers;
|
||||||
|
mod storage_config;
|
||||||
|
mod database_connection;
|
||||||
|
pub use database_connection::GlobalDatabaseConnection;
|
||||||
use uuid::{ContextV7, Timestamp, Uuid};
|
use uuid::{ContextV7, Timestamp, Uuid};
|
||||||
|
|
||||||
pub fn generate_uuid() -> Uuid {
|
pub fn generate_uuid() -> Uuid {
|
||||||
|
@ -30,123 +33,17 @@ pub static DEFAULT_DATABASE_FILE_NAME: LazyLock<PathBuf> = LazyLock::new(|| {
|
||||||
PathBuf::from(String::new() + env!("CARGO_PKG_NAME") + ".sqlite")
|
PathBuf::from(String::new() + env!("CARGO_PKG_NAME") + ".sqlite")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(any(test, feature="test"))]
|
||||||
|
pub struct TestGlobal {
|
||||||
pub static GLOBAL: Global = Global{
|
p2p_config: OnceCell<P2pConfig>,
|
||||||
core_config: OnceCell::const_new(),
|
storage_config: OnceCell<StorageConfig>,
|
||||||
main_database: OnceCell::const_new(),
|
data_database_connection: OnceCell<DatabaseConnection>,
|
||||||
cache_database: OnceCell::const_new(),
|
cache_database_connection: OnceCell<DatabaseConnection>,
|
||||||
peers: OnceCell::const_new(),
|
}
|
||||||
|
#[cfg(any(test, feature="test"))]
|
||||||
|
pub static GLOBAL: TestGlobal = TestGlobal{
|
||||||
|
p2p_config: OnceCell::const_new(),
|
||||||
|
storage_config: OnceCell::const_new(),
|
||||||
|
data_database_connection: OnceCell::const_new(),
|
||||||
|
cache_database_connection: OnceCell::const_new(),
|
||||||
};
|
};
|
||||||
pub struct Global {
|
|
||||||
pub core_config: OnceCell<CoreConfig>,
|
|
||||||
pub main_database: OnceCell<DatabaseConnection>,
|
|
||||||
pub cache_database: OnceCell<DatabaseConnection>,
|
|
||||||
pub peers: OnceCell<RwLock<HashMap<PeerId, Multiaddr>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Global {
|
|
||||||
pub fn get_core_config(&self) -> Option<&CoreConfig> {
|
|
||||||
self.core_config.get()
|
|
||||||
}
|
|
||||||
pub async fn get_or_init_core_config(&self, config: CoreConfig) -> &CoreConfig {
|
|
||||||
self.core_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_core_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());
|
|
||||||
}
|
|
||||||
}
|
|
18
lazy-supplements-core/src/global/peers.rs
Normal file
18
lazy-supplements-core/src/global/peers.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use libp2p::bytes::buf::UninitSlice;
|
||||||
|
use tokio::sync::{OnceCell, RwLock, RwLockReadGuard};
|
||||||
|
|
||||||
|
use crate::cache::entity::PeerModel;
|
||||||
|
|
||||||
|
static UNINITIALIZED_MESSAGE: &str = "Global peer set uninitialized!";
|
||||||
|
pub trait GlobalPeers {
|
||||||
|
fn get_peers_once_cell(&'static self) -> &OnceCell<RwLock<HashSet<PeerModel>>>;
|
||||||
|
async fn write_peers(&'static self) -> tokio::sync::RwLockWriteGuard<'_ ,HashSet<PeerModel>> {
|
||||||
|
self.get_peers_once_cell().get().expect(UNINITIALIZED_MESSAGE).write().await
|
||||||
|
}
|
||||||
|
async fn read_peers(&'static self) -> RwLockReadGuard<'_, HashSet<PeerModel>> {
|
||||||
|
self.get_peers_once_cell().get().expect(UNINITIALIZED_MESSAGE).read().await
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
22
lazy-supplements-core/src/global/storage_config.rs
Normal file
22
lazy-supplements-core/src/global/storage_config.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use sea_orm::{ConnectOptions, Database, DbErr, DatabaseConnection};
|
||||||
|
use sea_orm_migration::MigratorTrait;
|
||||||
|
use crate::{config::StorageConfig, error::Error};
|
||||||
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
|
static UNINITIALIZED_MESSAGE: &str = "global storage is uninitialized!";
|
||||||
|
|
||||||
|
pub trait GlobalStorageConfig {
|
||||||
|
|
||||||
|
fn init_storage_config(&'static self, config: StorageConfig) {
|
||||||
|
self.get_storage_config_once_cell().set(config).unwrap();
|
||||||
|
}
|
||||||
|
fn get_storage_config_once_cell(&'static self) -> &'static OnceCell<StorageConfig>;
|
||||||
|
fn get_storage_config(&'static self) -> Option<&'static StorageConfig> {
|
||||||
|
self.get_storage_config_once_cell().get()
|
||||||
|
}
|
||||||
|
fn get_and_unwrap_storage_config(&'static self) -> &'static StorageConfig {
|
||||||
|
self.get_storage_config().expect(UNINITIALIZED_MESSAGE)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ pub mod data;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod global;
|
pub mod global;
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
|
pub mod message;
|
||||||
pub mod migration;
|
pub mod migration;
|
||||||
pub mod p2p;
|
pub mod p2p;
|
||||||
#[cfg(any(test, feature="test"))]
|
#[cfg(any(test, feature="test"))]
|
||||||
|
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,11 +35,11 @@ impl Event {
|
||||||
match x {
|
match x {
|
||||||
mdns::Event::Discovered(e) => {
|
mdns::Event::Discovered(e) => {
|
||||||
for peer in e {
|
for peer in e {
|
||||||
let mut peers = crate::global::GLOBAL.write_peers().await;
|
//let mut peers = crate::global::GLOBAL.write_peers().await;
|
||||||
peers.insert(peer.0, peer.1);
|
//peers.insert(peer.0, peer.1);
|
||||||
}
|
}
|
||||||
let peers = crate::global::GLOBAL.read_peers().await;
|
//let peers = crate::global::GLOBAL.read_peers().await;
|
||||||
println!("Peers: {peers:?}");
|
//println!("Peers: {peers:?}");
|
||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
use std::{path::PathBuf, sync::LazyLock};
|
use std::{path::PathBuf, sync::LazyLock};
|
||||||
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
use crate::{ config::PartialConfig, message::Message};
|
||||||
|
|
||||||
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
||||||
pub static TEST_DIR_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
|
pub static TEST_DIR_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
|
||||||
let pkg_name = env!("CARGO_PKG_NAME");
|
let pkg_name = env!("CARGO_PKG_NAME");
|
||||||
|
@ -15,3 +19,22 @@ pub static TEST_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
|
||||||
pub static TEST_DATABASE_PATH: std::sync::LazyLock<PathBuf> = std::sync::LazyLock::new(|| {
|
pub static TEST_DATABASE_PATH: std::sync::LazyLock<PathBuf> = std::sync::LazyLock::new(|| {
|
||||||
TEST_DIR_PATH.join("lazy-supplements.sqlite")
|
TEST_DIR_PATH.join("lazy-supplements.sqlite")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
#[cfg(any(test, feature="test"))]
|
||||||
|
pub fn test_cbor_serialize_deserialize<T>(src: T)
|
||||||
|
where T: DeserializeOwned + Serialize + PartialEq + std::fmt::Debug
|
||||||
|
{
|
||||||
|
let mut buf: Vec<u8> = Vec::new();
|
||||||
|
ciborium::into_writer(&src, &mut buf).unwrap();
|
||||||
|
let dst: T = ciborium::from_reader(buf.as_slice()).unwrap();
|
||||||
|
assert_eq!(src, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature="test"))]
|
||||||
|
pub fn test_toml_serialize_deserialize<T>(src: T)
|
||||||
|
where T: DeserializeOwned + Serialize + PartialEq + std::fmt::Debug
|
||||||
|
{
|
||||||
|
let buf = toml::to_string(&src).unwrap();
|
||||||
|
let dst: T = toml::from_str(&buf).unwrap();
|
||||||
|
assert_eq!(src, dst);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue