1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use crate::models::error::{ModelError, ModelErrorKind};
use crate::models::PluginID;
use chrono::{DateTime, Duration, Utc};
use failure::ResultExt;
use serde_derive::{Deserialize, Serialize};
use std::fs;
use std::path::{Path, PathBuf};

static SYNC_AMOUNT_DEFAULT: u32 = 300;
static CONFIG_NAME: &str = "newsflash.json";

#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    backend: Option<String>,
    sync_amount: u32,
    #[serde(with = "json_time")]
    last_sync: DateTime<Utc>,
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    keep_articles_days: Option<u64>,
    #[serde(skip_serializing)]
    #[serde(skip_deserializing)]
    path: PathBuf,
}

impl Config {
    pub fn open(path: &Path) -> Result<Self, ModelError> {
        let path = path.join(CONFIG_NAME);
        if path.as_path().exists() {
            let data = fs::read_to_string(&path).context(ModelErrorKind::IO)?;
            let mut config: Self = serde_json::from_str(&data).context(ModelErrorKind::Json)?;
            config.path = path;
            return Ok(config);
        }

        let config = Config {
            backend: None,
            sync_amount: SYNC_AMOUNT_DEFAULT,
            last_sync: Utc::now() - Duration::weeks(1),
            keep_articles_days: None,
            path,
        };
        config.write()?;
        Ok(config)
    }

    fn write(&self) -> Result<(), ModelError> {
        let data = serde_json::to_string_pretty(self).context(ModelErrorKind::Json)?;
        fs::write(&self.path, data).context(ModelErrorKind::IO)?;
        Ok(())
    }

    pub fn get_backend(&self) -> Option<PluginID> {
        self.backend.as_ref().map(|plugin_id| PluginID::new(plugin_id))
    }

    pub fn set_backend(&mut self, backend: Option<&PluginID>) -> Result<(), ModelError> {
        self.backend = backend.map(|plugin_id| plugin_id.as_str().to_owned());
        self.write()?;
        Ok(())
    }

    pub fn get_sync_amount(&self) -> u32 {
        self.sync_amount
    }

    pub fn set_sync_amount(&mut self, amount: u32) -> Result<(), ModelError> {
        self.sync_amount = amount;
        self.write()?;
        Ok(())
    }

    pub fn get_last_sync(&self) -> DateTime<Utc> {
        self.last_sync
    }

    pub fn set_last_sync(&mut self, time: DateTime<Utc>) -> Result<(), ModelError> {
        self.last_sync = time;
        self.write()?;
        Ok(())
    }

    pub fn get_keep_articles_duration(&self) -> Option<Duration> {
        self.keep_articles_days.map(|days| Duration::days(days as i64))
    }

    pub fn set_keep_articles_duration(&mut self, duration: Option<Duration>) -> Result<(), ModelError> {
        if let Some(duration) = duration {
            if duration < Duration::days(1) {
                log::error!("Duration to keep articles is not allowed to be < 1 Day. Duration: {}", duration);
                return Err(ModelErrorKind::Input.into());
            }
        }
        self.keep_articles_days = duration.map(|d| d.num_days() as u64);
        self.write()?;
        Ok(())
    }
}

mod json_time {
    use chrono::{DateTime, Utc};
    use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};

    pub fn time_to_json(t: DateTime<Utc>) -> String {
        t.to_rfc3339()
    }

    pub fn serialize<S: Serializer>(time: &DateTime<Utc>, serializer: S) -> Result<S::Ok, S::Error> {
        time_to_json(*time).serialize(serializer)
    }

    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<DateTime<Utc>, D::Error> {
        let time: String = Deserialize::deserialize(deserializer)?;
        Ok(DateTime::parse_from_rfc3339(&time).map_err(D::Error::custom)?.with_timezone(&Utc))
    }
}