feat: move over to using the "mbwatch" group for watchers

Now mbwatch will look for a group called "mbwatch" to find what channels and
mailboxes to watch. This allows the user to be move specific in what they are
watching. You don't always need to watch the "INBOX" nor do you have to watch
all of the channels you have setup in with mbsync.

The config will need to be defined in the `Channels` prop on the group. It
needs to be a comma separated of channels and mailboxes. This is an example of
the config.

```
Group mbwatch
Channels work:INBOX, work:Archive, personal:INBOX
```
This commit is contained in:
Ade Attwood 2024-06-22 07:52:08 +01:00
parent 7549d36192
commit 30d34ce171
2 changed files with 80 additions and 10 deletions

View file

@ -40,17 +40,24 @@ impl ImapStoreConfig {
} }
} }
#[derive(Debug, Default)] #[derive(Debug, Default, Clone)]
pub struct ChannelConfig { pub struct ChannelConfig {
pub name: String, pub name: String,
pub near: String, pub near: String,
pub far: String, pub far: String,
} }
#[derive(Debug, Default, Clone)]
pub struct GroupConfig {
pub name: String,
pub channels: Vec<(String, String)>,
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Config { pub struct Config {
pub channels: Vec<ChannelConfig>, pub channels: Vec<ChannelConfig>,
pub imap_stores: Vec<ImapStoreConfig>, pub imap_stores: Vec<ImapStoreConfig>,
pub groups: Vec<GroupConfig>,
} }
impl Config { impl Config {
@ -60,6 +67,20 @@ impl Config {
.find(|store| format!(":{}:", store.name) == name) .find(|store| format!(":{}:", store.name) == name)
.map(|store| store.clone()) .map(|store| store.clone())
} }
pub fn find_channel(&self, name: &str) -> Option<ChannelConfig> {
self.channels
.iter()
.find(|channel| channel.name == name)
.map(|channel| channel.clone())
}
pub fn find_group(&self, name: &str) -> Option<GroupConfig> {
self.groups
.iter()
.find(|group| group.name == name)
.map(|group| group.clone())
}
} }
enum ConfigLine { enum ConfigLine {
@ -75,6 +96,9 @@ enum ConfigLine {
Near(String), Near(String),
Far(String), Far(String),
Group(String),
Channels(String),
End, End,
} }
@ -97,6 +121,9 @@ impl TryFrom<&str> for ConfigLine {
"Near" => Ok(ConfigLine::Near(value)), "Near" => Ok(ConfigLine::Near(value)),
"Far" => Ok(ConfigLine::Far(value)), "Far" => Ok(ConfigLine::Far(value)),
"Group" => Ok(ConfigLine::Group(value)),
"Channels" => Ok(ConfigLine::Channels(value)),
_ => { _ => {
if value == "" { if value == "" {
return Ok(ConfigLine::End); return Ok(ConfigLine::End);
@ -143,7 +170,7 @@ pub fn from_file(config_path: &str) -> Config {
ConfigLine::ImapStore(name) => { ConfigLine::ImapStore(name) => {
let mut imap_store_config = ImapStoreConfig::default(); let mut imap_store_config = ImapStoreConfig::default();
imap_store_config.name = name; imap_store_config.name = name;
config.imap_stores.push(imap_store_config) config.imap_stores.push(imap_store_config);
} }
ConfigLine::Host(host) => { ConfigLine::Host(host) => {
let store = config.imap_stores.last_mut().unwrap(); let store = config.imap_stores.last_mut().unwrap();
@ -184,6 +211,27 @@ pub fn from_file(config_path: &str) -> Config {
channel.near = near; channel.near = near;
} }
ConfigLine::Group(name) => {
let mut group = GroupConfig::default();
group.name = name;
config.groups.push(group);
}
ConfigLine::Channels(channels) => {
let group = config.groups.last_mut().unwrap();
group.channels = channels
.split(',')
.map(|channel| {
let mut iter = channel.trim().split(':');
(
iter.next().unwrap_or("").to_string(),
iter.next().unwrap_or("").to_string(),
)
})
.collect();
}
ConfigLine::End => {} ConfigLine::End => {}
} }
} }

View file

@ -6,7 +6,7 @@ use std::thread;
use config::ImapStoreConfig; use config::ImapStoreConfig;
use imap::{ImapConnection, Session}; use imap::{ImapConnection, Session};
fn connect(config: &ImapStoreConfig) -> Option<Session<Box<dyn ImapConnection>>> { fn connect(config: &ImapStoreConfig, mailbox: &String) -> Option<Session<Box<dyn ImapConnection>>> {
let mut client_builder = imap::ClientBuilder::new(config.host.clone(), config.port()) let mut client_builder = imap::ClientBuilder::new(config.host.clone(), config.port())
.mode(imap::ConnectionMode::AutoTls) .mode(imap::ConnectionMode::AutoTls)
.tls_kind(imap::TlsKind::Rust); .tls_kind(imap::TlsKind::Rust);
@ -38,7 +38,7 @@ fn connect(config: &ImapStoreConfig) -> Option<Session<Box<dyn ImapConnection>>>
return None; return None;
} }
session.select("INBOX").expect("Unable to select folder"); session.select(mailbox).expect("Unable to select folder");
Some(session) Some(session)
} }
@ -56,18 +56,32 @@ fn main() {
}; };
let config = config::from_file(&format!("{home}/.mbsyncrc")); let config = config::from_file(&format!("{home}/.mbsyncrc"));
let mbwatch_group = match config.find_group("mbwatch") {
Some(group) => group,
None => panic!("Unable to find mbwatch group in your mbsync config"),
};
let mut watchers = Vec::new(); let mut watchers = Vec::new();
for channel in &config.channels { for (channel_name, mailbox) in &mbwatch_group.channels {
let channel = match config.find_channel(channel_name) {
Some(channel) => channel,
None => panic!("Unable to find channel {}", channel_name),
};
if mailbox.is_empty() {
panic!("No mailbox defined for channel {}", channel_name);
}
let imap_store = match config.find_imap_store(&channel.far) { let imap_store = match config.find_imap_store(&channel.far) {
Some(store) => store, Some(store) => store,
None => panic!("Unable to find store {}", &channel.far), None => panic!("Unable to find store {}", &channel.far),
}; };
let channel_name = channel.name.clone(); let channel_name = channel.name.clone();
let mailbox = mailbox.clone();
watchers.push(thread::spawn(move || { watchers.push(thread::spawn(move || {
let mut session = match connect(&imap_store) { let mut session = match connect(&imap_store, &mailbox) {
Some(session) => session, Some(session) => session,
None => { None => {
log::error!("Unable to connect to channel {}", channel_name); log::error!("Unable to connect to channel {}", channel_name);
@ -75,7 +89,11 @@ fn main() {
} }
}; };
log::info!("Watching for messages on channel {}", channel_name); log::info!(
"Watching for messages on channel {} in mailbox {}",
channel_name,
mailbox
);
loop { loop {
let result = session let result = session
@ -86,15 +104,19 @@ fn main() {
log::error!("Error while idling: {:?}", result); log::error!("Error while idling: {:?}", result);
thread::sleep(std::time::Duration::from_secs(10)); thread::sleep(std::time::Duration::from_secs(10));
session = connect(&imap_store).unwrap(); session = connect(&imap_store, &mailbox).unwrap();
continue; continue;
} }
log::info!("Syncing changes for {}", channel_name); log::info!(
"Syncing changes for {} in mailbox {}",
channel_name,
mailbox
);
Command::new("mbsync") Command::new("mbsync")
.args(["--all", &format!("{}:INBOX", channel_name)]) .args(["--all", &format!("{}:{}", channel_name, mailbox)])
.output() .output()
.expect("Unable to sync mail"); .expect("Unable to sync mail");
} }