mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-10-22 00:54:37 -04:00
157 lines
5.5 KiB
Rust
157 lines
5.5 KiB
Rust
use rspc::Type;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::{
|
|
fs::File,
|
|
io::{self, BufReader, Seek, SeekFrom, Write},
|
|
path::{Path, PathBuf},
|
|
sync::Arc,
|
|
};
|
|
use thiserror::Error;
|
|
use tokio::sync::{RwLock, RwLockWriteGuard};
|
|
use uuid::Uuid;
|
|
|
|
/// NODE_STATE_CONFIG_NAME is the name of the file which stores the NodeState
|
|
pub const NODE_STATE_CONFIG_NAME: &str = "node_state.sdconfig";
|
|
|
|
/// ConfigMetadata is a part of node configuration that is loaded before the main configuration and contains information about the schema of the config.
|
|
/// This allows us to migrate breaking changes to the config format between Spacedrive releases.
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Type)]
|
|
pub struct ConfigMetadata {
|
|
/// version of Spacedrive. Determined from `CARGO_PKG_VERSION` environment variable.
|
|
pub version: Option<String>,
|
|
}
|
|
|
|
impl Default for ConfigMetadata {
|
|
fn default() -> Self {
|
|
Self {
|
|
version: Some(env!("CARGO_PKG_VERSION").into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// NodeConfig is the configuration for a node. This is shared between all libraries and is stored in a JSON file on disk.
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Type)]
|
|
pub struct NodeConfig {
|
|
#[serde(flatten)]
|
|
pub metadata: ConfigMetadata,
|
|
/// id is a unique identifier for the current node. Each node has a public identifier (this one) and is given a local id for each library (done within the library code).
|
|
pub id: Uuid,
|
|
/// name is the display name of the current node. This is set by the user and is shown in the UI. // TODO: Length validation so it can fit in DNS record
|
|
pub name: String,
|
|
// the port this node uses for peer to peer communication. By default a random free port will be chosen each time the application is started.
|
|
pub p2p_port: Option<u32>,
|
|
// /// The P2P identity public key
|
|
// pub p2p_cert: Vec<u8>,
|
|
// /// The P2P identity private key
|
|
// pub p2p_key: Vec<u8>,
|
|
// /// The address of the Spacetunnel discovery service being used.
|
|
// pub spacetunnel_addr: Option<String>,
|
|
}
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum NodeConfigError {
|
|
#[error("error saving or loading the config from the filesystem")]
|
|
IO(#[from] io::Error),
|
|
#[error("error serializing or deserializing the JSON in the config file")]
|
|
Json(#[from] serde_json::Error),
|
|
#[error("error migrating the config file")]
|
|
Migration(String),
|
|
}
|
|
|
|
impl NodeConfig {
|
|
fn default() -> Self {
|
|
NodeConfig {
|
|
id: Uuid::new_v4(),
|
|
name: match hostname::get() {
|
|
// SAFETY: This is just for display purposes so it doesn't matter if it's lossy
|
|
Ok(hostname) => hostname.to_string_lossy().into_owned(),
|
|
Err(err) => {
|
|
eprintln!("Falling back to default node name as an error occurred getting your systems hostname: '{}'", err);
|
|
"my-spacedrive".into()
|
|
}
|
|
},
|
|
p2p_port: None,
|
|
metadata: ConfigMetadata {
|
|
version: Some(env!("CARGO_PKG_VERSION").into()),
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct NodeConfigManager(RwLock<NodeConfig>, PathBuf);
|
|
|
|
impl NodeConfigManager {
|
|
/// new will create a new NodeConfigManager with the given path to the config file.
|
|
pub(crate) async fn new(data_path: PathBuf) -> Result<Arc<Self>, NodeConfigError> {
|
|
Ok(Arc::new(Self(
|
|
RwLock::new(Self::read(&data_path).await?),
|
|
data_path,
|
|
)))
|
|
}
|
|
|
|
/// get will return the current NodeConfig in a read only state.
|
|
pub(crate) async fn get(&self) -> NodeConfig {
|
|
self.0.read().await.clone()
|
|
}
|
|
|
|
/// data_directory returns the path to the directory storing the configuration data.
|
|
pub(crate) fn data_directory(&self) -> PathBuf {
|
|
self.1.clone()
|
|
}
|
|
|
|
/// write allows the user to update the configuration. This is done in a closure while a Mutex lock is held so that the user can't cause a race condition if the config were to be updated in multiple parts of the app at the same time.
|
|
#[allow(unused)]
|
|
pub(crate) async fn write<F: FnOnce(RwLockWriteGuard<NodeConfig>)>(
|
|
&self,
|
|
mutation_fn: F,
|
|
) -> Result<NodeConfig, NodeConfigError> {
|
|
mutation_fn(self.0.write().await);
|
|
let config = self.0.read().await;
|
|
Self::save(&self.1, &config).await?;
|
|
Ok(config.clone())
|
|
}
|
|
|
|
/// read will read the configuration from disk and return it.
|
|
async fn read(base_path: &PathBuf) -> Result<NodeConfig, NodeConfigError> {
|
|
let path = Path::new(base_path).join(NODE_STATE_CONFIG_NAME);
|
|
|
|
match path.try_exists().unwrap() {
|
|
true => {
|
|
let mut file = File::open(&path)?;
|
|
let base_config: ConfigMetadata =
|
|
serde_json::from_reader(BufReader::new(&mut file))?;
|
|
|
|
Self::migrate_config(base_config.version, path)?;
|
|
|
|
file.seek(SeekFrom::Start(0))?;
|
|
Ok(serde_json::from_reader(BufReader::new(&mut file))?)
|
|
}
|
|
false => {
|
|
let config = NodeConfig::default();
|
|
Self::save(base_path, &config).await?;
|
|
Ok(config)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// save will write the configuration back to disk
|
|
async fn save(base_path: &PathBuf, config: &NodeConfig) -> Result<(), NodeConfigError> {
|
|
let path = Path::new(base_path).join(NODE_STATE_CONFIG_NAME);
|
|
File::create(path)?.write_all(serde_json::to_string(config)?.as_bytes())?;
|
|
Ok(())
|
|
}
|
|
|
|
/// migrate_config is a function used to apply breaking changes to the config file.
|
|
fn migrate_config(
|
|
current_version: Option<String>,
|
|
config_path: PathBuf,
|
|
) -> Result<(), NodeConfigError> {
|
|
match current_version {
|
|
None => {
|
|
Err(NodeConfigError::Migration(format!("Your Spacedrive config file stored at '{}' is missing the `version` field. If you just upgraded please delete the file and restart Spacedrive! Please note this upgrade will stop using your old 'library.db' as the folder structure has changed.", config_path.display())))
|
|
}
|
|
_ => Ok(()),
|
|
}
|
|
}
|
|
}
|