termusiclib/config/v2/server/
config_extra.rs

1use std::{borrow::Cow, fmt::Write as _, path::Path};
2
3use anyhow::{Context, Result};
4use figment::{
5    Figment,
6    providers::{Format, Toml},
7};
8use serde::{Deserialize, Serialize};
9
10use crate::utils::get_app_config_path;
11
12use super::ServerSettings;
13
14/// The filename of the server config
15pub const FILE_NAME: &str = "server.toml";
16
17/// The type used by the application / the latest config version
18///
19/// This type exists so that it is easier to differentiate when the explicit type is meant, or later meant to be changed as a whole
20type ApplicationType = ServerSettings;
21
22/// Top-Level struct that wraps [`ServerConfigVersioned`] and a default version thereof if no `version` field exists.
23///
24/// This is required as serde does not have a concept of `default_tag` yet, see <https://github.com/serde-rs/serde/issues/2231>
25#[derive(Debug, Clone, Serialize)]
26#[serde(untagged)]
27pub enum ServerConfigVersionedDefaulted<'a> {
28    /// Case if config contains a `version` field
29    Versioned(ServerConfigVersioned<'a>),
30    /// Case if the config does not contain a `version` field, assume type of [`ServerConfigVersioned::V2`]
31    Unversioned(ServerSettings),
32}
33
34// Manual implementation because deserialize "serde(untagged)" error are *really* bad
35impl<'a, 'de> Deserialize<'de> for ServerConfigVersionedDefaulted<'a> {
36    fn deserialize<D>(deserializer: D) -> std::prelude::v1::Result<Self, D::Error>
37    where
38        D: serde::Deserializer<'de>,
39    {
40        // Note that those are marked "private", but are used in the derives, and there is no to me known public way, but saves some implementation complexity
41        let content =
42            <serde::__private::de::Content<'_> as serde::Deserialize>::deserialize(deserializer)?;
43        let deserializer = serde::__private::de::ContentRefDeserializer::<D::Error>::new(&content);
44
45        let mut err_res = String::new();
46
47        match <ServerConfigVersioned<'a>>::deserialize(deserializer)
48            .map(ServerConfigVersionedDefaulted::Versioned)
49        {
50            Ok(val) => return Ok(val),
51            Err(err) => {
52                let _ = write!(err_res, "{err:#}");
53            }
54        }
55        match ServerSettings::deserialize(deserializer)
56            .map(ServerConfigVersionedDefaulted::Unversioned)
57        {
58            Ok(val) => return Ok(val),
59            // no need to check if "err_res" is empty, as this code can only be executed if the above has failed
60            Err(err) => {
61                let err_str = err.to_string();
62                // only add if the error is different; otherwise you get duplicated errors
63                if err_str != err_res {
64                    let _ = write!(err_res, "\n{err_str:#}");
65                }
66            }
67        }
68
69        Err(<D::Error as serde::de::Error>::custom(err_res))
70    }
71}
72
73// Note: for saving, see
74impl ServerConfigVersionedDefaulted<'_> {
75    /// Read a config file, needs to be toml formatted
76    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
77        let path = path.as_ref();
78        {
79            let v1_config_path = path
80                .parent()
81                .context("expected server config path to have a parent")?
82                .join(super::super::super::v1::FILE_NAME);
83            if !path.exists() && v1_config_path.exists() {
84                info!("New config file does not exist, but old one does exist.");
85                return Self::migrate_from_v1(&v1_config_path, path);
86            }
87        }
88
89        // if path does not exist, create default instance and save it
90        if !path.exists() {
91            let config = ServerSettings::default();
92            Self::save_file(path, &config)?;
93            return Ok(Self::Unversioned(config));
94        }
95
96        let data: Self = Figment::new().merge(Toml::file(path)).extract()?;
97
98        Ok(data)
99    }
100
101    /// Read a config file from the default set app-path
102    pub fn from_config_path() -> Result<Self> {
103        let server_config_path = get_app_config_path()?.join(FILE_NAME);
104
105        Self::from_file(server_config_path)
106    }
107
108    /// Load the old settings, then transform them into the new settings
109    // public in config_v2 module so that the TUI can migrate the server config before itself
110    pub(in super::super) fn migrate_from_v1(_v1_path: &Path, v2_path: &Path) -> Result<Self> {
111        use super::super::super::v1::Settings;
112
113        info!("Migrating server config from v1 format to v2");
114
115        let old_settings = {
116            let mut settings = Settings::default();
117            settings.load()?;
118
119            settings
120        };
121
122        let new_settings = ServerSettings::try_from(old_settings)
123            .context("Failed to convert server config from v1 to v2 config")?;
124
125        // save the file directly to not have to re-do the convertion again, even if config does not change
126        Self::save_file(v2_path, &new_settings)?;
127
128        Ok(Self::Unversioned(new_settings))
129    }
130
131    /// Save type used by the application as a config file
132    ///
133    /// Will only save the latest version
134    pub fn save_file<'b, P: AsRef<Path>>(path: P, config: &'b ApplicationType) -> Result<()> {
135        // wrap the data in the latest version for saving
136        let data = ServerConfigVersionedDefaulted::<'b>::Versioned(ServerConfigVersioned::V2(
137            Cow::Borrowed(config),
138        ));
139        std::fs::write(path, toml::to_string(&data)?)?;
140
141        Ok(())
142    }
143
144    /// Save the given config to the default set app-path
145    pub fn save_config_path(config: &ApplicationType) -> Result<()> {
146        let server_config_path = get_app_config_path()?.join(FILE_NAME);
147
148        Self::save_file(server_config_path, config)
149    }
150
151    /// Convert Into the type used by the application, instead of what is parsed
152    ///
153    /// Will convert any version into the latest
154    #[must_use]
155    pub fn into_settings(self) -> ApplicationType {
156        let versioned = match self {
157            ServerConfigVersionedDefaulted::Versioned(versioned) => versioned,
158            ServerConfigVersionedDefaulted::Unversioned(v) => return v,
159        };
160
161        versioned.into_settings()
162    }
163}
164
165/// Enum that contains all versions for the server config
166#[derive(Debug, Clone, Deserialize, Serialize)]
167#[serde(tag = "version")]
168pub enum ServerConfigVersioned<'a> {
169    /// V2 is considered stable / non-backwards breaking changes allowed after b0daaddf58ec7f46b364fcf999c50d3aa043308f
170    // Cow data so that we can use a reference for saving instead of cloning
171    // Starting at Version 2 as the old format is referred as v1, but lives in a different config file name
172    #[serde(rename = "2")]
173    V2(Cow<'a, ServerSettings>),
174}
175
176impl ServerConfigVersioned<'_> {
177    /// Convert Into the type used by the application, instead of what is parsed
178    ///
179    /// Will convert any version into the latest
180    #[must_use]
181    pub fn into_settings(self) -> ApplicationType {
182        match self {
183            ServerConfigVersioned::V2(v) => v.into_owned(),
184        }
185    }
186}