deltachat/imap/
scan_folders.rs

1use std::collections::BTreeMap;
2
3use anyhow::{Context as _, Result};
4
5use super::{get_folder_meaning_by_attrs, get_folder_meaning_by_name};
6use crate::config::Config;
7use crate::imap::{session::Session, Imap};
8use crate::log::LogExt;
9use crate::tools::{self, time_elapsed};
10use crate::{context::Context, imap::FolderMeaning};
11
12impl Imap {
13    /// Returns true if folders were scanned, false if scanning was postponed.
14    pub(crate) async fn scan_folders(
15        &mut self,
16        context: &Context,
17        session: &mut Session,
18    ) -> Result<bool> {
19        // First of all, debounce to once per minute:
20        let mut last_scan = context.last_full_folder_scan.lock().await;
21        if let Some(last_scan) = *last_scan {
22            let elapsed_secs = time_elapsed(&last_scan).as_secs();
23            let debounce_secs = context
24                .get_config_u64(Config::ScanAllFoldersDebounceSecs)
25                .await?;
26
27            if elapsed_secs < debounce_secs {
28                return Ok(false);
29            }
30        }
31        info!(context, "Starting full folder scan");
32
33        let folders = session.list_folders().await?;
34        let watched_folders = get_watched_folders(context).await?;
35
36        let mut folder_configs = BTreeMap::new();
37        let mut folder_names = Vec::new();
38
39        for folder in folders {
40            let folder_meaning = get_folder_meaning_by_attrs(folder.attributes());
41            if folder_meaning == FolderMeaning::Virtual {
42                // Gmail has virtual folders that should be skipped. For example,
43                // emails appear in the inbox and under "All Mail" as soon as it is
44                // received. The code used to wrongly conclude that the email had
45                // already been moved and left it in the inbox.
46                continue;
47            }
48            folder_names.push(folder.name().to_string());
49            let folder_name_meaning = get_folder_meaning_by_name(folder.name());
50
51            if let Some(config) = folder_meaning.to_config() {
52                // Always takes precedence
53                folder_configs.insert(config, folder.name().to_string());
54            } else if let Some(config) = folder_name_meaning.to_config() {
55                // only set if none has been already set
56                folder_configs
57                    .entry(config)
58                    .or_insert_with(|| folder.name().to_string());
59            }
60
61            let folder_meaning = match folder_meaning {
62                FolderMeaning::Unknown => folder_name_meaning,
63                _ => folder_meaning,
64            };
65
66            // Don't scan folders that are watched anyway
67            if !watched_folders.contains(&folder.name().to_string())
68                && folder_meaning != FolderMeaning::Drafts
69                && folder_meaning != FolderMeaning::Trash
70            {
71                self.fetch_move_delete(context, session, folder.name(), folder_meaning)
72                    .await
73                    .context("Can't fetch new msgs in scanned folder")
74                    .log_err(context)
75                    .ok();
76            }
77        }
78
79        // Set configs for necessary folders. Or reset if the folder was deleted.
80        for conf in [
81            Config::ConfiguredSentboxFolder,
82            Config::ConfiguredTrashFolder,
83        ] {
84            let val = folder_configs.get(&conf).map(|s| s.as_str());
85            let interrupt = conf == Config::ConfiguredTrashFolder
86                && val.is_some()
87                && context.get_config(conf).await?.is_none();
88            context.set_config_internal(conf, val).await?;
89            if interrupt {
90                // `Imap::fetch_move_delete()` is possible now for other folders (NB: we are in the
91                // Inbox loop).
92                context.scheduler.interrupt_oboxes().await;
93            }
94        }
95
96        info!(context, "Found folders: {folder_names:?}.");
97        last_scan.replace(tools::Time::now());
98        Ok(true)
99    }
100}
101
102pub(crate) async fn get_watched_folder_configs(context: &Context) -> Result<Vec<Config>> {
103    let mut res = vec![Config::ConfiguredInboxFolder];
104    if context.get_config_bool(Config::SentboxWatch).await? {
105        res.push(Config::ConfiguredSentboxFolder);
106    }
107    if context.should_watch_mvbox().await? {
108        res.push(Config::ConfiguredMvboxFolder);
109    }
110    Ok(res)
111}
112
113pub(crate) async fn get_watched_folders(context: &Context) -> Result<Vec<String>> {
114    let mut res = Vec::new();
115    for folder_config in get_watched_folder_configs(context).await? {
116        if let Some(folder) = context.get_config(folder_config).await? {
117            res.push(folder);
118        }
119    }
120    Ok(res)
121}