use anyhow::{bail, Context, Result};
use rusqlite::{params, Connection};
use std::collections::HashSet;
use std::path::{Path, PathBuf};
pub mod brief;
pub mod folder;
pub const SCHEMA: &str = r#"
CREATE TABLE IF NOT EXISTS board_hidden (
desk_id INTEGER NOT NULL REFERENCES desks(id) ON DELETE CASCADE,
rel TEXT NOT NULL,
hidden_at INTEGER NOT NULL,
PRIMARY KEY (desk_id, rel)
);
"#;
pub const AGENT_CMD: &str = "claude";
pub fn offered(home: &Path) -> PathBuf {
home.join("Studio")
}
pub fn folder_ok(path: &str) -> Result<PathBuf> {
let p = Path::new(path.trim());
if !p.is_absolute() {
bail!("a studio folder is an absolute path");
}
let real = p
.canonicalize()
.with_context(|| format!("{} is not there", p.display()))?;
if !real.is_dir() {
bail!("{} is not a folder", p.display());
}
Ok(real)
}
pub fn make_folder(path: &str) -> Result<PathBuf> {
let p = Path::new(path.trim());
if !p.is_absolute() {
bail!("a studio folder is an absolute path");
}
if !p.exists() {
let parent = p
.parent()
.filter(|q| q.is_dir())
.with_context(|| format!("{} is not there", p.parent().unwrap_or(p).display()))?;
let name = p.file_name().map(|n| n.to_string_lossy().to_string());
if name.as_deref().is_none_or(|n| n.starts_with('.')) {
bail!("{} is not a folder name", p.display());
}
std::fs::create_dir(parent.join(name.unwrap_or_default()))
.with_context(|| format!("making {}", p.display()))?;
}
folder_ok(path)
}
pub fn stale_cmd(cmd: &str) -> bool {
let c = cmd.trim();
c.is_empty() || c == AGENT_CMD || c.starts_with("claude --add-dir ")
}
pub fn hidden(conn: &Connection, desk_id: i64) -> Result<HashSet<String>> {
let mut stmt = conn.prepare("SELECT rel FROM board_hidden WHERE desk_id = ?1")?;
let out = stmt
.query_map(params![desk_id], |r| r.get(0))?
.collect::<rusqlite::Result<_>>()?;
Ok(out)
}
pub fn set_hidden(conn: &Connection, desk_id: i64, rel: &str, hide: bool, now: i64) -> Result<()> {
if hide {
conn.execute(
"INSERT OR REPLACE INTO board_hidden(desk_id, rel, hidden_at) VALUES(?1, ?2, ?3)",
params![desk_id, rel, now],
)?;
} else {
conn.execute(
"DELETE FROM board_hidden WHERE desk_id = ?1 AND rel = ?2",
params![desk_id, rel],
)?;
}
Ok(())
}
pub fn move_folder(conn: &mut Connection, desk_id: i64, folder: &str) -> Result<bool> {
let tx = conn.transaction()?;
if !crate::desk::set_boards(&tx, desk_id, folder)? {
return Ok(false);
}
tx.execute(
"DELETE FROM board_hidden WHERE desk_id = ?1",
params![desk_id],
)?;
tx.commit()?;
Ok(true)
}
#[cfg(test)]
mod tests;