Skip to main content

link_cli/
named_types.rs

1//! Named type decorator for Rust link storage.
2//!
3//! This mirrors the C# `NamedTypesDecorator<uint>` role: link operations and
4//! pinned type access are delegated through `PinnedTypesDecorator`, while names
5//! are stored as external references in a separate links database.
6
7use std::path::{Path, PathBuf};
8
9use anyhow::Result;
10
11use crate::link::Link;
12use crate::link_storage::{ChangeObserver, LinkStorage};
13use crate::named_links::NamedLinks;
14use crate::pinned_types::{PinnedTypesAccess, PinnedTypesDecorator};
15
16pub trait NamedTypes {
17    fn get_name(&mut self, link: u32) -> Result<Option<String>>;
18    fn set_name(&mut self, link: u32, name: &str) -> Result<u32>;
19    fn get_by_name(&mut self, name: &str) -> Result<Option<u32>>;
20    fn remove_name(&mut self, link: u32) -> Result<()>;
21}
22
23pub struct NamedTypesDecorator {
24    pinned_types_decorator: PinnedTypesDecorator,
25    names_links: LinkStorage,
26    trace: bool,
27}
28
29impl NamedTypesDecorator {
30    pub fn new<P>(database_filename: P, trace: bool) -> Result<Self>
31    where
32        P: AsRef<Path>,
33    {
34        let names_database_filename =
35            Self::make_names_database_filename(database_filename.as_ref());
36        Self::with_names_database_path(database_filename, names_database_filename, trace)
37    }
38
39    pub fn with_names_database_path<P, N>(
40        database_filename: P,
41        names_database_filename: N,
42        trace: bool,
43    ) -> Result<Self>
44    where
45        P: AsRef<Path>,
46        N: AsRef<Path>,
47    {
48        let database_path = path_to_string(database_filename.as_ref());
49        let names_database_path = path_to_string(names_database_filename.as_ref());
50        let links = LinkStorage::new(&database_path, trace)?;
51        let names_links = LinkStorage::new(&names_database_path, trace)?;
52        Ok(Self::from_link_storages_with_trace(
53            links,
54            names_links,
55            trace,
56        ))
57    }
58
59    pub fn from_link_storages(links: LinkStorage, names_links: LinkStorage) -> Self {
60        Self::from_link_storages_with_trace(links, names_links, false)
61    }
62
63    pub fn from_pinned_types_decorator(
64        pinned_types_decorator: PinnedTypesDecorator,
65        names_links: LinkStorage,
66    ) -> Self {
67        Self::from_decorators_with_trace(pinned_types_decorator, names_links, false)
68    }
69
70    pub fn make_names_database_filename<P>(database_filename: P) -> PathBuf
71    where
72        P: AsRef<Path>,
73    {
74        let path = database_filename.as_ref();
75        let filename_without_extension = path
76            .file_stem()
77            .and_then(|value| value.to_str())
78            .unwrap_or_default();
79        let names_filename = format!("{filename_without_extension}.names.links");
80
81        path.parent()
82            .filter(|parent| !parent.as_os_str().is_empty())
83            .map(|parent| parent.join(&names_filename))
84            .unwrap_or_else(|| PathBuf::from(names_filename))
85    }
86
87    pub fn links(&self) -> &LinkStorage {
88        self.pinned_types_decorator.links()
89    }
90
91    pub fn links_mut(&mut self) -> &mut LinkStorage {
92        self.pinned_types_decorator.links_mut()
93    }
94
95    pub fn pinned_types_decorator(&self) -> &PinnedTypesDecorator {
96        &self.pinned_types_decorator
97    }
98
99    pub fn pinned_types_decorator_mut(&mut self) -> &mut PinnedTypesDecorator {
100        &mut self.pinned_types_decorator
101    }
102
103    pub fn names_links(&self) -> &LinkStorage {
104        &self.names_links
105    }
106
107    pub fn names_links_mut(&mut self) -> &mut LinkStorage {
108        &mut self.names_links
109    }
110
111    pub fn into_link_storages(self) -> (LinkStorage, LinkStorage) {
112        (
113            self.pinned_types_decorator.into_link_storage(),
114            self.names_links,
115        )
116    }
117
118    pub fn save(&self) -> Result<()> {
119        self.pinned_types_decorator.save()?;
120        self.names_links.save()?;
121        Ok(())
122    }
123
124    pub fn create(&mut self, source: u32, target: u32) -> u32 {
125        self.pinned_types_decorator.create(source, target)
126    }
127
128    pub fn ensure_created(&mut self, id: u32) -> u32 {
129        self.pinned_types_decorator.ensure_created(id)
130    }
131
132    pub fn get(&self, id: u32) -> Option<&Link> {
133        self.pinned_types_decorator.get(id)
134    }
135
136    pub fn exists(&self, id: u32) -> bool {
137        self.pinned_types_decorator.exists(id)
138    }
139
140    pub fn update(&mut self, id: u32, source: u32, target: u32) -> Result<Link> {
141        self.update_observed(id, source, target, &mut |_, _| {})
142    }
143
144    /// [`Self::update`], reporting every change the decorator stack made.
145    ///
146    /// Resolving a duplicate doublet deletes links, and a deleted link must not
147    /// keep its name. This mirrors the C# `NamedTypesDecorator.Update`, which
148    /// drops the name of every link the write handler reports as deleted:
149    ///
150    /// ```csharp
151    /// if (before != null && after == null)
152    /// {
153    ///     var deletedLinkIndex = _links.GetIndex(link: before);
154    ///     RemoveName(deletedLinkIndex);
155    /// }
156    /// ```
157    pub fn update_observed(
158        &mut self,
159        id: u32,
160        source: u32,
161        target: u32,
162        observer: ChangeObserver<'_>,
163    ) -> Result<Link> {
164        let mut deleted = Vec::new();
165        let updated = self.pinned_types_decorator.update_observed(
166            id,
167            source,
168            target,
169            &mut |before, after| {
170                if after.is_null() && !before.is_null() {
171                    deleted.push(before.index);
172                }
173                observer(before, after);
174            },
175        )?;
176        self.remove_names(&deleted)?;
177        Ok(updated)
178    }
179
180    pub fn delete(&mut self, id: u32) -> Result<Link> {
181        self.delete_observed(id, &mut |_, _| {})
182    }
183
184    /// [`Self::delete`], reporting every change the decorator stack made.
185    ///
186    /// A cascading delete removes every link that still referenced `id`; each of
187    /// them loses its name too, exactly as C# `NamedTypesDecorator.Delete` does.
188    pub fn delete_observed(&mut self, id: u32, observer: ChangeObserver<'_>) -> Result<Link> {
189        let mut deleted = Vec::new();
190        let removed = self
191            .pinned_types_decorator
192            .delete_observed(id, &mut |before, after| {
193                if after.is_null() && !before.is_null() {
194                    deleted.push(before.index);
195                }
196                observer(before, after);
197            })?;
198        if !deleted.contains(&id) {
199            deleted.push(id);
200        }
201        self.remove_names(&deleted)?;
202        Ok(removed)
203    }
204
205    fn remove_names(&mut self, indexes: &[u32]) -> Result<()> {
206        for index in indexes {
207            self.remove_name(*index)?;
208        }
209        Ok(())
210    }
211
212    pub fn all(&self) -> Vec<&Link> {
213        self.pinned_types_decorator.all()
214    }
215
216    pub fn query(
217        &self,
218        index: Option<u32>,
219        source: Option<u32>,
220        target: Option<u32>,
221    ) -> Vec<&Link> {
222        self.pinned_types_decorator.query(index, source, target)
223    }
224
225    pub fn search(&self, source: u32, target: u32) -> Option<u32> {
226        self.pinned_types_decorator.search(source, target)
227    }
228
229    pub fn get_or_create(&mut self, source: u32, target: u32) -> u32 {
230        self.pinned_types_decorator.get_or_create(source, target)
231    }
232
233    fn from_link_storages_with_trace(
234        links: LinkStorage,
235        names_links: LinkStorage,
236        trace: bool,
237    ) -> Self {
238        Self::from_decorators_with_trace(
239            PinnedTypesDecorator::from_link_storage(links),
240            names_links,
241            trace,
242        )
243    }
244
245    fn from_decorators_with_trace(
246        pinned_types_decorator: PinnedTypesDecorator,
247        names_links: LinkStorage,
248        trace: bool,
249    ) -> Self {
250        Self {
251            pinned_types_decorator,
252            names_links,
253            trace,
254        }
255    }
256
257    fn with_named_links<T>(
258        &mut self,
259        action: impl FnOnce(&mut NamedLinks<'_>) -> Result<T>,
260    ) -> Result<T> {
261        let mut named_links = NamedLinks::new(&mut self.names_links)?;
262        action(&mut named_links)
263    }
264}
265
266impl NamedTypes for NamedTypesDecorator {
267    fn get_name(&mut self, link: u32) -> Result<Option<String>> {
268        if self.trace {
269            eprintln!("[TRACE] NamedTypesDecorator get_name for link {link}");
270        }
271        self.with_named_links(|named_links| named_links.get_name_by_external_reference(link))
272    }
273
274    fn set_name(&mut self, link: u32, name: &str) -> Result<u32> {
275        if self.trace {
276            eprintln!("[TRACE] NamedTypesDecorator set_name for link {link}: {name}");
277        }
278        if let Some(existing_link_with_name) = self.get_by_name(name)? {
279            if existing_link_with_name != link {
280                self.remove_name(existing_link_with_name)?;
281            }
282        }
283        self.remove_name(link)?;
284        self.with_named_links(|named_links| named_links.set_name_for_external_reference(link, name))
285    }
286
287    fn get_by_name(&mut self, name: &str) -> Result<Option<u32>> {
288        if self.trace {
289            eprintln!("[TRACE] NamedTypesDecorator get_by_name for name {name}");
290        }
291        self.with_named_links(|named_links| named_links.get_external_reference_by_name(name))
292    }
293
294    fn remove_name(&mut self, link: u32) -> Result<()> {
295        if self.trace {
296            eprintln!("[TRACE] NamedTypesDecorator remove_name for link {link}");
297        }
298        self.with_named_links(|named_links| named_links.remove_name_by_external_reference(link))
299    }
300}
301
302impl PinnedTypesAccess for NamedTypesDecorator {
303    fn pinned_types(&mut self, count: usize) -> Result<Vec<u32>> {
304        self.pinned_types_decorator.pinned_types(count)
305    }
306}
307
308fn path_to_string(path: &Path) -> String {
309    path.to_string_lossy().into_owned()
310}