126 lines
4.1 KiB
Rust
126 lines
4.1 KiB
Rust
use crate::db::pool::{Connection, OptionalExtension, params};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
const ROLES: &[&str] = &["sent", "drafts", "trash", "junk", "archive"];
|
|
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
pub struct SpecialFolders {
|
|
pub sent: Option<String>,
|
|
pub drafts: Option<String>,
|
|
pub trash: Option<String>,
|
|
pub junk: Option<String>,
|
|
pub archive: Option<String>,
|
|
}
|
|
|
|
fn valid_role(role: &str) -> bool {
|
|
ROLES.contains(&role)
|
|
}
|
|
|
|
pub fn get_role(conn: &Connection, role: &str) -> Result<Option<String>, String> {
|
|
if !valid_role(role) {
|
|
return Err(format!("Invalid special folder role: {role}"));
|
|
}
|
|
conn.query_row(crate::dbq!(db_special_folders::Q_001), [role], |row| {
|
|
row.get(0)
|
|
})
|
|
.optional()
|
|
.map_err(|e| format!("Failed to read special folder {role}: {e}"))
|
|
}
|
|
|
|
pub fn get_all(conn: &Connection) -> Result<SpecialFolders, String> {
|
|
Ok(SpecialFolders {
|
|
sent: get_role(conn, "sent")?,
|
|
drafts: get_role(conn, "drafts")?,
|
|
trash: get_role(conn, "trash")?,
|
|
junk: get_role(conn, "junk")?,
|
|
archive: get_role(conn, "archive")?,
|
|
})
|
|
}
|
|
|
|
pub fn set_role(conn: &Connection, role: &str, folder_name: Option<&str>) -> Result<(), String> {
|
|
if !valid_role(role) {
|
|
return Err(format!("Invalid special folder role: {role}"));
|
|
}
|
|
match folder_name.map(str::trim).filter(|value| !value.is_empty()) {
|
|
Some(folder) => {
|
|
let exists: bool = conn
|
|
.query_row(crate::dbq!(db_special_folders::Q_002), [folder], |row| {
|
|
row.get(0)
|
|
})
|
|
.map_err(|e| format!("Failed to validate folder {folder}: {e}"))?;
|
|
if !exists {
|
|
return Err(format!("Folder does not exist: {folder}"));
|
|
}
|
|
conn.execute(
|
|
crate::dbq!(db_special_folders::Q_003),
|
|
params![role, folder],
|
|
)
|
|
.map_err(|e| format!("Failed to set special folder {role}: {e}"))?;
|
|
}
|
|
None => {
|
|
conn.execute(crate::dbq!(db_special_folders::Q_004), [role])
|
|
.map_err(|e| format!("Failed to clear special folder {role}: {e}"))?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn update_all(conn: &Connection, folders: &SpecialFolders) -> Result<SpecialFolders, String> {
|
|
set_role(conn, "sent", folders.sent.as_deref())?;
|
|
set_role(conn, "drafts", folders.drafts.as_deref())?;
|
|
set_role(conn, "trash", folders.trash.as_deref())?;
|
|
set_role(conn, "junk", folders.junk.as_deref())?;
|
|
set_role(conn, "archive", folders.archive.as_deref())?;
|
|
get_all(conn)
|
|
}
|
|
|
|
pub fn rename_folder_reference(
|
|
conn: &Connection,
|
|
old_name: &str,
|
|
new_name: &str,
|
|
) -> Result<(), String> {
|
|
conn.execute(
|
|
crate::dbq!(db_special_folders::Q_005),
|
|
params![new_name, old_name],
|
|
)
|
|
.map_err(|e| format!("Failed to update special folder reference: {e}"))?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn clear_folder_reference(conn: &Connection, folder_name: &str) -> Result<(), String> {
|
|
conn.execute(crate::dbq!(db_special_folders::Q_006), [folder_name])
|
|
.map_err(|e| format!("Failed to clear special folder reference: {e}"))?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::db::folders;
|
|
use crate::db::pool::open_test_db;
|
|
|
|
#[test]
|
|
fn special_folders_roundtrip() {
|
|
let conn = open_test_db();
|
|
for name in ["Sent Items", "Drafts", "Deleted", "Junk", "Archive"] {
|
|
folders::insert_folder_if_new(&conn, name, Some("/"), "").unwrap();
|
|
}
|
|
let value = SpecialFolders {
|
|
sent: Some("Sent Items".into()),
|
|
drafts: Some("Drafts".into()),
|
|
trash: Some("Deleted".into()),
|
|
junk: Some("Junk".into()),
|
|
archive: Some("Archive".into()),
|
|
};
|
|
let saved = update_all(&conn, &value).unwrap();
|
|
assert_eq!(saved.sent.as_deref(), Some("Sent Items"));
|
|
assert_eq!(saved.trash.as_deref(), Some("Deleted"));
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_unknown_folder() {
|
|
let conn = open_test_db();
|
|
assert!(set_role(&conn, "sent", Some("Missing")).is_err());
|
|
}
|
|
}
|