commit
bc5fbd6ba9
180 files changed
+63750
No files matched your search
@@ -0,0 +1,125 @@
|
||||
use rusqlite::{params, Connection, OptionalExtension};
|
||||
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(
|
||||
"SELECT folder_name FROM special_folders WHERE role = ?1",
|
||||
[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(
|
||||
"SELECT EXISTS(SELECT 1 FROM folders WHERE name = ?1)",
|
||||
[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(
|
||||
"INSERT INTO special_folders(role, folder_name, updated_at) VALUES(?1, ?2, datetime('now'))\n ON CONFLICT(role) DO UPDATE SET folder_name = excluded.folder_name, updated_at = datetime('now')",
|
||||
params![role, folder],
|
||||
)
|
||||
.map_err(|e| format!("Failed to set special folder {role}: {e}"))?;
|
||||
}
|
||||
None => {
|
||||
conn.execute("DELETE FROM special_folders WHERE role = ?1", [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(
|
||||
"UPDATE special_folders SET folder_name = ?1, updated_at = datetime('now') WHERE folder_name = ?2",
|
||||
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("DELETE FROM special_folders WHERE folder_name = ?1", [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());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user