manual refactoring
This commit is contained in:
parent
9bbfad554e
commit
05aa47d723
2 changed files with 146 additions and 131 deletions
141
src/lib.rs
141
src/lib.rs
|
|
@ -1 +1,142 @@
|
||||||
|
use std::io::{Error, Stdout};
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use crossterm::{event};
|
||||||
|
use crossterm::event::{Event, KeyCode};
|
||||||
|
use ratatui::backend::CrosstermBackend;
|
||||||
|
use ratatui::layout::{Constraint, Direction, Layout};
|
||||||
|
use ratatui::prelude::{Color, Modifier, Style};
|
||||||
|
use ratatui::Terminal;
|
||||||
|
use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph};
|
||||||
|
use crate::config::Config;
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
|
const POLL_INTERVAL: Duration = Duration::from_secs(30);
|
||||||
|
|
||||||
|
pub(crate) struct Email {
|
||||||
|
subject: String,
|
||||||
|
from: String,
|
||||||
|
date: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main(config: &Config, mut terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<(), Error>{
|
||||||
|
let mut inbox = fetch_inbox(config);
|
||||||
|
let mut last_fetch = Instant::now();
|
||||||
|
|
||||||
|
// --- Main loop ---
|
||||||
|
loop {
|
||||||
|
if last_fetch.elapsed() >= POLL_INTERVAL {
|
||||||
|
inbox = fetch_inbox(&config);
|
||||||
|
last_fetch = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.draw(|frame| {
|
||||||
|
let area = frame.area();
|
||||||
|
let layout = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Min(3), Constraint::Length(1)])
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
match &inbox {
|
||||||
|
Ok(emails) if !emails.is_empty() => {
|
||||||
|
let items: Vec<ListItem> = emails
|
||||||
|
.iter()
|
||||||
|
.map(|e| {
|
||||||
|
ListItem::new(format!("{} | {} | {}", e.date, e.from, e.subject))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let list = List::new(items)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.title(format!("Inbox ({} messages)", emails.len()))
|
||||||
|
.borders(Borders::ALL),
|
||||||
|
)
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
|
||||||
|
|
||||||
|
frame.render_widget(list, layout[0]);
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
let p = Paragraph::new("No messages in inbox.")
|
||||||
|
.block(Block::default().title("Inbox").borders(Borders::ALL))
|
||||||
|
.style(Style::default().fg(Color::Yellow));
|
||||||
|
frame.render_widget(p, layout[0]);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let p = Paragraph::new(format!("IMAP error: {}", e))
|
||||||
|
.block(Block::default().title("Inbox").borders(Borders::ALL))
|
||||||
|
.style(Style::default().fg(Color::Red));
|
||||||
|
frame.render_widget(p, layout[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = Paragraph::new(" 'q' quit | 'r' refresh")
|
||||||
|
.style(Style::default().fg(Color::DarkGray));
|
||||||
|
frame.render_widget(status, layout[1]);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// --- Input handling ---
|
||||||
|
if event::poll(Duration::from_millis(200))? {
|
||||||
|
if let Event::Key(key) = event::read()? {
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Char('q') | KeyCode::Esc => break,
|
||||||
|
KeyCode::Char('r') => {
|
||||||
|
inbox = fetch_inbox(&config);
|
||||||
|
last_fetch = Instant::now();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn fetch_inbox(config: &Config) -> Result<Vec<Email>, String> {
|
||||||
|
let imap_cfg = &config.imap;
|
||||||
|
let stream =
|
||||||
|
TcpStream::connect((&*imap_cfg.host, imap_cfg.port)).map_err(|e| e.to_string())?;
|
||||||
|
let client = imap::Client::new(stream);
|
||||||
|
let mut session = client
|
||||||
|
.login(&imap_cfg.username, &imap_cfg.password)
|
||||||
|
.map_err(|(e, _)| e.to_string())?;
|
||||||
|
|
||||||
|
session.select("INBOX").map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let mut emails = Vec::new();
|
||||||
|
|
||||||
|
let messages = session
|
||||||
|
.fetch("1:*", "BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)]")
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
for message in messages.iter() {
|
||||||
|
if let Some(body) = message.header() {
|
||||||
|
let header = String::from_utf8_lossy(body);
|
||||||
|
let mut subject = String::new();
|
||||||
|
let mut from = String::new();
|
||||||
|
let mut date = String::new();
|
||||||
|
|
||||||
|
for line in header.lines() {
|
||||||
|
if let Some(val) = line.strip_prefix("Subject: ") {
|
||||||
|
subject = val.to_string();
|
||||||
|
} else if let Some(val) = line.strip_prefix("From: ") {
|
||||||
|
from = val.to_string();
|
||||||
|
} else if let Some(val) = line.strip_prefix("Date: ") {
|
||||||
|
date = val.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emails.push(Email {
|
||||||
|
subject,
|
||||||
|
from,
|
||||||
|
date,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = session.logout();
|
||||||
|
Ok(emails)
|
||||||
|
}
|
||||||
|
|
|
||||||
134
src/main.rs
134
src/main.rs
|
|
@ -1,153 +1,27 @@
|
||||||
use std::{io, net::TcpStream, time::{Duration, Instant}};
|
use std::io;
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{self, Event, KeyCode},
|
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
|
use hello_ratatui::config::Config;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
backend::CrosstermBackend,
|
backend::CrosstermBackend,
|
||||||
layout::{Constraint, Direction, Layout},
|
|
||||||
style::{Color, Modifier, Style},
|
|
||||||
widgets::{Block, Borders, List, ListItem, Paragraph},
|
|
||||||
Terminal,
|
Terminal,
|
||||||
};
|
};
|
||||||
use hello_ratatui::config::Config;
|
|
||||||
|
|
||||||
struct Email {
|
|
||||||
subject: String,
|
|
||||||
from: String,
|
|
||||||
date: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch_inbox(config: &Config) -> Result<Vec<Email>, String> {
|
|
||||||
let imap_cfg = &config.imap;
|
|
||||||
let stream =
|
|
||||||
TcpStream::connect((&*imap_cfg.host, imap_cfg.port)).map_err(|e| e.to_string())?;
|
|
||||||
let client = imap::Client::new(stream);
|
|
||||||
let mut session = client
|
|
||||||
.login(&imap_cfg.username, &imap_cfg.password)
|
|
||||||
.map_err(|(e, _)| e.to_string())?;
|
|
||||||
|
|
||||||
session.select("INBOX").map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut emails = Vec::new();
|
|
||||||
|
|
||||||
let messages = session
|
|
||||||
.fetch("1:*", "BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)]")
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
for message in messages.iter() {
|
|
||||||
if let Some(body) = message.header() {
|
|
||||||
let header = String::from_utf8_lossy(body);
|
|
||||||
let mut subject = String::new();
|
|
||||||
let mut from = String::new();
|
|
||||||
let mut date = String::new();
|
|
||||||
|
|
||||||
for line in header.lines() {
|
|
||||||
if let Some(val) = line.strip_prefix("Subject: ") {
|
|
||||||
subject = val.to_string();
|
|
||||||
} else if let Some(val) = line.strip_prefix("From: ") {
|
|
||||||
from = val.to_string();
|
|
||||||
} else if let Some(val) = line.strip_prefix("Date: ") {
|
|
||||||
date = val.to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emails.push(Email {
|
|
||||||
subject,
|
|
||||||
from,
|
|
||||||
date,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = session.logout();
|
|
||||||
Ok(emails)
|
|
||||||
}
|
|
||||||
|
|
||||||
const POLL_INTERVAL: Duration = Duration::from_secs(30);
|
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
let config = Config::load().unwrap();
|
let config = Config::load().unwrap();
|
||||||
let mut inbox = fetch_inbox(&config);
|
|
||||||
let mut last_fetch = Instant::now();
|
|
||||||
|
|
||||||
// --- Setup terminal ---
|
// --- Setup terminal ---
|
||||||
enable_raw_mode()?;
|
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
execute!(stdout, EnterAlternateScreen)?;
|
execute!(stdout, EnterAlternateScreen)?;
|
||||||
|
enable_raw_mode()?;
|
||||||
|
|
||||||
let backend = CrosstermBackend::new(stdout);
|
let backend = CrosstermBackend::new(stdout);
|
||||||
let mut terminal = Terminal::new(backend)?;
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
// --- Main loop ---
|
hello_ratatui::main(&config, &mut terminal)?;
|
||||||
loop {
|
|
||||||
if last_fetch.elapsed() >= POLL_INTERVAL {
|
|
||||||
inbox = fetch_inbox(&config);
|
|
||||||
last_fetch = Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
terminal.draw(|frame| {
|
|
||||||
let area = frame.area();
|
|
||||||
let layout = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints([Constraint::Min(3), Constraint::Length(1)])
|
|
||||||
.split(area);
|
|
||||||
|
|
||||||
match &inbox {
|
|
||||||
Ok(emails) if !emails.is_empty() => {
|
|
||||||
let items: Vec<ListItem> = emails
|
|
||||||
.iter()
|
|
||||||
.map(|e| {
|
|
||||||
ListItem::new(format!("{} | {} | {}", e.date, e.from, e.subject))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let list = List::new(items)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.title(format!("Inbox ({} messages)", emails.len()))
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
)
|
|
||||||
.style(Style::default().fg(Color::White))
|
|
||||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
|
|
||||||
|
|
||||||
frame.render_widget(list, layout[0]);
|
|
||||||
}
|
|
||||||
Ok(_) => {
|
|
||||||
let p = Paragraph::new("No messages in inbox.")
|
|
||||||
.block(Block::default().title("Inbox").borders(Borders::ALL))
|
|
||||||
.style(Style::default().fg(Color::Yellow));
|
|
||||||
frame.render_widget(p, layout[0]);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let p = Paragraph::new(format!("IMAP error: {}", e))
|
|
||||||
.block(Block::default().title("Inbox").borders(Borders::ALL))
|
|
||||||
.style(Style::default().fg(Color::Red));
|
|
||||||
frame.render_widget(p, layout[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = Paragraph::new(" 'q' quit | 'r' refresh")
|
|
||||||
.style(Style::default().fg(Color::DarkGray));
|
|
||||||
frame.render_widget(status, layout[1]);
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// --- Input handling ---
|
|
||||||
if event::poll(Duration::from_millis(200))? {
|
|
||||||
if let Event::Key(key) = event::read()? {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Char('q') | KeyCode::Esc => break,
|
|
||||||
KeyCode::Char('r') => {
|
|
||||||
inbox = fetch_inbox(&config);
|
|
||||||
last_fetch = Instant::now();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Restore terminal ---
|
// --- Restore terminal ---
|
||||||
disable_raw_mode()?;
|
disable_raw_mode()?;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue