Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 71 additions & 44 deletions src-tauri/src/commands/boards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,57 +163,19 @@ pub(crate) fn set_boards_index(
let mut conn = open_db(&app)?;
let tx = conn.transaction().map_err(|error| error.to_string())?;

tx.execute("DELETE FROM index_items", [])
.map_err(|error| error.to_string())?;
tx.execute("DELETE FROM folder_items", [])
.map_err(|error| error.to_string())?;
tx.execute("DELETE FROM folders", [])
.map_err(|error| error.to_string())?;
clear_index_tables(&tx)?;

for (position, item) in items.iter().enumerate() {
match item {
BoardListItem::Board(board) => {
tx.execute(
"INSERT INTO index_items (position, item_type, item_id) VALUES (?1, 'board', ?2)",
params![position as i64, board.id],
)
.map_err(|error| error.to_string())?;
}
BoardListItem::Folder(folder) => {
tx.execute(
"INSERT OR REPLACE INTO folders (id, name) VALUES (?1, ?2)",
params![folder.id, folder.name],
)
.map_err(|error| error.to_string())?;
tx.execute(
"INSERT INTO index_items (position, item_type, item_id) VALUES (?1, 'folder', ?2)",
params![position as i64, folder.id],
)
.map_err(|error| error.to_string())?;
for (folder_pos, board) in folder.items.iter().enumerate() {
tx.execute(
"INSERT INTO folder_items (folder_id, board_id, position) VALUES (?1, ?2, ?3)",
params![folder.id, board.id, folder_pos as i64],
)
.map_err(|error| error.to_string())?;
}
}
}
persist_index_item(&tx, position as i64, item)?;
}

let mut index = BoardsIndex {
let active_board_id = get_setting(&tx, ACTIVE_BOARD_SETTING_KEY)?;
let normalized_active_board_id = resolve_active_board_id(&items, active_board_id);
let index = BoardsIndex {
items,
active_board_id: get_setting(&tx, ACTIVE_BOARD_SETTING_KEY)?,
active_board_id: normalized_active_board_id,
};

if let Some(active_id) = index.active_board_id.clone() {
if !board_exists(&index.items, &active_id) {
index.active_board_id = first_board_id(&index.items);
}
} else {
index.active_board_id = first_board_id(&index.items);
}

set_setting(
&tx,
ACTIVE_BOARD_SETTING_KEY,
Expand All @@ -223,6 +185,71 @@ pub(crate) fn set_boards_index(
Ok(index)
}

fn clear_index_tables(tx: &rusqlite::Transaction<'_>) -> Result<(), String> {
tx.execute("DELETE FROM index_items", [])
.map_err(|error| error.to_string())?;
tx.execute("DELETE FROM folder_items", [])
.map_err(|error| error.to_string())?;
tx.execute("DELETE FROM folders", [])
.map_err(|error| error.to_string())?;
Ok(())
}

fn persist_index_item(
tx: &rusqlite::Transaction<'_>,
position: i64,
item: &BoardListItem,
) -> Result<(), String> {
match item {
BoardListItem::Board(board) => {
tx.execute(
"INSERT INTO index_items (position, item_type, item_id) VALUES (?1, 'board', ?2)",
params![position, &board.id],
)
.map_err(|error| error.to_string())?;
Ok(())
}
BoardListItem::Folder(folder) => persist_folder_item(tx, position, folder),
}
}

fn persist_folder_item(
tx: &rusqlite::Transaction<'_>,
position: i64,
folder: &crate::models::BoardFolder,
) -> Result<(), String> {
tx.execute(
"INSERT OR REPLACE INTO folders (id, name) VALUES (?1, ?2)",
params![&folder.id, &folder.name],
)
.map_err(|error| error.to_string())?;
tx.execute(
"INSERT INTO index_items (position, item_type, item_id) VALUES (?1, 'folder', ?2)",
params![position, &folder.id],
)
.map_err(|error| error.to_string())?;

for (folder_position, board) in folder.items.iter().enumerate() {
tx.execute(
"INSERT INTO folder_items (folder_id, board_id, position) VALUES (?1, ?2, ?3)",
params![&folder.id, &board.id, folder_position as i64],
)
.map_err(|error| error.to_string())?;
}

Ok(())
}

fn resolve_active_board_id(
items: &[BoardListItem],
active_board_id: Option<String>,
) -> Option<String> {
match active_board_id {
Some(active_id) if board_exists(items, &active_id) => Some(active_id),
_ => first_board_id(items),
}
}

fn insert_board_with_data(
tx: &rusqlite::Transaction<'_>,
board: &Board,
Expand Down
130 changes: 91 additions & 39 deletions src-tauri/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,27 +177,34 @@ pub(crate) fn first_board_id_from_db(conn: &Connection) -> Result<Option<String>
while let Some(row) = rows.next().map_err(|e| e.to_string())? {
let item_type: String = row.get(0).map_err(|e| e.to_string())?;
let item_id: String = row.get(1).map_err(|e| e.to_string())?;
if item_type == "board" {
return Ok(Some(item_id));
}
if item_type == "folder" {
let board_id: Option<String> = conn
.query_row(
"SELECT board_id FROM folder_items WHERE folder_id = ?1 ORDER BY position ASC LIMIT 1",
params![item_id],
|row| row.get(0),
)
.optional()
.map_err(|e| e.to_string())?;
if board_id.is_some() {
return Ok(board_id);
}
let board_id = board_id_from_index_item(conn, &item_type, &item_id)?;
if board_id.is_some() {
return Ok(board_id);
}
}

Ok(None)
}

fn board_id_from_index_item(
conn: &Connection,
item_type: &str,
item_id: &str,
) -> Result<Option<String>, String> {
match item_type {
"board" => Ok(Some(item_id.to_string())),
"folder" => conn
.query_row(
"SELECT board_id FROM folder_items WHERE folder_id = ?1 ORDER BY position ASC LIMIT 1",
params![item_id],
|row| row.get(0),
)
.optional()
.map_err(|e| e.to_string()),
_ => Ok(None),
}
}

fn load_folder_boards(
conn: &Connection,
folder_id: &str,
Expand All @@ -220,6 +227,18 @@ fn load_folder_boards(
}

pub(crate) fn load_boards_index_from_db(conn: &Connection) -> Result<BoardsIndex, String> {
let boards = load_boards_map(conn)?;
let folder_names = load_folder_names_map(conn)?;
let items = load_index_items(conn, &boards, &folder_names)?;
let active_board_id = get_setting(conn, "active_board_id")?;

Ok(BoardsIndex {
items,
active_board_id,
})
}

fn load_boards_map(conn: &Connection) -> Result<HashMap<String, Board>, String> {
let mut boards = HashMap::new();
let mut stmt = conn
.prepare(
Expand All @@ -242,6 +261,10 @@ pub(crate) fn load_boards_index_from_db(conn: &Connection) -> Result<BoardsIndex
boards.insert(board.id.clone(), board);
}

Ok(boards)
}

fn load_folder_names_map(conn: &Connection) -> Result<HashMap<String, String>, String> {
let mut folder_names = HashMap::new();
let mut stmt = conn
.prepare("SELECT id, name FROM folders")
Expand All @@ -253,41 +276,70 @@ pub(crate) fn load_boards_index_from_db(conn: &Connection) -> Result<BoardsIndex
folder_names.insert(id, name);
}

Ok(folder_names)
}

fn load_index_items(
conn: &Connection,
boards: &HashMap<String, Board>,
folder_names: &HashMap<String, String>,
) -> Result<Vec<BoardListItem>, String> {
let mut items = Vec::new();
let mut stmt = conn
.prepare("SELECT item_type, item_id FROM index_items ORDER BY position ASC")
.map_err(|e| e.to_string())?;
let mut rows = stmt.query([]).map_err(|e| e.to_string())?;

while let Some(row) = rows.next().map_err(|e| e.to_string())? {
let item_type: String = row.get(0).map_err(|e| e.to_string())?;
let item_id: String = row.get(1).map_err(|e| e.to_string())?;
match item_type.as_str() {
"board" => {
if let Some(board) = boards.get(&item_id) {
items.push(BoardListItem::Board(board.clone()));
}
}
"folder" => {
if let Some(name) = folder_names.get(&item_id) {
let folder_items = load_folder_boards(conn, &item_id, &boards)?;
if !folder_items.is_empty() {
items.push(BoardListItem::Folder(BoardFolder {
id: item_id,
name: name.clone(),
items: folder_items,
}));
}
}
}
_ => {}

if let Some(item) =
board_list_item_from_index_row(conn, &item_type, &item_id, boards, folder_names)?
{
items.push(item);
}
}

let active_board_id = get_setting(conn, "active_board_id")?;
Ok(BoardsIndex {
items,
active_board_id,
})
Ok(items)
}

fn board_list_item_from_index_row(
conn: &Connection,
item_type: &str,
item_id: &str,
boards: &HashMap<String, Board>,
folder_names: &HashMap<String, String>,
) -> Result<Option<BoardListItem>, String> {
match item_type {
"board" => Ok(boards
.get(item_id)
.map(|board| BoardListItem::Board(board.clone()))),
"folder" => folder_item_from_index_row(conn, item_id, boards, folder_names),
_ => Ok(None),
}
}

fn folder_item_from_index_row(
conn: &Connection,
folder_id: &str,
boards: &HashMap<String, Board>,
folder_names: &HashMap<String, String>,
) -> Result<Option<BoardListItem>, String> {
let Some(name) = folder_names.get(folder_id) else {
return Ok(None);
};

let folder_items = load_folder_boards(conn, folder_id, boards)?;
if folder_items.is_empty() {
return Ok(None);
}

Ok(Some(BoardListItem::Folder(BoardFolder {
id: folder_id.to_string(),
name: name.clone(),
items: folder_items,
})))
}

pub(crate) fn normalize_active_board_id(
Expand Down
Loading
Loading