diff options
Diffstat (limited to 'src/shared.rs')
| -rw-r--r-- | src/shared.rs | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/src/shared.rs b/src/shared.rs new file mode 100644 index 0000000..bbca537 --- /dev/null +++ b/src/shared.rs @@ -0,0 +1,167 @@ +use std::ffi::{OsStr, OsString}; +#[cfg(target_family="unix")] +use std::os::unix::ffi::OsStrExt; +#[cfg(target_family="windows")] +use std::os::windows::ffi::OsStrExt; +use std::sync::Mutex; +use std::path::Path; + +use rusqlite::{Connection, params}; + +// in some configurations (`main.rs`), nothing actually uses id. +#[allow(dead_code)] +pub struct Project { + pub id: u32, + pub name: String, + pub dir: OsString, + pub host: Option<String>, + pub info: Option<String>, + pub upstream: Option<String>, +} + +impl Project { + pub fn is_here(&self) -> bool { + let uname = rustix::system::uname(); + let me = uname.nodename(); + if let Some(host) = self.host.as_ref() { + host.as_bytes() == me.to_bytes() + } else { + std::path::Path::new(&self.dir).exists() + } + } +} + +pub struct DbCtx { + pub conn: Mutex<Connection>, +} + +impl DbCtx { + pub fn new<P: AsRef<Path>>(db_path: P) -> Result<Self, String> { + let conn = Connection::open(db_path) + .map_err(|e| format!("failed to open db: {e}"))?; + + Ok(Self { + conn: Mutex::new(conn) + }) + } + + pub fn init<P: AsRef<Path>>(db_path: P) -> Result<(), String> { + let conn = Connection::open(db_path) + .map_err(|e| format!("failed to open db: {e}"))?; + + conn.execute( + "CREATE TABLE projects (\ + id INTEGER PRIMARY KEY AUTOINCREMENT, \ + name TEXT UNIQUE NOT NULL, \ + dir BLOB NOT NULL, \ + host TEXT, \ + info TEXT, \ + upstream TEXT\ + );", params![]) + .map_err(|e| format!("failed to create projects table: {e}"))?; + + conn.execute( + "CREATE INDEX projname on projects(name);", params![]) + .map_err(|e| format!("failed to create index?!: {e}"))?; + + Ok(()) + } + + pub fn project_add(&self, p: Project) -> Result<(), String> { + let conn = self.conn.lock().unwrap(); + conn + .execute( + "insert into projects \ + (name, dir, host, info, upstream) \ + values (?1, ?2, ?3, ?4, ?5);",/* \ + on conflict (name) do update \ + set dir=excluded.dir \ + info=excluded.info \ + upstream=excluded.upstream;",*/ + params![p.name, p.dir.as_os_str().as_bytes(), p.host, p.info, p.upstream] + ) + .map_err(|e| format!("failed to insert project: {e}"))?; + Ok(()) + } + + pub fn project_update(&self, p: &Project) -> Result<(), String> { + let conn = self.conn.lock().unwrap(); + // TODO: don't panic on name conflict.. + conn + .execute( + "update projects \ + set name=?2, \ + dir=?3, \ + host=?4, \ + info=?5, \ + upstream=?6 \ + where id=?1", + params![p.id.to_string(), p.name, p.dir.as_os_str().as_bytes(), p.host, p.info, p.upstream] + ) + .map_err(|e| format!("failed to update project: {e}"))?; + Ok(()) + } + + pub fn project_forget(&self, name: &str) -> Result<(), String> { + let conn = self.conn.lock().unwrap(); + conn + .execute( + "delete from projects where name=?1;", + params![name] + ) + .map_err(|e| format!("failed to delete project: {e}"))?; + Ok(()) + } + + pub fn for_each_project(&self, op: impl Fn(Project)) -> Result<(), String> { + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare("select * from projects").expect("can prepare"); + let mut rows = stmt.query([]) + .map_err(|e| format!("failed to select projects: {e}"))?; + + while let Some(row) = rows.next().expect("can read row") { + let id: u32 = row.get(0).expect("typecheck"); + let dir: Box<[u8]> = row.get(2).expect("typecheck"); + let osstr: &OsStr = OsStrExt::from_bytes(&dir); + let project = Project { + id, + name: row.get(1).expect("typecheck"), + dir: osstr.to_os_string(), + host: row.get(3).expect("typecheck"), + info: row.get(4).expect("typecheck"), + upstream: row.get(5).expect("typecheck"), + }; + + op(project); + } + Ok(()) + } + + pub fn project_by_name(&self, name: &str) -> Result<Option<Project>, String> { + let conn = self.conn.lock().unwrap(); + let mut stmt = conn.prepare("select id, name, dir, host, info, upstream from projects where name=?1") + .expect("can prepare"); + let mut rows = stmt.query([name]) + .map_err(|e| format!("failed to find projects: {e}"))?; + + let project = if let Some(row) = rows.next().expect("can read row") { + let id: u32 = row.get(0).expect("typecheck"); + let dir: Box<[u8]> = row.get(2).expect("typecheck"); + let osstr: &OsStr = OsStrExt::from_bytes(&dir); + Some(Project { + id, + name: row.get(1).expect("typecheck"), + dir: osstr.to_os_string(), + host: row.get(3).expect("typecheck"), + info: row.get(4).expect("typecheck"), + upstream: row.get(5).expect("typecheck"), + }) + } else { + None + }; + + assert!(rows.next().expect("can try getting next row").is_none()); + + Ok(project) + } +} |
