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 CheckNew,
47 Folders,
49 Unseen(Vec<String>),
51 SearchBody(String),
53 Switch(String),
55 Manage(Manage),
57}
58
59#[derive(Clone)]
62pub enum Manage {
63 Create(String),
64 Delete(String),
65 Rename(String, String),
66 Subscribe(String, bool),
67}
68
69impl Job {
70 pub fn what(&self) -> &'static str {
72 match self {
73 Job::FetchBodies(paths) => match paths.len() {
74 1 => "fetching the message",
75 _ => "fetching the messages",
76 },
77 Job::Sync { .. } => "syncing",
78 Job::CopyToFolder { .. } => "copying to the trash",
79 Job::Append { .. } => "saving to the server",
80 Job::CheckNew => "checking for new mail",
81 Job::Folders => "listing folders",
82 Job::Manage(_) => "managing folders",
83 Job::Unseen(_) => "counting unread",
84 Job::SearchBody(_) => "searching on the server",
85 Job::Switch(_) => "opening the folder",
86 }
87 }
88}
89
90pub enum Done {
92 Nothing,
94 Arrived(usize),
96 Folders(Vec<(String, usize)>),
97 Counts(Vec<usize>),
99 Uids(Vec<u32>),
100 Folder(String),
102 Switched(Box<Facts>),
105}
106
107#[derive(Clone)]
110pub struct Facts {
111 pub spec: String,
113 pub account: Account,
114 pub mailbox: String,
115 pub cache: PathBuf,
116 pub pending_backfill: Vec<u32>,
119}
120
121impl Facts {
122 fn of(remote: &Remote) -> Facts {
123 Facts {
124 spec: remote.spec.clone(),
125 account: remote.account.clone(),
126 mailbox: remote.mailbox.clone(),
127 cache: remote.cache.clone(),
128 pending_backfill: remote.pending_backfill.clone(),
129 }
130 }
131}
132
133pub struct Imap {
136 pub facts: Facts,
137 jobs: Sender<Job>,
138 answers: Receiver<Result<Done>>,
139 thread: Option<JoinHandle<()>>,
140 progress: Arc<Mutex<Option<String>>>,
143 busy: Option<&'static str>,
146 cutoff: rmut_core::net::Cutoff,
148}
149
150impl Imap {
151 pub fn new(remote: Remote) -> Imap {
153 let facts = Facts::of(&remote);
154 let cutoff = remote.cutoff();
155 let (jobs, inbox) = channel::<Job>();
156 let (outbox, answers) = channel::<Result<Done>>();
157 let progress = Arc::new(Mutex::new(None));
158 let thread = std::thread::spawn({
159 let progress = progress.clone();
160 move || run(remote, inbox, outbox, progress)
161 });
162 Imap {
163 facts,
164 jobs,
165 answers,
166 thread: Some(thread),
167 progress,
168 busy: None,
169 cutoff,
170 }
171 }
172
173 pub fn abort(&self) {
177 if self.busy.is_some() {
178 self.cutoff.cut();
179 }
180 }
181
182 pub fn progress_sink(slot: &Arc<Mutex<Option<String>>>) -> Progress {
185 let slot = slot.clone();
186 Box::new(move |line: &str| {
187 if let Ok(mut slot) = slot.lock() {
188 *slot = Some(line.to_string());
189 }
190 })
191 }
192
193 pub fn take_progress(&mut self) -> Option<String> {
195 self.progress.lock().ok().and_then(|mut slot| slot.take())
196 }
197
198 pub fn busy(&self) -> Option<&'static str> {
200 self.busy
201 }
202
203 pub fn start(&mut self, job: Job) -> Result<()> {
205 let what = job.what();
206 self.jobs
207 .send(job)
208 .map_err(|_| anyhow!("the connection is gone"))?;
209 self.busy = Some(what);
210 Ok(())
211 }
212
213 pub fn collect(&mut self) -> Option<Result<Done>> {
215 match self.answers.try_recv() {
216 Ok(done) => {
217 self.busy = None;
218 Some(self.remember(done))
219 }
220 Err(TryRecvError::Empty) => None,
221 Err(TryRecvError::Disconnected) => {
222 self.busy = None;
223 Some(Err(anyhow!("the connection is gone")))
224 }
225 }
226 }
227
228 pub fn blocking(&mut self, job: Job) -> Result<Done> {
232 self.start(job)?;
233 self.wait()
234 }
235
236 pub fn wait(&mut self) -> Result<Done> {
240 let done = match self.answers.recv() {
241 Ok(done) => done,
242 Err(RecvError) => Err(anyhow!("the connection is gone")),
243 };
244 self.busy = None;
245 self.remember(done)
246 }
247
248 fn remember(&mut self, done: Result<Done>) -> Result<Done> {
250 if let Ok(Done::Switched(facts)) = &done {
251 self.facts = (**facts).clone();
252 }
253 done
254 }
255
256 pub fn take_backfill(&mut self) -> Vec<u32> {
259 std::mem::take(&mut self.facts.pending_backfill)
260 }
261}
262
263impl Drop for Imap {
264 fn drop(&mut self) {
265 let (jobs, _) = channel();
268 let _ = std::mem::replace(&mut self.jobs, jobs);
269 if let Some(thread) = self.thread.take() {
270 let _ = thread.join();
271 }
272 }
273}
274
275fn run(
277 mut remote: Remote,
278 jobs: Receiver<Job>,
279 answers: Sender<Result<Done>>,
280 progress: Arc<Mutex<Option<String>>>,
281) {
282 remote.set_progress(Imap::progress_sink(&progress));
283 while let Ok(job) = jobs.recv() {
284 let what = job.what();
285 let done = do_job(&mut remote, job).map_err(|err| err.context(what));
286 if let Ok(mut slot) = progress.lock() {
287 *slot = None;
288 }
289 if answers.send(done).is_err() {
290 break; }
292 }
293}
294
295fn do_job(remote: &mut Remote, job: Job) -> Result<Done> {
296 match job {
297 Job::FetchBodies(paths) => {
298 for path in &paths {
299 remote.fetch_body(path)?;
300 }
301 Ok(Done::Nothing)
302 }
303 Job::Sync { flags, deletes } => {
304 for (path, flags) in &flags {
305 remote.push_flags(path, *flags)?;
306 }
307 if !deletes.is_empty() {
308 remote.delete(&deletes)?;
309 }
310 Ok(Done::Nothing)
311 }
312 Job::CopyToFolder { paths, mailbox } => {
313 remote.copy_to_folder(&paths, &mailbox)?;
314 Ok(Done::Nothing)
315 }
316 Job::Append {
317 mailbox,
318 flags,
319 body,
320 } => {
321 let folder = match &mailbox {
322 Some(mailbox) => remote.append_to(mailbox, flags, &body)?,
323 None => remote.append_sent(&body)?,
324 };
325 Ok(Done::Folder(folder))
326 }
327 Job::CheckNew => Ok(Done::Arrived(remote.check_new()?)),
328 Job::Folders => Ok(Done::Folders(remote.folders()?)),
329 Job::Unseen(folders) => Ok(Done::Counts(
330 folders.iter().map(|f| remote.unseen(f)).collect(),
331 )),
332 Job::SearchBody(term) => Ok(Done::Uids(remote.search_body(&term)?)),
333 Job::Switch(mailbox) => {
334 remote.switch(&mailbox)?;
335 Ok(Done::Switched(Box::new(Facts::of(remote))))
336 }
337 Job::Manage(action) => {
338 match action {
339 Manage::Create(name) => remote.create_folder(&name)?,
340 Manage::Delete(name) => remote.delete_folder(&name)?,
341 Manage::Rename(from, to) => remote.rename_folder(&from, &to)?,
342 Manage::Subscribe(name, on) => remote.subscribe_folder(&name, on)?,
343 }
344 Ok(Done::Nothing)
345 }
346 }
347}