1use std::fs;
2use std::path::Path;
3
4use clap::Parser;
5use walkdir::WalkDir;
6
7use crate::schema::rebuild::{LOCK_CHECK, LOCK_SENTINEL};
8use crate::security::{io, state};
9
10#[derive(Parser, Clone)]
11pub struct LockArgs {
12 #[arg(long)]
13 pub hide: bool,
14}
15
16pub fn run_lock(args: LockArgs) {
17 if state::is_locked() {
18 println!("🔒 Project already locked.");
19 return;
20 }
21
22 let _ = args.hide;
25
26 let Some(pass) = io::read_password_confirmed() else {
27 return;
28 };
29
30 create_lockcheck();
31 create_sentinel(&pass);
32
33 encrypt_project(&pass);
34
35 state::write_lock();
36
37 println!("🔐 Project encrypted.");
38}
39
40fn create_lockcheck() {
41 let path = Path::new(".fur/.lockcheck");
42 fs::write(path, "FUR_LOCK_CHECK_V1").unwrap();
43}
44
45fn create_sentinel(password: &str) {
52 let chats = Path::new("chats");
53
54 if !chats.exists() {
55 return;
56 }
57
58 let _ = fs::write(chats.join(LOCK_SENTINEL), "FUR_LOCKED_V1\n");
59
60 let check = chats.join(LOCK_CHECK);
61 let _ = fs::write(&check, "FUR_LOCK_CHECK_V1");
62 io::encrypt_file(&check, password);
63}
64
65fn encrypt_project(password: &str) {
66 encrypt_dir(".fur/messages", password);
67 encrypt_dir(".fur/threads", password);
68
69 encrypt_file(".fur/index.json", password);
70 encrypt_file(".fur/.lockcheck", password);
71
72 encrypt_markdowns(password);
73}
74
75fn encrypt_dir(dir: &str, password: &str) {
76 let path = Path::new(dir);
77
78 if let Ok(entries) = fs::read_dir(path) {
79 for e in entries.flatten() {
80 let p = e.path();
81
82 if p.is_file() {
83 io::encrypt_file(&p, password);
84 }
85 }
86 }
87}
88
89fn encrypt_file(path: &str, password: &str) {
90 let p = Path::new(path);
91
92 if p.exists() {
93 io::encrypt_file(p, password);
94 }
95}
96
97fn encrypt_markdowns(password: &str) {
101 let chats = Path::new("chats");
102
103 if !chats.exists() {
104 return;
105 }
106
107 for entry in WalkDir::new(chats)
108 .follow_links(false)
109 .into_iter()
110 .filter_map(|e| e.ok())
111 {
112 let p = entry.path();
113
114 if !p.is_file() || is_sentinel(p) {
115 continue;
116 }
117
118 io::encrypt_file(p, password);
119 }
120}
121
122fn is_sentinel(path: &Path) -> bool {
123 matches!(
124 path.file_name().and_then(|f| f.to_str()),
125 Some(LOCK_SENTINEL) | Some(LOCK_CHECK)
126 )
127}