This commit is contained in:
Michael Zhang 2021-09-07 06:30:11 -05:00
parent c2002a06e5
commit 6f2b7c33cf
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
10 changed files with 261 additions and 104 deletions

1
Cargo.lock generated
View file

@ -745,6 +745,7 @@ name = "panorama-mbsync"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait",
"bitflags", "bitflags",
"clap", "clap",
"derivative", "derivative",

View file

@ -11,7 +11,7 @@ test:
cargo test --all cargo test --all
watch: watch:
cargo watch -c cargo watch -c -x 'check --all'
afl-imap: afl-imap:
#!/bin/bash #!/bin/bash
@ -23,4 +23,4 @@ afl-imap:
fuzz-imap: fuzz-imap:
#!/bin/bash #!/bin/bash
cd imap cd imap
cargo +nightly fuzz run parse_response cargo +nightly fuzz run parse_response

View file

@ -17,6 +17,7 @@ default = ["rfc2177", "rfc6154"]
low-level = [] low-level = []
rfc2087 = [] # quota rfc2087 = [] # quota
rfc2177 = [] # idle rfc2177 = [] # idle
rfc4315 = [] # uidplus
rfc6154 = [] # list rfc6154 = [] # list
fuzzing = ["arbitrary", "panorama-proto-common/fuzzing"] fuzzing = ["arbitrary", "panorama-proto-common/fuzzing"]
@ -40,4 +41,4 @@ webpki-roots = "0.21.1"
panorama-proto-common = { path = "../proto-common" } panorama-proto-common = { path = "../proto-common" }
# for fuzzing # for fuzzing
arbitrary = { version = "1", optional = true, features = ["derive"] } arbitrary = { version = "1", optional = true, features = ["derive"] }

View file

@ -0,0 +1,4 @@
//! IMAP `UIDPLUS` Extension
//! ---
//!
//! Grammar from <https://datatracker.ietf.org/doc/html/rfc4315#section-4>

View file

@ -23,3 +23,4 @@ tokio = { version = "1.9.0", features = ["full"] }
log = "0.4.14" log = "0.4.14"
env_logger = "0.9.0" env_logger = "0.9.0"
futures = "0.3.16" futures = "0.3.16"
async-trait = "0.1.51"

View file

@ -248,12 +248,12 @@ pub struct Config {
pub channels: HashMap<String, Channel>, pub channels: HashMap<String, Channel>,
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Account { pub enum Account {
Imap(ImapAccount), Imap(ImapAccount),
} }
#[derive(Derivative, Builder, Serialize, Deserialize)] #[derive(Clone, Derivative, Builder, Serialize, Deserialize)]
#[derivative(Debug)] #[derivative(Debug)]
pub struct ImapAccount { pub struct ImapAccount {
pub name: String, pub name: String,
@ -282,7 +282,7 @@ pub enum Store {
Imap(ImapStore), Imap(ImapStore),
} }
#[derive(Debug, Builder, Serialize, Deserialize)] #[derive(Debug, Clone, Builder, Serialize, Deserialize)]
pub struct MaildirStore { pub struct MaildirStore {
pub name: String, pub name: String,
pub path: PathBuf, pub path: PathBuf,

View file

@ -10,6 +10,8 @@ extern crate derive_builder;
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
#[macro_use]
extern crate async_trait;
pub mod config; pub mod config;
pub mod store; pub mod store;

View file

@ -5,6 +5,7 @@ use std::path::PathBuf;
use anyhow::Result; use anyhow::Result;
use clap::Clap; use clap::Clap;
use futures::join;
use panorama_mbsync::{ use panorama_mbsync::{
config::{self, ChannelSyncOps}, config::{self, ChannelSyncOps},
store, store,
@ -40,12 +41,19 @@ async fn main() -> Result<()> {
for channel in config.channels.values() { for channel in config.channels.values() {
info!("beginning to sync {}", channel.name); info!("beginning to sync {}", channel.name);
let far = store::open(&config, &channel.far).await?; let mut far = store::create(&config, &channel.far);
let near = store::open(&config, &channel.near).await?; let mut near = store::create(&config, &channel.near);
if channel.sync.contains(ChannelSyncOps::PULL) { let (far_res, near_res) = join!(far.open(), near.open());
store::sync(&far, &near, channel.sync).await?; far_res?;
} near_res?;
// let far = store::open(&config, &channel.far).await?;
// let near = store::open(&config, &channel.near).await?;
// if channel.sync.contains(ChannelSyncOps::PULL) {
// store::sync(&far, &near, channel.sync).await?;
// }
} }
Ok(()) Ok(())

125
mbsync/src/store-old.rs Normal file
View file

@ -0,0 +1,125 @@
use std::path::PathBuf;
use anyhow::Result;
use panorama_imap::client::{auth::Login, ClientAuthenticated, ConfigBuilder};
use crate::config::{Account, ChannelSyncOps, 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");
debug!("account: {:?}", account);
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,
};
trace!("opening client...");
let mut client = ConfigBuilder::default()
.hostname(account.host.clone())
.port(port)
.tls(tls)
.open()
.await?;
trace!("opened.");
if let SslType::Starttls = account.ssltype {
trace!("upgrading client...");
client = client.upgrade().await?;
trace!("upgraded.");
}
println!("connected");
let login = Login {
username: account.user.clone(),
password: account.pass.clone(),
};
let mut client = client.auth(login).await?;
let select = client.select("INBOX").await?;
println!("select: {:?}", select);
use futures::stream::StreamExt;
use panorama_imap::proto::command::FetchItems;
let mut result = client.uid_fetch(&[8225], &[], FetchItems::All).await?;
while let Some(item) = result.next().await {
println!("epic: {:?}", item);
}
println!("authenticated");
Ok(OpenedStore::Imap(ImapStore {
name: account.name.clone(),
client,
}))
}
}
}
Store::Maildir(store) => {
let path = store.path.clone();
Ok(OpenedStore::Maildir(MaildirStore {
name: store.name.clone(),
path,
}))
}
}
}
pub enum OpenedStore {
Imap(ImapStore),
Maildir(MaildirStore),
}
pub async fn sync(from: &OpenedStore, to: &OpenedStore, flags: ChannelSyncOps) -> Result<()> {
println!("{} -> {}", from.name(), to.name());
let from_uid = from.uid();
let to_uid = to.uid();
Ok(())
}
impl OpenedStore {
fn name(&self) -> &str {
match self {
OpenedStore::Imap(store) => &store.name,
OpenedStore::Maildir(store) => &store.name,
}
}
async fn uid(&self) -> Result<u32> {
match self {
OpenedStore::Imap(store) => {
todo!()
}
OpenedStore::Maildir(store) => {
todo!()
}
}
}
}
pub struct ImapStore {
name: String,
client: ClientAuthenticated,
}
pub struct MaildirStore {
name: String,
path: PathBuf,
}

View file

@ -1,18 +1,22 @@
use std::path::PathBuf;
use anyhow::Result; use anyhow::Result;
use panorama_imap::client::{auth::Login, ClientAuthenticated, ConfigBuilder};
use crate::config::{Account, ChannelSyncOps, Config, SslType, Store}; use panorama_imap::client::{
auth::Login, ClientAuthenticated, ClientUnauthenticated, ConfigBuilder,
};
pub async fn open(config: &Config, store_name: impl AsRef<str>) -> Result<OpenedStore> { use crate::config::{
Account as AccountConfig, Config, ImapAccount as ImapAccountConfig,
MaildirStore as MaildirStoreConfig, SslType, Store as StoreConfig,
};
pub fn create(config: &Config, store_name: impl AsRef<str>) -> Box<dyn IStore> {
let store = config let store = config
.stores .stores
.get(store_name.as_ref()) .get(store_name.as_ref())
.expect("already checked by config reader"); .expect("already checked by config reader");
match store { match store {
Store::Imap(store) => { StoreConfig::Imap(store) => {
let account = config let account = config
.accounts .accounts
.get(&store.account) .get(&store.account)
@ -20,106 +24,117 @@ pub async fn open(config: &Config, store_name: impl AsRef<str>) -> Result<Opened
debug!("account: {:?}", account); debug!("account: {:?}", account);
match account { match account {
Account::Imap(account) => { AccountConfig::Imap(account) => ImapStore::new(account.clone()),
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,
};
trace!("opening client...");
let mut client = ConfigBuilder::default()
.hostname(account.host.clone())
.port(port)
.tls(tls)
.open()
.await?;
trace!("opened.");
if let SslType::Starttls = account.ssltype {
trace!("upgrading client...");
client = client.upgrade().await?;
trace!("upgraded.");
}
println!("connected");
let login = Login {
username: account.user.clone(),
password: account.pass.clone(),
};
let mut client = client.auth(login).await?;
let select = client.select("INBOX").await?;
println!("select: {:?}", select);
use futures::stream::StreamExt;
use panorama_imap::proto::command::FetchItems;
let mut result = client.uid_fetch(&[8225], &[], FetchItems::All).await?;
while let Some(item) = result.next().await {
println!("epic: {:?}", item);
}
println!("authenticated");
Ok(OpenedStore::Imap(ImapStore {
name: account.name.clone(),
client,
}))
}
} }
} }
Store::Maildir(store) => { StoreConfig::Maildir(store) => MaildirStore::new(store.clone()),
let path = store.path.clone();
Ok(OpenedStore::Maildir(MaildirStore {
name: store.name.clone(),
path,
}))
}
} }
} }
pub enum OpenedStore { #[async_trait]
Imap(ImapStore), pub trait IStore {
Maildir(MaildirStore), async fn open(&mut self) -> Result<()>;
}
pub async fn sync(from: &OpenedStore, to: &OpenedStore, flags: ChannelSyncOps) -> Result<()> { async fn get_uidnext(&self) -> Result<usize> { todo!() }
println!("{} -> {}", from.name(), to.name());
let from_uid = from.uid();
let to_uid = to.uid();
Ok(()) async fn create_mailbox(&mut self) -> Result<()> { todo!() }
}
impl OpenedStore { async fn open_mailbox(&mut self) -> Result<()> { todo!() }
fn name(&self) -> &str {
match self {
OpenedStore::Imap(store) => &store.name,
OpenedStore::Maildir(store) => &store.name,
}
}
async fn uid(&self) -> Result<u32> { async fn delete_mailbox(&mut self) -> Result<()> { todo!() }
match self {
OpenedStore::Imap(store) => { async fn prepare_load_mailbox(&mut self, opts: u32) -> Result<()> { todo!() }
todo!()
} async fn close_mailbox(&mut self) -> Result<()> { todo!() }
OpenedStore::Maildir(store) => {
todo!() async fn load_mailbox(&mut self) -> Result<()> { todo!() }
}
} async fn fetch_message(&mut self) -> Result<()> { todo!() }
}
async fn store_message(&mut self) -> Result<()> { todo!() }
async fn trash_message(&mut self) -> Result<()> { todo!() }
} }
pub struct ImapStore { pub struct ImapStore {
name: String, config: ImapAccountConfig,
client: ClientAuthenticated, client: Option<ClientAuthenticated>,
}
impl ImapStore {
pub fn new(config: ImapAccountConfig) -> Box<dyn IStore> {
Box::new(ImapStore {
config,
client: None,
})
}
}
#[async_trait]
impl IStore for ImapStore {
async fn open(&mut self) -> Result<()> {
let account = &self.config;
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,
};
trace!("opening client...");
let mut client = ConfigBuilder::default()
.hostname(account.host.clone())
.port(port)
.tls(tls)
.open()
.await?;
trace!("opened.");
if let SslType::Starttls = account.ssltype {
trace!("upgrading client...");
client = client.upgrade().await?;
trace!("upgraded.");
}
println!("connected");
let login = Login {
username: account.user.clone(),
password: account.pass.clone(),
};
let mut client = client.auth(login).await?;
let select = client.select("INBOX").await?;
println!("select: {:?}", select);
use futures::stream::StreamExt;
use panorama_imap::proto::command::FetchItems;
let mut result = client.uid_fetch(&[8225], &[], FetchItems::All).await?;
while let Some(item) = result.next().await {
println!("epic: {:?}", item);
}
println!("authenticated");
self.client = Some(client);
Ok(())
}
} }
pub struct MaildirStore { pub struct MaildirStore {
name: String, config: MaildirStoreConfig,
path: PathBuf, }
impl MaildirStore {
pub fn new(config: MaildirStoreConfig) -> Box<dyn IStore> { Box::new(MaildirStore { config }) }
}
#[async_trait]
impl IStore for MaildirStore {
async fn open(&mut self) -> Result<()> {
println!("Hellosu! {:?}", self.config);
Ok(())
}
} }