1use std::path::PathBuf;
15use std::sync::mpsc::{Receiver, RecvError, Sender, TryRecvError, channel};
16use std::sync::{Arc, Mutex};
17use std::thread::JoinHandle;
18
19use anyhow::{Result, anyhow};
20use rmut_core::config::Account;
21use rmut_core::maildir::Flags;
22use rmut_core::remote::{Progress, Remote};
23
24pub enum Job {
26 FetchBodies(Vec<PathBuf>),
28 Sync {
30 flags: Vec<(PathBuf, Flags)>,
31 deletes: Vec<PathBuf>,
32 },
33 CopyToFolder {
35 paths: Vec<PathBuf>,
36 mailbox: String,
37 },
38 Append {
41 mailbox: Option<String>,
42 flags: Flags,
43 body: Vec<u8>,
44 },
45 AppendAll {
48 mailbox: String,
49 messages: Vec<(Flags, Vec<u8>)>,
50 },
51 CheckNew,
53 Folders,
55 Unseen(Vec<String>),
57 SearchBody(String),
59 Switch(String),
61 Manage(Manage),
63}
64
65#[derive(Clone)]
68pub enum Manage {
69 Create(String),
70 Delete(String),
71 Rename(String, String),
72 Subscribe(String, bool),
73}
74
75impl Job {
76 pub fn what(&self) -> &'static str {
78 match self {
79 Job::FetchBodies(paths) => match paths.len() {
80 1 => "fetching the message",
81 _ => "fetching the messages",
82 },
83 Job::Sync { .. } => "syncing",
84 Job::CopyToFolder { .. } => "copying to the trash",
85 Job::Append { .. } | Job::AppendAll { .. } => "saving to the server",
86 Job::CheckNew => "checking for new mail",
87 Job::Folders => "listing folders",
88 Job::Manage(_) => "managing folders",
89 Job::Unseen(_) => "counting unread",
90 Job::SearchBody(_) => "searching on the server",
91 Job::Switch(_) => "opening the folder",
92 }
93 }
94}
95
96pub enum Done {
98 Nothing,
100 Arrived(usize),
102 Folders(Vec<(String, usize)>),
103 Counts(Vec<usize>),
105 Uids(Vec<u32>),
106 Folder(String),
108 Appended(Vec<Result<(), String>>),
111 Switched(Box<Facts>),
114}
115
116#[derive(Clone)]
119pub struct Facts {
120 pub spec: String,
122 pub account: Account,
123 pub mailbox: String,
124 pub cache: PathBuf,
125 pub pending_backfill: Vec<u32>,
128}
129
130impl Facts {
131 fn of(remote: &Remote) -> Facts {
132 Facts {
133 spec: remote.spec.clone(),
134 account: remote.account.clone(),
135 mailbox: remote.mailbox.clone(),
136 cache: remote.cache.clone(),
137 pending_backfill: remote.pending_backfill.clone(),
138 }
139 }
140}
141
142pub struct Imap {
145 pub facts: Facts,
146 jobs: Sender<Job>,
147 answers: Receiver<Result<Done>>,
148 thread: Option<JoinHandle<()>>,
149 progress: Arc<Mutex<Option<String>>>,
152 busy: Option<&'static str>,
155 cutoff: rmut_core::net::Cutoff,
157}
158
159impl Imap {
160 pub fn new(remote: Remote) -> Imap {
162 let facts = Facts::of(&remote);
163 let cutoff = remote.cutoff();
164 let (jobs, inbox) = channel::<Job>();
165 let (outbox, answers) = channel::<Result<Done>>();
166 let progress = Arc::new(Mutex::new(None));
167 let thread = std::thread::spawn({
168 let progress = progress.clone();
169 move || run(remote, inbox, outbox, progress)
170 });
171 Imap {
172 facts,
173 jobs,
174 answers,
175 thread: Some(thread),
176 progress,
177 busy: None,
178 cutoff,
179 }
180 }
181
182 pub fn abort(&self) {
186 if self.busy.is_some() {
187 self.cutoff.cut();
188 }
189 }
190
191 pub fn progress_sink(slot: &Arc<Mutex<Option<String>>>) -> Progress {
194 let slot = slot.clone();
195 Box::new(move |line: &str| {
196 if let Ok(mut slot) = slot.lock() {
197 *slot = Some(line.to_string());
198 }
199 })
200 }
201
202 pub fn take_progress(&mut self) -> Option<String> {
204 self.progress.lock().ok().and_then(|mut slot| slot.take())
205 }
206
207 pub fn busy(&self) -> Option<&'static str> {
209 self.busy
210 }
211
212 pub fn start(&mut self, job: Job) -> Result<()> {
214 let what = job.what();
215 self.jobs
216 .send(job)
217 .map_err(|_| anyhow!("the connection is gone"))?;
218 self.busy = Some(what);
219 Ok(())
220 }
221
222 pub fn collect(&mut self) -> Option<Result<Done>> {
224 match self.answers.try_recv() {
225 Ok(done) => {
226 self.busy = None;
227 Some(self.remember(done))
228 }
229 Err(TryRecvError::Empty) => None,
230 Err(TryRecvError::Disconnected) => {
231 self.busy = None;
232 Some(Err(anyhow!("the connection is gone")))
233 }
234 }
235 }
236
237 pub fn blocking(&mut self, job: Job) -> Result<Done> {
241 self.start(job)?;
242 self.wait()
243 }
244
245 pub fn wait(&mut self) -> Result<Done> {
249 let done = match self.answers.recv() {
250 Ok(done) => done,
251 Err(RecvError) => Err(anyhow!("the connection is gone")),
252 };
253 self.busy = None;
254 self.remember(done)
255 }
256
257 fn remember(&mut self, done: Result<Done>) -> Result<Done> {
259 if let Ok(Done::Switched(facts)) = &done {
260 self.facts = (**facts).clone();
261 }
262 done
263 }
264
265 pub fn take_backfill(&mut self) -> Vec<u32> {
268 std::mem::take(&mut self.facts.pending_backfill)
269 }
270}
271
272impl Drop for Imap {
273 fn drop(&mut self) {
274 let (jobs, _) = channel();
277 let _ = std::mem::replace(&mut self.jobs, jobs);
278 if let Some(thread) = self.thread.take() {
279 let _ = thread.join();
280 }
281 }
282}
283
284fn run(
286 mut remote: Remote,
287 jobs: Receiver<Job>,
288 answers: Sender<Result<Done>>,
289 progress: Arc<Mutex<Option<String>>>,
290) {
291 remote.set_progress(Imap::progress_sink(&progress));
292 while let Ok(job) = jobs.recv() {
293 let what = job.what();
294 let done = do_job(&mut remote, job).map_err(|err| err.context(what));
295 if let Ok(mut slot) = progress.lock() {
296 *slot = None;
297 }
298 if answers.send(done).is_err() {
299 break; }
301 }
302}
303
304fn do_job(remote: &mut Remote, job: Job) -> Result<Done> {
305 match job {
306 Job::FetchBodies(paths) => {
307 for path in &paths {
308 remote.fetch_body(path)?;
309 }
310 Ok(Done::Nothing)
311 }
312 Job::Sync { flags, deletes } => {
313 for (path, flags) in &flags {
314 remote.push_flags(path, *flags)?;
315 }
316 if !deletes.is_empty() {
317 remote.delete(&deletes)?;
318 }
319 Ok(Done::Nothing)
320 }
321 Job::CopyToFolder { paths, mailbox } => {
322 remote.copy_to_folder(&paths, &mailbox)?;
323 Ok(Done::Nothing)
324 }
325 Job::Append {
326 mailbox,
327 flags,
328 body,
329 } => {
330 let folder = match &mailbox {
331 Some(mailbox) => remote.append_to(mailbox, flags, &body)?,
332 None => remote.append_sent(&body)?,
333 };
334 Ok(Done::Folder(folder))
335 }
336 Job::AppendAll { mailbox, messages } => {
337 let mut outcomes = Vec::new();
338 for (flags, body) in &messages {
339 let outcome = remote.append_to(&mailbox, *flags, body);
340 let failed = outcome.is_err();
341 outcomes.push(outcome.map(drop).map_err(|err| format!("{err:#}")));
342 if failed {
345 break;
346 }
347 }
348 Ok(Done::Appended(outcomes))
349 }
350 Job::CheckNew => Ok(Done::Arrived(remote.check_new()?)),
351 Job::Folders => Ok(Done::Folders(remote.folders()?)),
352 Job::Unseen(folders) => Ok(Done::Counts(
353 folders.iter().map(|f| remote.unseen(f)).collect(),
354 )),
355 Job::SearchBody(term) => Ok(Done::Uids(remote.search_body(&term)?)),
356 Job::Switch(mailbox) => {
357 remote.switch(&mailbox)?;
358 Ok(Done::Switched(Box::new(Facts::of(remote))))
359 }
360 Job::Manage(action) => {
361 match action {
362 Manage::Create(name) => remote.create_folder(&name)?,
363 Manage::Delete(name) => remote.delete_folder(&name)?,
364 Manage::Rename(from, to) => remote.rename_folder(&from, &to)?,
365 Manage::Subscribe(name, on) => remote.subscribe_folder(&name, on)?,
366 }
367 Ok(Done::Nothing)
368 }
369 }
370}