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
119
120
121
pub mod env;

use anyhow::Error;
use async_trait::async_trait;
pub use env::FromEnv;
use meio::LiteTask;
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Debug;
use std::marker::PhantomData;
use std::path::PathBuf;
use tokio::fs::{remove_file, File};
use tokio::io::{AsyncReadExt, AsyncWriteExt};

pub trait Config: Debug + Send + 'static {}

#[async_trait]
pub trait ReadableConfig: Config + DeserializeOwned {
    async fn read(path: PathBuf) -> Result<Self, Error> {
        let mut file = File::open(path).await?;
        let mut contents = Vec::new();
        file.read_to_end(&mut contents).await?;
        let config: Self = toml::from_slice(&contents)?;
        Ok(config)
    }
}

pub struct ReadConfigFile<T> {
    path: Option<PathBuf>,
    default_path: &'static str,
    _config: PhantomData<T>,
}

impl<T> ReadConfigFile<T> {
    pub fn new(path: Option<PathBuf>, default_path: &'static str) -> Self {
        Self {
            path,
            default_path,
            _config: PhantomData,
        }
    }
}

#[async_trait]
impl<T: ReadableConfig> LiteTask for ReadConfigFile<T> {
    type Output = Option<T>;

    async fn interruptable_routine(mut self) -> Result<Self::Output, Error> {
        let config = {
            if let Some(path) = self.path {
                Some(T::read(path).await?)
            } else {
                let path = self.default_path.into();
                T::read(path).await.ok()
            }
        };
        log::trace!("Config ready: {:?}", config);
        Ok(config)
    }
}

#[async_trait]
pub trait WritableConfig: Config + Serialize + Sync {
    async fn write(&self, path: PathBuf) -> Result<(), Error> {
        let mut file = File::create(path).await?;
        let data = toml::to_vec(self)?;
        file.write_all(&data).await?;
        Ok(())
    }

    async fn drop_file(path: PathBuf) -> Result<(), Error> {
        remove_file(path).await?;
        Ok(())
    }
}

pub struct WriteConfigFile<T> {
    path: PathBuf,
    config: T,
}

impl<T> WriteConfigFile<T> {
    pub fn new(path: PathBuf, config: T) -> Self {
        Self { path, config }
    }
}

#[async_trait]
impl<T: WritableConfig> LiteTask for WriteConfigFile<T> {
    type Output = ();

    async fn interruptable_routine(mut self) -> Result<Self::Output, Error> {
        log::trace!("Storing config to {:?}: {:?}", self.path, self.config);
        self.config.write(self.path).await?;
        Ok(())
    }
}

pub struct DropConfigFile<T> {
    path: PathBuf,
    _config: PhantomData<T>,
}

impl<T> DropConfigFile<T> {
    pub fn new(path: PathBuf) -> Self {
        Self {
            path,
            _config: PhantomData,
        }
    }
}

#[async_trait]
impl<T: WritableConfig> LiteTask for DropConfigFile<T> {
    type Output = ();

    async fn interruptable_routine(mut self) -> Result<Self::Output, Error> {
        log::trace!("Drop config file: {:?}", self.path);
        T::drop_file(self.path).await?;
        Ok(())
    }
}