//! Unified mount/location presentation rows. //! //! A device or attached row stores only its `sync_folder` identity. Its path, remote root, //! mode, state, pending mode, and last-sync timestamp are selected from //! `sync_folder`, which remains the authoritative sync-engine table. use rusqlite::params; use super::Db; use crate::mounts::{MountAccess, MountKind, MountMode, MountSpec}; use crate::{Error, Result}; impl Db { /// Project the configured primary mount into the presentation table. /// /// `root_share_id` is optional because an offline startup may only have the /// cached root node. A previously learned share id is retained only when it /// belongs to the same root uid. pub fn mount_upsert_my_files( &self, local_path: &str, root_uid: &str, root_share_id: Option<&str>, ) -> Result { let mut conn = self.conn.lock(); let tx = conn.transaction()?; let updated = tx.execute( "UPDATE mount SET local_path = ?1, root_share_id = CASE WHEN root_uid = ?2 THEN COALESCE(NULLIF(?3, ''), root_share_id) ELSE COALESCE(?3, '') END, root_uid = ?2, mode = 'ondemand', access = 'rw' WHERE kind = 'myfiles'", params![local_path, root_uid, root_share_id], )?; let id = if updated == 0 { tx.execute( "INSERT INTO mount (kind, local_path, root_uid, root_share_id, mode, access) VALUES ('myfiles', ?1, ?2, COALESCE(?3, ''), 'ondemand', 'rw')", params![local_path, root_uid, root_share_id], )?; tx.last_insert_rowid() } else { tx.query_row("SELECT id FROM mount WHERE kind = 'myfiles'", [], |row| { row.get(0) })? }; tx.commit()?; Ok(id) } /// Store a share id learned asynchronously, but only if the projection still /// names the root it was resolved for. pub fn mount_repair_my_files_share_id( &self, root_uid: &str, root_share_id: &str, ) -> Result { let conn = self.conn.lock(); Ok(conn.execute( "UPDATE mount SET root_share_id = ?2 WHERE kind = 'myfiles' AND root_uid = ?1", params![root_uid, root_share_id], )? > 0) } /// Every local location, with synced-folder fields joined from `sync_folder`. pub fn mount_list(&self) -> Result> { let conn = self.read(); let mut stmt = conn.prepare( "SELECT m.id, m.kind, m.sync_folder_id, m.share_root_uid, CASE WHEN m.kind IN ('device', 'attached') THEN sf.local_path ELSE m.local_path END, CASE WHEN m.kind IN ('device', 'attached') THEN sf.remote_uid ELSE m.root_uid END, CASE WHEN m.kind IN ('device', 'attached') THEN sf.remote_share_id ELSE m.root_share_id END, CASE WHEN m.kind IN ('device', 'attached') THEN sf.mode ELSE m.mode END, m.access, CASE WHEN m.kind IN ('device', 'attached') THEN sf.state ELSE 'idle' END, CASE WHEN m.kind IN ('device', 'attached') THEN sf.last_sync ELSE 0 END, CASE WHEN m.kind IN ('device', 'attached') THEN sf.pending_mode ELSE NULL END FROM mount AS m LEFT JOIN sync_folder AS sf ON sf.id = m.sync_folder_id ORDER BY CASE m.kind WHEN 'myfiles' THEN 0 WHEN 'device' THEN 1 ELSE 2 END, m.id", )?; let rows = stmt.query_map([], |row| { Ok(( row.get::<_, i64>(0)?, row.get::<_, String>(1)?, row.get::<_, Option>(2)?, row.get::<_, Option>(3)?, row.get::<_, String>(4)?, row.get::<_, String>(5)?, row.get::<_, String>(6)?, row.get::<_, String>(7)?, row.get::<_, String>(8)?, row.get::<_, String>(9)?, row.get::<_, i64>(10)?, row.get::<_, Option>(11)?, )) })?; let mut mounts = Vec::new(); for row in rows { let ( id, kind, sync_folder_id, share_root_uid, local_path, root_uid, root_share_id, mode, access, state, last_sync, pending_mode, ) = row?; let kind = match kind.as_str() { "myfiles" => MountKind::MyFiles, "device" => MountKind::Device { sync_folder_id: sync_folder_id.ok_or_else(|| { Error::Other(format!("mount row {id} has no sync_folder_id")) })?, }, "attached" => MountKind::Attached { sync_folder_id: sync_folder_id.ok_or_else(|| { Error::Other(format!("mount row {id} has no sync_folder_id")) })?, }, "shared" => MountKind::Shared { share_root_uid: share_root_uid.ok_or_else(|| { Error::Other(format!("mount row {id} has no share_root_uid")) })?, }, other => { return Err(Error::Other(format!( "mount row {id} has unknown kind {other:?}" ))); } }; let access = match access.as_str() { "rw" => MountAccess::Rw, "ro" => MountAccess::Ro, other => { return Err(Error::Other(format!( "mount row {id} has unknown access {other:?}" ))); } }; mounts.push(MountSpec { id, kind, local_path, root_uid, root_share_id, mode: MountMode::from(mode.as_str()), access, state, last_sync, pending_mode: pending_mode.as_deref().map(MountMode::from), mounted: false, progress: None, paused: false, }); } Ok(mounts) } }