mbsync
This commit is contained in:
parent
24cf869da5
commit
dfdb71dea2
18 changed files with 545 additions and 52 deletions
114
Cargo.lock
generated
114
Cargo.lock
generated
|
@ -4,9 +4,9 @@ version = 3
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.42"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486"
|
||||
checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
|
@ -50,9 +50,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
|||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
|
@ -116,9 +116,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.0.0-beta.2"
|
||||
version = "3.0.0-beta.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
|
||||
checksum = "fcd70aa5597dbc42f7217a543f9ef2768b2ef823ba29036072d30e1d88e98406"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
|
@ -129,15 +129,14 @@ dependencies = [
|
|||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.0.0-beta.2"
|
||||
version = "3.0.0-beta.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1"
|
||||
checksum = "0b5bb0d655624a0b8770d1c178fb8ffcb1f91cc722cb08f451e3dc72465421ac"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
|
@ -181,6 +180,17 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.10.2"
|
||||
|
@ -342,9 +352,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.3"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726"
|
||||
checksum = "d7f3675cfef6a30c8031cf9e6493ebdc3bb3272a3fea3923c4210d1830e6a472"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
|
@ -407,9 +417,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.4.1"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68"
|
||||
checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
|
@ -489,15 +499,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.52"
|
||||
version = "0.3.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752"
|
||||
checksum = "e4bf49d50e2961077d9c99f4b7997d770a1114f087c3c2e0069b36c13fc2979d"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
@ -523,9 +533,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.98"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
|
@ -545,6 +555,19 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mbsync"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"clap",
|
||||
"derivative",
|
||||
"derive_builder",
|
||||
"panorama-imap",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.3.4"
|
||||
|
@ -632,9 +655,9 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
|||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "2.4.0"
|
||||
version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
|
||||
checksum = "6acbef58a60fe69ab50510a55bc8cdd4d6cf2283d27ad338f54cb52747a9cf2d"
|
||||
|
||||
[[package]]
|
||||
name = "panorama-daemon"
|
||||
|
@ -643,6 +666,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"async-trait",
|
||||
"clap",
|
||||
"derivative",
|
||||
"futures",
|
||||
"hyper",
|
||||
"inotify",
|
||||
|
@ -863,18 +887,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.127"
|
||||
version = "1.0.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8"
|
||||
checksum = "1056a0db1978e9dbf0f6e4fca677f6f9143dc1c19de346f22cac23e422196834"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.127"
|
||||
version = "1.0.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc"
|
||||
checksum = "13af2fbb8b60a8950d6c72a56d2095c28870367cc8e10c55e9745bac4995a2c4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -935,9 +959,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.74"
|
||||
version = "1.0.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
|
||||
checksum = "b7f58f7e8eaa0009c5fec437aabf511bd9933e4b2d7407bd05273c01a8906ea7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -961,9 +985,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.12.1"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
|
||||
checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
@ -990,9 +1014,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.9.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b7b349f11a7047e6d1276853e612d152f5e8a352c61917887cc2169e2366b4c"
|
||||
checksum = "01cf844b23c6131f624accf65ce0e4e9956a8bb329400ea5bcc26ae3a5c20b0b"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
|
@ -1072,9 +1096,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.18"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052"
|
||||
checksum = "2ca517f43f0fb96e0c3072ed5c275fe5eece87e8cb52f4a77b69226d3b1c9df8"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
@ -1139,9 +1163,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.75"
|
||||
version = "0.2.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586"
|
||||
checksum = "8ce9b1b516211d33767048e5d47fa2a381ed8b76fc48d2ce4aa39877f9f183e0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
|
@ -1149,9 +1173,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.75"
|
||||
version = "0.2.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f"
|
||||
checksum = "cfe8dc78e2326ba5f845f4b5bf548401604fa20b1dd1d365fb73b6c1d6364041"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
|
@ -1164,9 +1188,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.75"
|
||||
version = "0.2.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c"
|
||||
checksum = "44468aa53335841d9d6b6c023eaab07c0cd4bddbcfdee3e2bb1e8d2cb8069fef"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
@ -1174,9 +1198,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.75"
|
||||
version = "0.2.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f"
|
||||
checksum = "0195807922713af1e67dc66132c7328206ed9766af3858164fb583eedc25fbad"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1187,15 +1211,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.75"
|
||||
version = "0.2.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2"
|
||||
checksum = "acdb075a845574a1fa5f09fd77e43f7747599301ea3417a9fbffdeedfc1f4a29"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.52"
|
||||
version = "0.3.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696"
|
||||
checksum = "224b2f6b67919060055ef1a67807367c2066ed520c3862cc013d26cf893a783c"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
|
|
@ -4,6 +4,7 @@ members = [
|
|||
"imap",
|
||||
"smtp",
|
||||
"proto-common",
|
||||
"mbsync",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
|
3
Justfile
3
Justfile
|
@ -3,3 +3,6 @@ fmt:
|
|||
|
||||
doc:
|
||||
cargo doc --workspace --no-deps
|
||||
|
||||
watch:
|
||||
cargo watch -c
|
|
@ -7,6 +7,7 @@ edition = "2018"
|
|||
anyhow = "1.0.42"
|
||||
async-trait = "0.1.50"
|
||||
clap = "3.0.0-beta.2"
|
||||
derivative = "2.2.0"
|
||||
futures = "0.3.16"
|
||||
hyper = { version = "0.14.11", features = ["server", "http2", "stream"] }
|
||||
inotify = { version = "0.9.3", features = ["stream"] }
|
||||
|
|
|
@ -59,12 +59,18 @@ pub struct ImapConfig {
|
|||
}
|
||||
|
||||
/// Method of authentication for the IMAP server
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, Derivative)]
|
||||
#[derivative(Debug)]
|
||||
#[serde(tag = "auth")]
|
||||
pub enum ImapAuth {
|
||||
/// Use plain username/password authentication
|
||||
#[serde(rename = "plain")]
|
||||
Plain { username: String, password: String },
|
||||
Plain {
|
||||
username: String,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
password: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// Describes when to perform the TLS handshake
|
||||
|
|
|
@ -6,6 +6,8 @@ extern crate anyhow;
|
|||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate derivative;
|
||||
|
||||
mod config;
|
||||
mod mail;
|
||||
|
|
|
@ -84,7 +84,7 @@ impl DisplayBytes for Command {
|
|||
#[cfg(feature = "rfc2177")]
|
||||
Command::Idle => write_bytes!(w, b"IDLE"),
|
||||
|
||||
_ => Ok(()),
|
||||
_ => todo!("unimplemented command"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use super::response::{
|
|||
};
|
||||
use super::rfc2234::{is_char, is_cr, is_ctl, is_digit, is_dquote, is_lf, is_sp, CRLF, DQUOTE, SP};
|
||||
|
||||
#[macro_export]
|
||||
/// Grammar rule `T / nil` produces `Option<T>`
|
||||
macro_rules! opt_nil {
|
||||
($t:expr) => {
|
||||
alt((map($t, Some), map(crate::proto::rfc3501::nil, |_| None)))
|
||||
|
|
13
mbsync/Cargo.toml
Normal file
13
mbsync/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "mbsync"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.42"
|
||||
bitflags = "1.2.1"
|
||||
clap = "3.0.0-beta.2"
|
||||
derivative = "2.2.0"
|
||||
derive_builder = "0.10.2"
|
||||
panorama-imap = { path = "../imap" }
|
||||
tokio = { version = "1.9.0", features = ["full"] }
|
315
mbsync/src/config.rs
Normal file
315
mbsync/src/config.rs
Normal file
|
@ -0,0 +1,315 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
pub fn read_from_file(path: impl AsRef<Path>) -> Result<Config> {
|
||||
let path = path.as_ref();
|
||||
let file = File::open(path)?;
|
||||
read_from_reader(file)
|
||||
}
|
||||
|
||||
pub fn read_from_reader<R: Read>(r: R) -> Result<Config> {
|
||||
let r = BufReader::new(r);
|
||||
|
||||
let mut accounts = HashMap::new();
|
||||
let mut stores = HashMap::new();
|
||||
let mut channels = HashMap::new();
|
||||
|
||||
enum Section {
|
||||
ImapAccount(ImapAccountBuilder),
|
||||
ImapStore(ImapStoreBuilder),
|
||||
MaildirStore(MaildirStoreBuilder),
|
||||
Channel(ChannelBuilder),
|
||||
}
|
||||
let mut current_section = None;
|
||||
let finish_section = |accounts: &mut HashMap<_, _>,
|
||||
stores: &mut HashMap<_, _>,
|
||||
channels: &mut HashMap<_, _>,
|
||||
current_section: &mut Option<Section>|
|
||||
-> Result<()> {
|
||||
match current_section {
|
||||
Some(Section::ImapAccount(builder)) => {
|
||||
let account = builder.build()?;
|
||||
accounts.insert(account.name.clone(), Account::Imap(account));
|
||||
}
|
||||
Some(Section::ImapStore(builder)) => {
|
||||
let store = builder.build()?;
|
||||
stores.insert(store.name.clone(), Store::Imap(store));
|
||||
}
|
||||
Some(Section::MaildirStore(builder)) => {
|
||||
let store = builder.build()?;
|
||||
stores.insert(store.name.clone(), Store::Maildir(store));
|
||||
}
|
||||
Some(Section::Channel(builder)) => {
|
||||
let channel = builder.build()?;
|
||||
channels.insert(channel.name.clone(), channel);
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
*current_section = None;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for line in r.lines() {
|
||||
let line = line?.trim().to_string();
|
||||
if line.is_empty() || line.starts_with('#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
let parts = line.split(" ").collect::<Vec<_>>();
|
||||
|
||||
match parts[0].to_lowercase().as_str() {
|
||||
"imapaccount" => {
|
||||
finish_section(
|
||||
&mut accounts,
|
||||
&mut stores,
|
||||
&mut channels,
|
||||
&mut current_section,
|
||||
)?;
|
||||
let mut builder = ImapAccountBuilder::default();
|
||||
builder.name(parts[1].to_string());
|
||||
current_section = Some(Section::ImapAccount(builder));
|
||||
}
|
||||
"host" => match current_section.as_mut() {
|
||||
Some(Section::ImapAccount(ref mut builder)) => {
|
||||
builder.host(parts[1].to_string());
|
||||
}
|
||||
_ => panic!("unexpected host keyword"),
|
||||
},
|
||||
"user" => match current_section.as_mut() {
|
||||
Some(Section::ImapAccount(ref mut builder)) => {
|
||||
builder.user(parts[1].to_string());
|
||||
}
|
||||
_ => panic!("unexpected user keyword"),
|
||||
},
|
||||
"pass" => match current_section.as_mut() {
|
||||
Some(Section::ImapAccount(ref mut builder)) => {
|
||||
builder.pass(parts[1].to_string());
|
||||
}
|
||||
_ => panic!("unexpected pass keyword"),
|
||||
},
|
||||
"ssltype" => match current_section.as_mut() {
|
||||
Some(Section::ImapAccount(ref mut builder)) => {
|
||||
builder.ssltype(match parts[1].to_lowercase().as_str() {
|
||||
"none" => SslType::None,
|
||||
"starttls" => SslType::Starttls,
|
||||
"imaps" => SslType::Imaps,
|
||||
unknown => panic!("unknown ssl type '{}'", unknown),
|
||||
});
|
||||
}
|
||||
_ => panic!("unexpected ssltype keyword"),
|
||||
},
|
||||
|
||||
"imapstore" => {
|
||||
finish_section(
|
||||
&mut accounts,
|
||||
&mut stores,
|
||||
&mut channels,
|
||||
&mut current_section,
|
||||
)?;
|
||||
let mut builder = ImapStoreBuilder::default();
|
||||
builder.name(parts[1].to_string());
|
||||
current_section = Some(Section::ImapStore(builder));
|
||||
}
|
||||
"account" => match current_section.as_mut() {
|
||||
Some(Section::ImapStore(ref mut builder)) => {
|
||||
builder.account(parts[1].to_string());
|
||||
}
|
||||
_ => panic!("unexpected account keyword"),
|
||||
},
|
||||
|
||||
"maildirstore" => {
|
||||
finish_section(
|
||||
&mut accounts,
|
||||
&mut stores,
|
||||
&mut channels,
|
||||
&mut current_section,
|
||||
)?;
|
||||
let mut builder = MaildirStoreBuilder::default();
|
||||
builder.name(parts[1].to_string());
|
||||
current_section = Some(Section::MaildirStore(builder));
|
||||
}
|
||||
"path" => match current_section.as_mut() {
|
||||
Some(Section::MaildirStore(ref mut builder)) => {
|
||||
builder.path(PathBuf::from(parts[1].to_string()));
|
||||
}
|
||||
_ => panic!("unexpected path keyword"),
|
||||
},
|
||||
"inbox" => match current_section.as_mut() {
|
||||
Some(Section::MaildirStore(ref mut builder)) => {
|
||||
builder.inbox(PathBuf::from(parts[1].to_string()));
|
||||
}
|
||||
_ => panic!("unexpected inbox keyword"),
|
||||
},
|
||||
"subfolders" => match current_section.as_mut() {
|
||||
Some(Section::MaildirStore(ref mut builder)) => {
|
||||
builder.subfolders(match parts[1].to_lowercase().as_str() {
|
||||
"verbatim" => MaildirSubfolderStyle::Verbatim,
|
||||
"maildir++" => MaildirSubfolderStyle::Maildirpp,
|
||||
"legacy" => MaildirSubfolderStyle::Legacy,
|
||||
unknown => panic!("unknown subfolder style '{}'", unknown),
|
||||
});
|
||||
}
|
||||
_ => panic!("unexpected subfolders keyword"),
|
||||
},
|
||||
|
||||
"channel" => {
|
||||
finish_section(
|
||||
&mut accounts,
|
||||
&mut stores,
|
||||
&mut channels,
|
||||
&mut current_section,
|
||||
)?;
|
||||
let mut builder = ChannelBuilder::default();
|
||||
builder.name(parts[1].to_string());
|
||||
current_section = Some(Section::Channel(builder));
|
||||
}
|
||||
"far" => match current_section.as_mut() {
|
||||
Some(Section::Channel(ref mut builder)) => {
|
||||
builder.far(parts[1].trim_matches(':').to_string());
|
||||
}
|
||||
_ => panic!("unexpected far keyword"),
|
||||
},
|
||||
"near" => match current_section.as_mut() {
|
||||
Some(Section::Channel(ref mut builder)) => {
|
||||
builder.near(parts[1].trim_matches(':').to_string());
|
||||
}
|
||||
_ => panic!("unexpected near keyword"),
|
||||
},
|
||||
"sync" => match current_section.as_mut() {
|
||||
Some(Section::Channel(ref mut builder)) => {
|
||||
builder.sync(parts[1..].iter().fold(ChannelSyncOps::empty(), |a, b| {
|
||||
a | match b.to_lowercase().as_str() {
|
||||
"none" => ChannelSyncOps::empty(),
|
||||
"pull" => ChannelSyncOps::PULL,
|
||||
"push" => ChannelSyncOps::PUSH,
|
||||
"new" => ChannelSyncOps::NEW,
|
||||
"renew" => ChannelSyncOps::RENEW,
|
||||
"delete" => ChannelSyncOps::DELETE,
|
||||
"flags" => ChannelSyncOps::FLAGS,
|
||||
"all" => ChannelSyncOps::all(),
|
||||
unknown => panic!("unknown sync op '{}'", unknown),
|
||||
}
|
||||
}));
|
||||
}
|
||||
_ => panic!("unexpected near keyword"),
|
||||
},
|
||||
|
||||
unknown => panic!("unknown keyword '{}'", unknown),
|
||||
}
|
||||
}
|
||||
|
||||
finish_section(
|
||||
&mut accounts,
|
||||
&mut stores,
|
||||
&mut channels,
|
||||
&mut current_section,
|
||||
)?;
|
||||
|
||||
let config = Config {
|
||||
accounts,
|
||||
stores,
|
||||
channels,
|
||||
};
|
||||
check_config(&config)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn check_config(config: &Config) -> Result<()> {
|
||||
for store in config.stores.values() {
|
||||
if let Store::Imap(store) = store {
|
||||
ensure!(config.accounts.contains_key(&store.account));
|
||||
}
|
||||
}
|
||||
|
||||
for channel in config.channels.values() {
|
||||
ensure!(config.stores.contains_key(&channel.near));
|
||||
ensure!(config.stores.contains_key(&channel.far));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
pub accounts: HashMap<String, Account>,
|
||||
pub stores: HashMap<String, Store>,
|
||||
pub channels: HashMap<String, Channel>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Account {
|
||||
Imap(ImapAccount),
|
||||
}
|
||||
|
||||
#[derive(Derivative, Builder)]
|
||||
#[derivative(Debug)]
|
||||
pub struct ImapAccount {
|
||||
pub name: String,
|
||||
pub host: String,
|
||||
#[builder(default)]
|
||||
pub port: Option<u16>,
|
||||
#[builder(default = "20")]
|
||||
pub timeout: u32,
|
||||
pub user: String,
|
||||
pub ssltype: SslType,
|
||||
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub pass: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum SslType {
|
||||
None,
|
||||
Starttls,
|
||||
Imaps,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Store {
|
||||
Maildir(MaildirStore),
|
||||
Imap(ImapStore),
|
||||
}
|
||||
|
||||
#[derive(Debug, Builder)]
|
||||
pub struct MaildirStore {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
pub inbox: PathBuf,
|
||||
pub subfolders: MaildirSubfolderStyle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MaildirSubfolderStyle {
|
||||
Verbatim,
|
||||
Maildirpp,
|
||||
Legacy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Builder)]
|
||||
pub struct ImapStore {
|
||||
pub name: String,
|
||||
pub account: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Builder)]
|
||||
pub struct Channel {
|
||||
pub name: String,
|
||||
pub far: String,
|
||||
pub near: String,
|
||||
pub sync: ChannelSyncOps,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct ChannelSyncOps: u32 {
|
||||
const PULL = 1 << 1;
|
||||
const PUSH = 1 << 2;
|
||||
const NEW = 1 << 3;
|
||||
const RENEW = 1 << 4;
|
||||
const DELETE = 1 << 5;
|
||||
const FLAGS = 1 << 6;
|
||||
}
|
||||
}
|
11
mbsync/src/lib.rs
Normal file
11
mbsync/src/lib.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[macro_use]
|
||||
extern crate derivative;
|
||||
#[macro_use]
|
||||
extern crate derive_builder;
|
||||
|
||||
pub mod config;
|
||||
pub mod store;
|
36
mbsync/src/main.rs
Normal file
36
mbsync/src/main.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Clap;
|
||||
use mbsync::{config, store};
|
||||
|
||||
#[derive(Debug, Clap)]
|
||||
struct Opt {
|
||||
/// The path to the config file (defaults to ~/.mbsyncrc).
|
||||
#[clap(name = "config", long = "config", short = 'c')]
|
||||
config_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let opt = Opt::parse();
|
||||
println!("opts: {:?}", opt);
|
||||
|
||||
let config_path = match opt.config_path {
|
||||
Some(path) => path,
|
||||
None => PathBuf::from(env!("HOME")).join(".mbsyncrc"),
|
||||
};
|
||||
println!("config path: {:?}", config_path);
|
||||
|
||||
let config = config::read_from_file(&config_path)?;
|
||||
println!("config: {:?}", config);
|
||||
|
||||
for channel in config.channels.values() {
|
||||
println!("beginning to sync {}", channel.name);
|
||||
|
||||
let far = store::open(&config, &channel.far).await?;
|
||||
let near = store::open(&config, &channel.near).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
71
mbsync/src/store.rs
Normal file
71
mbsync/src/store.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use anyhow::Result;
|
||||
use panorama_imap::client::{auth::Login, ClientAuthenticated, ConfigBuilder};
|
||||
|
||||
use crate::config::{Account, Config, SslType, Store};
|
||||
|
||||
pub async fn open(config: &Config, store_name: impl AsRef<str>) -> Result<OpenedStore> {
|
||||
let store = config
|
||||
.stores
|
||||
.get(store_name.as_ref())
|
||||
.expect("already checked by config reader");
|
||||
|
||||
match store {
|
||||
Store::Imap(store) => {
|
||||
let account = config
|
||||
.accounts
|
||||
.get(&store.account)
|
||||
.expect("already checked by config reader");
|
||||
match account {
|
||||
Account::Imap(account) => {
|
||||
let port = account.port.unwrap_or_else(|| match account.ssltype {
|
||||
SslType::None | SslType::Starttls => 143,
|
||||
SslType::Imaps => 993,
|
||||
});
|
||||
let tls = match account.ssltype {
|
||||
SslType::None | SslType::Starttls => false,
|
||||
SslType::Imaps => true,
|
||||
};
|
||||
let mut client = ConfigBuilder::default()
|
||||
.hostname(account.host.clone())
|
||||
.port(port)
|
||||
.tls(tls)
|
||||
.open()
|
||||
.await?;
|
||||
|
||||
if let SslType::Starttls = account.ssltype {
|
||||
client = client.upgrade().await?;
|
||||
}
|
||||
|
||||
println!("connected");
|
||||
|
||||
let login = Login {
|
||||
username: account.user.clone(),
|
||||
password: account.pass.clone(),
|
||||
};
|
||||
let client = client.auth(login).await?;
|
||||
|
||||
println!("authenticated");
|
||||
Ok(OpenedStore::Imap(ImapStore { client }))
|
||||
}
|
||||
}
|
||||
}
|
||||
Store::Maildir(store) => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum OpenedStore {
|
||||
Imap(ImapStore),
|
||||
Maildir(MaildirStore),
|
||||
}
|
||||
|
||||
impl OpenedStore {
|
||||
|
||||
}
|
||||
|
||||
pub struct ImapStore {
|
||||
client: ClientAuthenticated,
|
||||
}
|
||||
|
||||
pub struct MaildirStore {}
|
|
@ -1,7 +1,8 @@
|
|||
#[allow(unused_imports)]
|
||||
use crate::{Bytes, VResult};
|
||||
|
||||
/// Defines a new parser rule that operates on [`Bytes`] and produces a [`VResult`].
|
||||
/// Defines a new parser rule that operates on [`Bytes`] and produces a
|
||||
/// [`VResult`].
|
||||
#[macro_export]
|
||||
macro_rules! rule {
|
||||
($vis:vis $name:ident : $ret:ty => $expr:expr) => {
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
|
||||
//! High-level SMTP Client
|
||||
//! ---
|
||||
|
|
|
@ -1,2 +1,5 @@
|
|||
mod client;
|
||||
#[macro_use]
|
||||
extern crate panorama_proto_common;
|
||||
|
||||
pub mod client;
|
||||
pub mod proto;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Helper functions for manipulating the wire protocol.
|
||||
|
||||
#![allow(non_snake_case, dead_code)]
|
||||
|
||||
pub mod rfc5321;
|
||||
|
|
|
@ -3,3 +3,6 @@
|
|||
//!
|
||||
//! Grammar from <https://datatracker.ietf.org/doc/html/rfc5321>
|
||||
|
||||
use panorama_proto_common::{tagi, Bytes};
|
||||
|
||||
rule!(pub ehlo : Bytes => tagi(b"EHLO"));
|
||||
|
|
Loading…
Reference in a new issue