1use anyhow::{Context, Result};
6use std::collections::{HashMap, HashSet};
7use std::fs::{File, OpenOptions};
8use std::io::{BufRead, BufReader, BufWriter, Write};
9use std::path::{Path, PathBuf};
10
11use crate::error::LinkError;
12use crate::link::Link;
13use crate::storage::StorageRevision;
14
15pub struct LinkStorage {
18 links: HashMap<u32, Link>,
19 names: HashMap<u32, String>,
20 name_to_id: HashMap<String, u32>,
21 next_id: u32,
22 db_path: PathBuf,
23 revision: StorageRevision,
24 trace: bool,
25}
26
27impl LinkStorage {
28 pub fn new<P: AsRef<Path>>(db_path: P, trace: bool) -> Result<Self> {
34 let db_path = db_path.as_ref().to_path_buf();
35 let exists = db_path.exists();
36 let mut storage = Self {
37 links: HashMap::new(),
38 names: HashMap::new(),
39 name_to_id: HashMap::new(),
40 next_id: 1,
41 db_path,
42 revision: StorageRevision::default(),
43 trace,
44 };
45
46 if exists {
48 storage.load()?;
49 }
50 storage.revision = StorageRevision::of(&storage.db_path)?;
51
52 Ok(storage)
53 }
54
55 pub fn database_path(&self) -> &Path {
57 &self.db_path
58 }
59
60 pub fn observed_revision(&self) -> StorageRevision {
62 self.revision
63 }
64
65 pub fn refresh_observed_revision(&mut self) -> Result<(), LinkError> {
69 self.revision = StorageRevision::of(&self.db_path)?;
70 Ok(())
71 }
72
73 pub fn reload_from_disk(&mut self) -> Result<()> {
75 self.links.clear();
76 self.names.clear();
77 self.name_to_id.clear();
78 self.next_id = 1;
79 if self.db_path.exists() {
80 self.load()?;
81 }
82 self.revision = StorageRevision::of(&self.db_path)?;
83 Ok(())
84 }
85
86 fn load(&mut self) -> Result<()> {
88 let file = File::open(&self.db_path)
89 .with_context(|| format!("Failed to open database: {}", self.db_path.display()))?;
90
91 let reader = BufReader::new(file);
92
93 for line in reader.lines() {
94 let line = line?;
95 let line = line.trim();
96
97 if line.is_empty() || line.starts_with('#') {
98 continue;
99 }
100
101 if let Some((link, name)) = self.parse_link_line(line) {
103 self.links.insert(link.index, link);
104 if link.index >= self.next_id {
105 self.next_id = link.index + 1;
106 }
107 if let Some(name) = name {
108 self.names.insert(link.index, name.clone());
109 self.name_to_id.insert(name, link.index);
110 }
111 }
112 }
113
114 if self.trace {
115 eprintln!(
116 "[TRACE] Loaded {} links from {}",
117 self.links.len(),
118 self.db_path.display()
119 );
120 }
121
122 Ok(())
123 }
124
125 fn parse_link_line(&self, line: &str) -> Option<(Link, Option<String>)> {
127 let line = line.trim_matches(|c| c == '(' || c == ')');
129 let parts: Vec<&str> = line.split_whitespace().collect();
130
131 if parts.len() >= 3 {
132 let index = parts[0].parse().ok()?;
133 let source = parts[1].parse().ok()?;
134 let target = parts[2].parse().ok()?;
135 let name = if parts.len() > 3 {
136 Some(parts[3].trim_matches('"').to_string())
137 } else {
138 None
139 };
140 return Some((Link::new(index, source, target), name));
141 }
142
143 None
144 }
145
146 pub fn save(&self) -> Result<()> {
148 let file = OpenOptions::new()
149 .write(true)
150 .create(true)
151 .truncate(true)
152 .open(&self.db_path)
153 .with_context(|| format!("Failed to create database: {}", self.db_path.display()))?;
154
155 let mut writer = BufWriter::new(file);
156
157 let mut links: Vec<_> = self.links.values().collect();
159 links.sort_by_key(|l| l.index);
160
161 for link in links {
162 if let Some(name) = self.names.get(&link.index) {
163 writeln!(
164 writer,
165 "({} {} {} \"{}\")",
166 link.index, link.source, link.target, name
167 )?;
168 } else {
169 writeln!(writer, "({} {} {})", link.index, link.source, link.target)?;
170 }
171 }
172
173 writer.flush()?;
174
175 if self.trace {
176 eprintln!(
177 "[TRACE] Saved {} links to {}",
178 self.links.len(),
179 self.db_path.display()
180 );
181 }
182
183 Ok(())
184 }
185
186 pub fn create(&mut self, source: u32, target: u32) -> u32 {
188 let id = self.next_id;
189 self.next_id += 1;
190
191 let link = Link::new(id, source, target);
192 self.links.insert(id, link);
193
194 if self.trace {
195 eprintln!("[TRACE] Created link: ({} {} {})", id, source, target);
196 }
197
198 id
199 }
200
201 pub fn ensure_created(&mut self, id: u32) -> u32 {
203 if self.links.contains_key(&id) {
204 return id;
205 }
206
207 if self.next_id > id {
208 let link = Link::new(id, 0, 0);
209 self.links.insert(id, link);
210 if self.trace {
211 eprintln!("[TRACE] Ensured link: ({} 0 0)", id);
212 }
213 return id;
214 }
215
216 while self.next_id <= id {
218 let placeholder_id = self.next_id;
219 self.next_id += 1;
220 if placeholder_id == id {
221 let link = Link::new(id, 0, 0);
222 self.links.insert(id, link);
223 if self.trace {
224 eprintln!("[TRACE] Ensured link: ({} 0 0)", id);
225 }
226 return id;
227 }
228 }
229
230 id
231 }
232
233 pub fn get(&self, id: u32) -> Option<&Link> {
235 self.links.get(&id)
236 }
237
238 pub fn exists(&self, id: u32) -> bool {
240 self.links.contains_key(&id)
241 }
242
243 pub fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link> {
245 if let Some(link) = self.links.get_mut(&id) {
246 let before = *link;
247 if self.trace {
248 eprintln!(
249 "[TRACE] Updating link {} from ({} {}) to ({} {})",
250 id, link.source, link.target, source, target
251 );
252 }
253 link.source = source;
254 link.target = target;
255 Ok(before)
256 } else {
257 Err(LinkError::not_found(id).into())
258 }
259 }
260
261 pub fn delete(&mut self, id: u32) -> Result<Link> {
263 if let Some(name) = self.names.remove(&id) {
265 self.name_to_id.remove(&name);
266 }
267
268 if let Some(link) = self.links.remove(&id) {
269 if self.trace {
270 eprintln!(
271 "[TRACE] Deleted link: ({} {} {})",
272 link.index, link.source, link.target
273 );
274 }
275 Ok(link)
276 } else {
277 Err(LinkError::not_found(id).into())
278 }
279 }
280
281 pub fn all(&self) -> Vec<&Link> {
283 self.links.values().collect()
284 }
285
286 pub fn query(
288 &self,
289 index: Option<u32>,
290 source: Option<u32>,
291 target: Option<u32>,
292 ) -> Vec<&Link> {
293 self.links
294 .values()
295 .filter(|link| {
296 (index.is_none() || index == Some(link.index))
297 && (source.is_none() || source == Some(link.source))
298 && (target.is_none() || target == Some(link.target))
299 })
300 .collect()
301 }
302
303 pub fn search(&self, source: u32, target: u32) -> Option<u32> {
305 for link in self.links.values() {
306 if link.source == source && link.target == target {
307 return Some(link.index);
308 }
309 }
310 None
311 }
312
313 pub fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
315 if let Some(id) = self.search(source, target) {
316 id
317 } else {
318 self.create(source, target)
319 }
320 }
321
322 pub fn format(&self, link: &Link) -> String {
324 let index_str = self
326 .names
327 .get(&link.index)
328 .cloned()
329 .unwrap_or_else(|| link.index.to_string());
330 let source_str = self
331 .names
332 .get(&link.source)
333 .cloned()
334 .unwrap_or_else(|| link.source.to_string());
335 let target_str = self
336 .names
337 .get(&link.target)
338 .cloned()
339 .unwrap_or_else(|| link.target.to_string());
340 format!("({} {} {})", index_str, source_str, target_str)
341 }
342
343 pub fn format_lino(&self, link: &Link) -> String {
345 format!(
346 "({}: {} {})",
347 self.format_lino_reference(link.index),
348 self.format_lino_reference(link.source),
349 self.format_lino_reference(link.target)
350 )
351 }
352
353 pub fn lino_lines(&self) -> Vec<String> {
355 let mut links: Vec<_> = self.all();
356 links.sort_by_key(|l| l.index);
357 links
358 .into_iter()
359 .map(|link| self.format_lino(link))
360 .collect()
361 }
362
363 pub fn write_lino_output<P: AsRef<Path>>(&self, path: P) -> Result<()> {
365 let path = path.as_ref();
366 let file = OpenOptions::new()
367 .write(true)
368 .create(true)
369 .truncate(true)
370 .open(path)
371 .with_context(|| format!("Failed to create LiNo output: {}", path.display()))?;
372
373 let mut writer = BufWriter::new(file);
374 for line in self.lino_lines() {
375 writeln!(writer, "{line}")?;
376 }
377 writer.flush()?;
378 Ok(())
379 }
380
381 pub fn format_structure(&self, id: u32) -> Result<String> {
383 let mut visited = HashSet::new();
384 self.format_structure_recursive(id, &mut visited)
385 }
386
387 fn format_structure_recursive(&self, id: u32, visited: &mut HashSet<u32>) -> Result<String> {
389 let link = self.get(id).ok_or(LinkError::not_found(id))?;
390 if !visited.insert(id) {
391 return Ok(self.format_lino_reference(id));
392 }
393
394 let source = if self.exists(link.source) && !visited.contains(&link.source) {
395 self.format_structure_recursive(link.source, visited)?
396 } else {
397 self.format_lino_reference(link.source)
398 };
399 let target = self.format_lino_reference(link.target);
400 let index = self.format_lino_reference(link.index);
401 visited.remove(&id);
402
403 Ok(format!("({index}: {source} {target})"))
404 }
405
406 pub fn print_all_links(&self) {
408 let mut links: Vec<_> = self.all();
409 links.sort_by_key(|l| l.index);
410 for link in links {
411 println!("{}", self.format(link));
412 }
413 }
414
415 pub fn print_change(&self, before: &Option<Link>, after: &Option<Link>) {
417 let before_text = before.map(|l| self.format(&l)).unwrap_or_default();
418 let after_text = after.map(|l| self.format(&l)).unwrap_or_default();
419 println!("({}) ({})", before_text, after_text);
420 }
421
422 pub fn get_or_create_named(&mut self, name: &str) -> u32 {
426 if let Some(&id) = self.name_to_id.get(name) {
427 id
428 } else {
429 let id = self.create(0, 0);
431 self.update(id, id, id).ok();
432 self.names.insert(id, name.to_string());
433 self.name_to_id.insert(name.to_string(), id);
434 if self.trace {
435 eprintln!("[TRACE] Created named link: {} => {}", name, id);
436 }
437 id
438 }
439 }
440
441 pub fn set_name(&mut self, id: u32, name: &str) {
443 if let Some(old_name) = self.names.remove(&id) {
445 self.name_to_id.remove(&old_name);
446 }
447 self.names.insert(id, name.to_string());
448 self.name_to_id.insert(name.to_string(), id);
449 if self.trace {
450 eprintln!("[TRACE] Set name: {} => {}", id, name);
451 }
452 }
453
454 pub fn get_name(&self, id: u32) -> Option<&String> {
456 self.names.get(&id)
457 }
458
459 pub fn get_by_name(&self, name: &str) -> Option<u32> {
461 self.name_to_id.get(name).copied()
462 }
463
464 pub fn remove_name(&mut self, id: u32) {
466 if let Some(name) = self.names.remove(&id) {
467 self.name_to_id.remove(&name);
468 if self.trace {
469 eprintln!("[TRACE] Removed name: {} => {}", id, name);
470 }
471 }
472 }
473
474 pub fn is_trace_enabled(&self) -> bool {
476 self.trace
477 }
478
479 fn format_lino_reference(&self, id: u32) -> String {
480 self.names
481 .get(&id)
482 .map(|name| escape_lino_reference(name))
483 .unwrap_or_else(|| id.to_string())
484 }
485}
486
487fn escape_lino_reference(reference: &str) -> String {
488 if reference.is_empty() || reference.trim().is_empty() {
489 return String::new();
490 }
491
492 let has_single_quote = reference.contains('\'');
493 let has_double_quote = reference.contains('"');
494 let needs_quoting = reference.contains(':')
495 || reference.contains('(')
496 || reference.contains(')')
497 || reference.contains(' ')
498 || reference.contains('\t')
499 || reference.contains('\n')
500 || reference.contains('\r')
501 || has_single_quote
502 || has_double_quote;
503
504 if has_single_quote && has_double_quote {
505 return format!("'{}'", reference.replace('\'', "\\'"));
506 }
507
508 if has_double_quote {
509 return format!("'{reference}'");
510 }
511
512 if has_single_quote {
513 return format!("\"{reference}\"");
514 }
515
516 if needs_quoting {
517 return format!("'{reference}'");
518 }
519
520 reference.to_string()
521}