Add task count to task list panel header

Show 'X todo / Y done' in the Tasks panel title bar.

Also includes prior uncommitted work:
- Pagination in fetch_tasks (maxResults=100 + pageToken loop)
- fetch_tasks_since for incremental pull sync
- SyncStats struct with version/last_sync/last_pull/changed counts
- Periodic push (30s) and pull (5min) sync engine
- event::poll(100ms) for non-blocking UI refresh
- Ctrl+R full sync (push + pull)
- refresh_if_needed() to reload data after background sync
- Retry mechanism (MAX_SYNC_RETRIES=3) for sync queue items
- HTTP status code checks in fetch_lists/fetch_tasks/fetch_tasks_since
- Fix move_task URL to use reqwest query()
- Remove CASCADE via replace_all_lists (use insert_list instead)
- has_pending_sync() to prevent pull during pending push
This commit is contained in:
Ruben Rosario
2026-06-21 14:21:14 +01:00
parent ae9910bcbc
commit 6eee90f128
7 changed files with 394 additions and 69 deletions
+40 -15
View File
@@ -37,9 +37,14 @@ impl Db {
task_id TEXT NOT NULL,
list_id TEXT NOT NULL,
payload TEXT NOT NULL,
created_at TEXT NOT NULL
created_at TEXT NOT NULL,
retries INTEGER NOT NULL DEFAULT 0
);",
)?;
conn.execute_batch(
"ALTER TABLE sync_queue ADD COLUMN retries INTEGER NOT NULL DEFAULT 0;",
)
.ok();
Ok(Self { conn: Mutex::new(conn) })
}
@@ -201,16 +206,6 @@ impl Db {
Ok(())
}
pub fn replace_all_lists(&self, lists: &[TaskList]) -> SqlResult<()> {
let conn = self.conn.lock().unwrap();
conn.execute("DELETE FROM task_lists", [])?;
drop(conn);
for list in lists {
self.insert_list(list)?;
}
Ok(())
}
pub fn replace_all_tasks(&self, list_id: &str, tasks: &[Task]) -> SqlResult<()> {
let conn = self.conn.lock().unwrap();
conn.execute("DELETE FROM tasks WHERE list_id = ?1", params![list_id])?;
@@ -226,7 +221,27 @@ impl Db {
Ok(())
}
pub fn push_sync(&self, action: SyncAction, task_id: &str, list_id: &str, payload: &str) -> SqlResult<()> {
pub fn push_sync(
&self,
action: SyncAction,
task_id: &str,
list_id: &str,
payload: &str,
) -> SqlResult<()> {
self.push_sync_with_retry(action, task_id, list_id, payload, 0)
}
pub fn push_sync_with_retry(
&self,
action: SyncAction,
task_id: &str,
list_id: &str,
payload: &str,
retries: i32,
) -> SqlResult<()> {
if retries > MAX_SYNC_RETRIES {
return Ok(());
}
let action_str = match action {
SyncAction::Create => "Create",
SyncAction::Update => "Update",
@@ -235,24 +250,33 @@ impl Db {
};
let conn = self.conn.lock().unwrap();
conn.execute(
"INSERT INTO sync_queue (action, task_id, list_id, payload, created_at)
VALUES (?1, ?2, ?3, ?4, ?5)",
"INSERT INTO sync_queue (action, task_id, list_id, payload, created_at, retries)
VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
params![
action_str,
task_id,
list_id,
payload,
chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(),
retries,
],
)?;
Ok(())
}
pub fn has_pending_sync(&self) -> bool {
let conn = self.conn.lock().unwrap();
let count: i64 = conn
.query_row("SELECT COUNT(*) FROM sync_queue", [], |row| row.get(0))
.unwrap_or(0);
count > 0
}
pub fn drain_sync(&self) -> Vec<SyncQueueItem> {
let conn = self.conn.lock().unwrap();
let items: Vec<SyncQueueItem> = {
let mut stmt = conn
.prepare("SELECT id, action, task_id, list_id, payload, created_at FROM sync_queue ORDER BY id")
.prepare("SELECT id, action, task_id, list_id, payload, created_at, retries FROM sync_queue ORDER BY id")
.unwrap();
stmt.query_map([], |row| {
let action_str: String = row.get(1)?;
@@ -270,6 +294,7 @@ impl Db {
list_id: row.get(3)?,
payload: row.get(4)?,
created_at: row.get(5)?,
retries: row.get(6)?,
})
})
.unwrap()