This commit is contained in:
Michael Zhang 2021-07-29 20:17:14 -05:00
parent 2893b22d03
commit 04bd3e62b6
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
11 changed files with 306 additions and 2459 deletions

2522
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,4 @@
[workspace]
members = [
"panorama-gui",
"panorama-core",
"core",
]

12
core/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "panorama-core"
version = "0.1.0"
edition = "2018"
[dependencies]
anyhow = "1.0.42"
serde = { version = "1.0.126", features = ["derive"] }
tokio = { version = "1.9.0", features = ["full"] }
clap = "3.0.0-beta.2"
futures = "0.3.16"
inotify = { version = "0.9.3", features = ["stream"] }

76
core/src/config/mod.rs Normal file
View file

@ -0,0 +1,76 @@
mod watcher;
use std::path::{PathBuf, Path};
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use anyhow::Result;
/// Configuration
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
pub struct Config {
/// Version of the config to use
/// (potentially for migration later?)
pub version: String,
/// Directory to store panorama-related data in
pub data_dir: PathBuf,
/// Mail accounts
#[serde(rename = "mail")]
pub mail_accounts: HashMap<String, MailAccountConfig>,
}
impl Config {
pub async fn from_file(path: impl AsRef<Path>) -> Result<Self> {
let mut file = File::open(path.as_ref())?;
let mut contents = Vec::new();
file.read_to_end(&mut contents)?;
let config = toml::from_slice(&contents)?;
Ok(config)
}
}
/// Configuration for a single mail account
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MailAccountConfig {
/// Imap
pub imap: ImapConfig,
}
/// Configuring an IMAP server
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ImapConfig {
/// Host of the IMAP server (needs to be hostname for TLS)
pub server: String,
/// Port of the IMAP server
pub port: u16,
/// TLS
pub tls: TlsMethod,
/// Auth
#[serde(flatten)]
pub auth: ImapAuth,
}
/// Method of authentication for the IMAP server
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "auth")]
pub enum ImapAuth {
/// Use plain username/password authentication
#[serde(rename = "plain")]
#[allow(missing_docs)]
Plain { username: String, password: String },
}
/// Describes when to perform the TLS handshake
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum TlsMethod {
/// Perform TLS handshake immediately upon connection
#[serde(rename = "on")]
On,
/// Perform TLS handshake after issuing the STARTTLS command
#[serde(rename = "starttls")]
Starttls,
/// Don't perform TLS handshake at all (unsecured)
#[serde(rename = "off")]
Off,
}

View file

@ -0,0 +1,83 @@
use std::path::{Path, PathBuf};
use anyhow::{Result, Context};
use futures::{future::TryFutureExt, stream::StreamExt};
use inotify::Inotify;
use tokio::{task::JoinHandle, sync::watch};
use super::Config;
pub type ConfigWatcher = watch::Receiver<Config>;
/// Start the entire config watcher system, and return a [ConfigWatcher][self::ConfigWatcher],
/// which is a cloneable receiver of config update events.
pub fn spawn_config_watcher_system() -> Result<(JoinHandle<()>, ConfigWatcher)> {
let mut inotify = Inotify::init()?;
let xdg = BaseDirectories::new()?;
let config_home = xdg.get_config_home().join("panorama");
if !config_home.exists() {
fs::create_dir_all(&config_home)?;
}
inotify
.add_watch(&config_home, WatchMask::CLOSE_WRITE)
.context("adding watch for config home")?;
// let config_file_path = config_home.join("panorama.toml");
// if config_file_path.exists() {
// inotify
// .add_watch(config_file_path, WatchMask::ALL_EVENTS)
// .context("adding watch for config file")?;
// }
debug!("watching {:?}", config_home);
let (config_tx, config_update) = watch::channel(Config::default());
let handle = tokio::spawn(
start_inotify_stream(inotify, config_home, config_tx).unwrap_or_else(report_err),
);
Ok((handle, config_update))
}
async fn start_inotify_stream(
mut inotify: Inotify,
config_home: impl AsRef<Path>,
config_tx: watch::Sender<Config>,
) -> Result<()> {
let mut buffer = vec![0u8; 1024];
let mut event_stream = inotify.event_stream(&mut buffer)?;
let config_home = config_home.as_ref().to_path_buf();
let config_path = config_home.join("panorama.toml");
// first shot
{
let config = Config::from_file(&config_path).await?;
config_tx.send(config)?;
}
debug!("listening for inotify events");
while let Some(v) = event_stream.next().await {
let event = v.context("event")?;
debug!("inotify event: {:?}", event);
if let Some(name) = event.name {
let path = PathBuf::from(name);
let path_c = config_home
.clone()
.join(path.clone())
.canonicalize()
.context("osu")?;
if !path_c.exists() {
debug!("path {:?} doesn't exist", path_c);
continue;
}
// TODO: any better way to do this?
let config_path_c = config_path.canonicalize().context("cfg_path")?;
if config_path_c != path_c {
debug!("did not match {:?} {:?}", config_path_c, path_c);
continue;
}
debug!("reading config from {:?}", path_c);
let config = Config::from_file(path_c).await.context("read")?;
// debug!("sending config {:?}", config);
config_tx.send(config)?;
}
}
Ok(())
}

0
core/src/imap.rs Normal file
View file

37
core/src/main.rs Normal file
View file

@ -0,0 +1,37 @@
#[macro_use]
extern crate serde;
extern crate futures;
mod config;
mod imap;
use std::path::PathBuf;
use clap::Clap;
use anyhow::Result;
use crate::config::Config;
/// Panorama core
#[derive(Debug, Clap)]
struct Options {
/// Config file path (defaults to XDG)
#[clap(long = "config", short = 'c')]
config_file: Option<PathBuf>,
}
async fn run_with_config(config: Config) -> Result<()> {
Ok(())
}
#[tokio::main]
async fn main() -> Result<()> {
let opts = Options::parse();
println!("{:?}", opts);
let (tx, mut rx) = watch::channel(());
loop {
}
}

View file

@ -1,8 +0,0 @@
[package]
name = "panorama-core"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View file

@ -1,5 +0,0 @@
pub struct Panorama {}
impl Panorama {
pub fn init() {}
}

View file

@ -1,13 +0,0 @@
[package]
name = "panorama-gui"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.42"
iced = "0.3.0"
iced_native = "0.4.0"
structopt = "0.3.22"
tokio = { version = "1.8.2", features = ["full"] }

View file

@ -1,6 +0,0 @@
use anyhow::Result;
#[tokio::main]
async fn main() -> Result<()> {
Ok(())
}