//! `on access`: who can see a change or a path. //! //! `me` on a path makes it one of your own files (like .env): saved and //! carried to your other workspaces, never landed. Paths for groups and //! sharing changes are rules the host keeps. use serde_json::json; use crate::app::{App, Session}; use crate::ids; use crate::out::{Fail, Out, Report}; pub fn run(app: &App, target: &str, who: Vec, edit: bool, sealed: bool) -> Out { let mut s = app.session()?; app.save(&mut s)?; let is_change = ids::looks_like_change(target) && !app.db.changes_by_prefix(&s.project.id, target)?.is_empty(); if is_change { return crate::remote::access_change(app, &s, target, who, edit); } let path = normalize(app, &s, target); if sealed { return crate::seal::seal_folder(app, &mut s, &path, who); } let mut patterns = personal(app, &s.project.id)?; if who.is_empty() { let mut r = Report::new(); let mine = patterns.iter().any(|p| p == &path) || crate::ws::Mine::new(&crate::ws::config(&s.root), &patterns).matches(&path); if mine { r.line("access", format!("{path}: only you (one of your own files)")); r.set("who", vec!["me"]); } else { let rules = crate::remote::path_rules(app, &s.project)?; match rules.iter().find(|(p, _)| crate::path_matches(&path, p)) { Some((p, who)) => { let sealed = crate::seal::Seals::load(&app.db, &s.project.id)?.folder_of(&path).is_some(); r.line("access", format!("{path}: {} (rule on {p}){}", who.join(", "), if sealed { "; sealed: encrypted on their machines, the host can't read it" } else { "" })); r.set("who", who.clone()).set("sealed", sealed); if sealed { // Which machines have its key: compare with what on key shows on each. let machines = crate::seal::machines_of(&s.project, p)?; for m in &machines { let mut text = format!("@{}: {}", m.handle, if m.have.is_empty() { "no machine yet".to_string() } else { m.have.join(", ") }); if !m.waiting.is_empty() { text.push_str(&format!("; waiting: {}", m.waiting.join(", "))); } r.line("machines", text); } r.set("machines", machines.iter().map(|m| serde_json::json!({ "handle": m.handle, "machines": m.have, "waiting": m.waiting })).collect::>()); } } None => { r.line("access", format!("{path}: everyone with access to the project")); r.set("who", vec!["team"]); } } } r.set("path", path); return Ok(r); } if who.len() == 1 && who[0] == "me" { if patterns.contains(&path) { let mut r = Report::new(); r.line("access", format!("{path} is already only yours")); return Ok(r); } let before = patterns.clone(); patterns.push(path.clone()); set_personal(app, &s.project.id, &patterns)?; app.db.add_op(&s.ws.id, "access", &json!({ "target": path, "who": "me", "personal_before": before }))?; let mut r = Report::new(); r.line("access", format!("{path} is now only yours: saved and carried to your other workspaces, never landed")); r.set("path", path).set("who", vec!["me"]); if crate::app::index_files(&crate::ws::Index::load(&s.root)).contains_key(&r.json["path"].as_str().unwrap_or("").to_string()) { r.next("main still has it; land a change that removes it from main if others shouldn't see it"); } return Ok(r); } if !s.project.hosted() { return Err(Fail::new("bad_usage", "a project on this machine only has you; rules for other people need a hosted project") .state("nothing changed") .next("on publish to put it on a host")); } crate::remote::set_path_rule(app, &s, &path, who) } fn normalize(app: &App, s: &Session, target: &str) -> String { let abs = app.cwd.join(target); let rel = abs.strip_prefix(&s.root).ok().and_then(crate::sys::tree_path).unwrap_or_else(|| target.replace('\\', "/")); rel.trim_matches('/').to_string() } pub fn personal(app: &App, project: &str) -> anyhow::Result> { let text: Option = app .db .conn .query_row("SELECT patterns FROM personal WHERE project = ?1", [project], |r| r.get(0)) .ok(); Ok(text.and_then(|t| serde_json::from_str(&t).ok()).unwrap_or_default()) } fn set_personal(app: &App, project: &str, patterns: &[String]) -> anyhow::Result<()> { app.db.conn.execute( "INSERT OR REPLACE INTO personal (project, patterns) VALUES (?1, ?2)", rusqlite::params![project, serde_json::to_string(patterns)?], )?; Ok(()) } pub fn undo(app: &App, s: &Session, data: &serde_json::Value) -> Result<(), Fail> { if let Some(before) = data.get("personal_before") { let patterns: Vec = serde_json::from_value(before.clone()).unwrap_or_default(); set_personal(app, &s.project.id, &patterns)?; return Ok(()); } if let Some(before) = data.get("rule_before") { let path = data.get("target").and_then(|v| v.as_str()).unwrap_or(""); let who: Vec = serde_json::from_value(before.clone()).unwrap_or_default(); crate::remote::set_path_rule(app, s, path, if who.is_empty() { vec!["team".into()] } else { who })?; return Ok(()); } Err(Fail::new("failed", "that access change can't be undone here")) }