d
This commit is contained in:
parent
2893b22d03
commit
04bd3e62b6
11 changed files with 306 additions and 2459 deletions
2522
Cargo.lock
generated
2522
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,5 +1,4 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"panorama-gui",
|
"core",
|
||||||
"panorama-core",
|
|
||||||
]
|
]
|
||||||
|
|
12
core/Cargo.toml
Normal file
12
core/Cargo.toml
Normal 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
76
core/src/config/mod.rs
Normal 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,
|
||||||
|
}
|
83
core/src/config/watcher.rs
Normal file
83
core/src/config/watcher.rs
Normal 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
0
core/src/imap.rs
Normal file
37
core/src/main.rs
Normal file
37
core/src/main.rs
Normal 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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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]
|
|
|
@ -1,5 +0,0 @@
|
||||||
pub struct Panorama {}
|
|
||||||
|
|
||||||
impl Panorama {
|
|
||||||
pub fn init() {}
|
|
||||||
}
|
|
|
@ -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"] }
|
|
|
@ -1,6 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
Loading…
Reference in a new issue