aboutsummaryrefslogtreecommitdiff
path: root/src/shared.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared.rs')
-rw-r--r--src/shared.rs167
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)
+ }
+}