1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
//! Module dedicated to email folders synchronization cache.
//!
//! This module contains everything needed to manipule folder
//! synchronization cache entities using SQLite.

pub use rusqlite::Error;
use rusqlite::{Connection, Transaction};
use std::collections::HashSet;

use crate::Result;

use super::{FolderSyncStrategy, FoldersName};

const CREATE_FOLDERS_TABLE: &str = "
    CREATE TABLE IF NOT EXISTS folders (
        account TEXT NOT NULL,
        name    TEXT NOT NULL,
        UNIQUE(name, account)
    )
";

const INSERT_FOLDER: &str = "
    INSERT INTO folders
    VALUES (?, ?)
";

const DELETE_FOLDER: &str = "
    DELETE FROM folders
    WHERE account = ?
    AND name = ?
";

const SELECT_ALL_FOLDERS: &str = "
    SELECT name
    FROM folders
    WHERE account = ?
";

const SELECT_FOLDERS_IN: &str = "
    SELECT name
    FROM folders
    WHERE account = ?
    AND name IN (!)
";

const SELECT_FOLDERS_NOT_IN: &str = "
    SELECT name
    FROM folders
    WHERE account = ?
    AND name NOT IN (!)
";

/// The folder synchronization cache.
///
/// This structure contains all functions needed to manipule SQLite
/// cache.
pub struct FolderSyncCache;

impl FolderSyncCache {
    const LOCAL_SUFFIX: &str = ":cache";

    /// Creates the folder synchronization SQLite table.
    pub fn init(conn: &mut Connection) -> Result<()> {
        conn.execute(CREATE_FOLDERS_TABLE, ())?;
        Ok(())
    }

    fn list_all_folders(conn: &mut Connection, account: impl AsRef<str>) -> Result<FoldersName> {
        let mut stmt = conn.prepare(SELECT_ALL_FOLDERS)?;
        let folders: Vec<String> = stmt
            .query_map([account.as_ref()], |row| row.get(0))?
            .collect::<rusqlite::Result<_>>()?;

        Ok(FoldersName::from_iter(folders))
    }

    fn list_folders_with(
        conn: &mut Connection,
        account: impl AsRef<str>,
        folders: &HashSet<String>,
        query: &str,
    ) -> Result<FoldersName> {
        let folders = folders
            .iter()
            .map(|f| format!("{f:#?}"))
            .collect::<Vec<_>>()
            .join(", ");

        let mut stmt = conn.prepare(&query.replace("!", &folders))?;

        let folders: Vec<String> = stmt
            .query_map([account.as_ref()], |row| row.get(0))?
            .collect::<rusqlite::Result<_>>()?;

        Ok(FoldersName::from_iter(folders))
    }

    pub fn list_local_folders(
        conn: &mut Connection,
        account: impl ToString,
        strategy: &FolderSyncStrategy,
    ) -> Result<FoldersName> {
        match strategy {
            FolderSyncStrategy::All => {
                Self::list_all_folders(conn, account.to_string() + Self::LOCAL_SUFFIX)
            }
            FolderSyncStrategy::Include(folders) => Self::list_folders_with(
                conn,
                account.to_string() + Self::LOCAL_SUFFIX,
                folders,
                SELECT_FOLDERS_IN,
            ),
            FolderSyncStrategy::Exclude(folders) => Self::list_folders_with(
                conn,
                account.to_string() + Self::LOCAL_SUFFIX,
                folders,
                SELECT_FOLDERS_NOT_IN,
            ),
        }
    }

    pub fn list_remote_folders(
        conn: &mut Connection,
        account: impl AsRef<str>,
        strategy: &FolderSyncStrategy,
    ) -> Result<FoldersName> {
        match strategy {
            FolderSyncStrategy::All => Self::list_all_folders(conn, account),
            FolderSyncStrategy::Include(folders) => {
                Self::list_folders_with(conn, account, folders, SELECT_FOLDERS_IN)
            }
            FolderSyncStrategy::Exclude(folders) => {
                Self::list_folders_with(conn, account, folders, SELECT_FOLDERS_NOT_IN)
            }
        }
    }

    fn insert_folder(
        tx: &Transaction,
        account: impl AsRef<str>,
        folder: impl AsRef<str>,
    ) -> Result<()> {
        tx.execute(INSERT_FOLDER, [account.as_ref(), folder.as_ref()])?;
        Ok(())
    }

    pub fn insert_local_folder(
        tx: &Transaction,
        account: impl ToString,
        folder: impl AsRef<str>,
    ) -> Result<()> {
        Self::insert_folder(tx, account.to_string() + Self::LOCAL_SUFFIX, folder)
    }

    pub fn insert_remote_folder(
        tx: &Transaction,
        account: impl AsRef<str>,
        folder: impl AsRef<str>,
    ) -> Result<()> {
        Self::insert_folder(tx, account, folder)
    }

    fn delete_folder(
        tx: &Transaction,
        account: impl AsRef<str>,
        folder: impl AsRef<str>,
    ) -> Result<()> {
        tx.execute(DELETE_FOLDER, [account.as_ref(), folder.as_ref()])?;
        Ok(())
    }

    pub fn delete_local_folder(
        tx: &Transaction,
        account: impl ToString,
        folder: impl AsRef<str>,
    ) -> Result<()> {
        Self::delete_folder(tx, account.to_string() + Self::LOCAL_SUFFIX, folder)
    }

    pub fn delete_remote_folder(
        tx: &Transaction,
        account: impl AsRef<str>,
        folder: impl AsRef<str>,
    ) -> Result<()> {
        Self::delete_folder(tx, account, folder)
    }
}