1pub(crate) mod attachment;
2pub(crate) mod autotype;
3pub(crate) mod color;
4pub(crate) mod custom_data;
5pub(crate) mod entry;
6pub(crate) mod group;
7pub(crate) mod history;
8pub(crate) mod icon;
9pub(crate) mod iconid;
10pub(crate) mod meta;
11pub(crate) mod node;
12pub(crate) mod times;
13pub(crate) mod value;
14
15pub use attachment::Attachment;
16pub use autotype::{AutoType, AutoTypeAssociation, DataTransferObfuscation};
17pub use color::{Color, ParseColorError};
18pub use custom_data::{CustomDataItem, CustomDataValue};
19pub use entry::Entry;
20pub use group::Group;
21pub use history::History;
22pub use icon::{CustomIcon, Icon};
23pub use iconid::IconId;
24pub use meta::{MemoryProtection, Meta};
25pub use node::{
26 Node, NodeIterator, NodePtr, SerializableNodePtr, group_add_child, group_get_children, group_remove_node_by_uuid, node_is_entry,
27 node_is_equals_to, node_is_group, rc_refcell_node, search_node_by_uuid, search_node_by_uuid_with_specific_type, with_node,
28 with_node_mut,
29};
30pub use times::Times;
31pub use value::Value;
32
33use crate::config::DatabaseConfig;
34use std::collections::{HashMap, HashSet};
35
36use chrono::NaiveDateTime;
37use uuid::Uuid;
38
39#[derive(Debug)]
41#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
42pub struct Database {
43 pub config: DatabaseConfig,
45
46 pub root: SerializableNodePtr,
48
49 pub deleted_objects: HashMap<Uuid, Option<NaiveDateTime>>,
51
52 pub meta: Meta,
54}
55
56impl Clone for Database {
57 fn clone(&self) -> Self {
58 Self {
59 config: self.config.clone(),
60 root: self.root.borrow().duplicate().into(),
61 deleted_objects: self.deleted_objects.clone(),
62 meta: self.meta.clone(),
63 }
64 }
65}
66
67impl PartialEq for Database {
68 fn eq(&self, other: &Self) -> bool {
69 self.config == other.config
70 && self.deleted_objects == other.deleted_objects
71 && self.meta == other.meta
72 && node_is_equals_to(&self.root, &other.root)
73 }
74}
75
76impl Eq for Database {}
77
78impl Database {
79 pub fn purge_unused_custom_icons(&mut self) -> usize {
83 let mut referenced = HashSet::new();
84
85 for node in NodeIterator::new(&self.root) {
86 let node = node.borrow();
87 if let Icon::Custom(uuid) = node.get_icon() {
88 referenced.insert(uuid);
89 }
90
91 if let Some(entry) = node.downcast_ref::<Entry>() {
92 for history_entry in entry.get_history().iter().flat_map(|history| &history.entries) {
93 if let Icon::Custom(uuid) = history_entry.icon {
94 referenced.insert(uuid);
95 }
96 }
97 }
98 }
99
100 let before = self.meta.custom_icons.len();
101 self.meta.custom_icons.retain(|uuid, _| referenced.contains(uuid));
102 before - self.meta.custom_icons.len()
103 }
104
105 pub fn resolve_custom_icon(&self, icon: &Icon) -> Option<&CustomIcon> {
106 match icon {
107 Icon::BuiltIn(_) => None,
108 Icon::Custom(uuid) => self.meta.custom_icon(*uuid),
109 }
110 }
111
112 pub fn new(config: DatabaseConfig) -> Database {
114 Self {
115 config,
116 root: rc_refcell_node(Group::new("Root")).into(),
117 deleted_objects: Default::default(),
118 meta: Meta::new(),
119 }
120 }
121
122 pub fn node_get_parents(&self, node: &NodePtr) -> Vec<Uuid> {
123 let mut parents = Vec::new();
124 let mut parent_uuid = node.borrow().get_parent();
125 while let Some(uuid) = parent_uuid {
126 parents.push(uuid);
127 let parent_node = search_node_by_uuid_with_specific_type::<Group>(&self.root, uuid);
128 parent_uuid = parent_node.and_then(|node| node.borrow().get_parent());
129 }
130 parents
131 }
132
133 pub fn set_recycle_bin_enabled(&mut self, enabled: bool) {
134 self.meta.set_recycle_bin_enabled(enabled);
135 }
136
137 pub fn recycle_bin_enabled(&self) -> bool {
138 self.meta.recycle_bin_enabled()
139 }
140
141 pub fn node_is_recycle_bin(&self, node: &NodePtr) -> bool {
142 let uuid = node.borrow().get_uuid();
143 node_is_group(node) && self.get_recycle_bin().is_some_and(|bin| bin.borrow().get_uuid() == uuid)
144 }
145
146 pub fn node_is_in_recycle_bin(&self, node: Uuid) -> bool {
147 if let Some(node) = search_node_by_uuid(&self.root, node) {
148 let parents = self.node_get_parents(&node);
149 self.get_recycle_bin()
150 .map(|bin| bin.borrow().get_uuid())
151 .is_some_and(|uuid| parents.contains(&uuid))
152 } else {
153 false
154 }
155 }
156
157 pub fn get_recycle_bin(&self) -> Option<NodePtr> {
158 if !self.recycle_bin_enabled() {
159 return None;
160 }
161 let uuid = self.meta.recyclebin_uuid?;
162 group_get_children(&self.root).and_then(|children| {
163 children
164 .into_iter()
165 .find(|child| child.borrow().get_uuid() == uuid && node_is_group(child))
166 })
167 }
168
169 pub fn create_recycle_bin(&mut self) -> crate::Result<NodePtr> {
170 use crate::error::Error;
171 if !self.recycle_bin_enabled() {
172 return Err(Error::RecycleBinDisabled);
173 }
174 if self.get_recycle_bin().is_some() {
175 return Err(Error::RecycleBinAlreadyExists);
176 }
177 let recycle_bin = rc_refcell_node(Group::new("Recycle Bin"));
178 recycle_bin.borrow_mut().set_icon(Icon::BuiltIn(IconId::RECYCLE_BIN));
179 self.meta.recyclebin_uuid = Some(recycle_bin.borrow().get_uuid());
180 let count = group_get_children(&self.root).ok_or("")?.len();
181 group_add_child(&self.root, recycle_bin.clone(), count)?;
182 Ok(recycle_bin)
183 }
184
185 pub fn remove_node_by_uuid(&mut self, uuid: Uuid) -> crate::Result<NodePtr> {
186 if !self.recycle_bin_enabled() {
187 let node = group_remove_node_by_uuid(&self.root, uuid)?;
188 self.deleted_objects.insert(uuid, Some(Times::now()));
189 return Ok(node);
190 }
191 let node_in_recycle_bin = self.node_is_in_recycle_bin(uuid);
192 let recycle_bin = self.get_recycle_bin().ok_or("").or_else(|_| self.create_recycle_bin())?;
193 let recycle_bin_uuid = recycle_bin.borrow().get_uuid();
194 let node = group_remove_node_by_uuid(&self.root, uuid)?;
196 self.deleted_objects.insert(uuid, Some(Times::now()));
197 if uuid != recycle_bin_uuid && !node_in_recycle_bin {
198 group_add_child(&recycle_bin, node.clone(), 0)?;
199 }
200 self.meta.set_recycle_bin_changed();
201 Ok(node)
202 }
203
204 pub fn search_node_by_uuid(&self, uuid: Uuid) -> Option<NodePtr> {
205 search_node_by_uuid(&self.root, uuid)
206 }
207
208 fn create_new_node<T: Node + Default>(&self, parent: Uuid, index: usize) -> crate::Result<NodePtr> {
209 let new_node = rc_refcell_node(T::default());
210 let parent = search_node_by_uuid_with_specific_type::<Group>(&self.root, parent)
211 .or_else(|| Some(self.root.clone().into()))
212 .ok_or("No parent node")?;
213 with_node_mut::<Group, _, _>(&parent, |parent| {
214 parent.add_child(new_node.clone(), index);
215 });
216 Ok(new_node)
217 }
218
219 pub fn create_new_entry(&self, parent: Uuid, index: usize) -> crate::Result<NodePtr> {
220 self.create_new_node::<Entry>(parent, index)
221 }
222
223 pub fn create_new_group(&self, parent: Uuid, index: usize) -> crate::Result<NodePtr> {
224 self.create_new_node::<Group>(parent, index)
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use uuid::uuid;
232
233 fn custom_icon(id: Uuid) -> CustomIcon {
234 CustomIcon {
235 id,
236 name: None,
237 last_modification_time: None,
238 data: vec![1, 2, 3],
239 }
240 }
241
242 #[test]
243 fn purge_unused_custom_icons_keeps_all_referenced_icons() {
244 let group_icon = uuid!("11111111111111111111111111111111");
245 let entry_icon = uuid!("22222222222222222222222222222222");
246 let history_icon = uuid!("33333333333333333333333333333333");
247 let orphan_icon = uuid!("44444444444444444444444444444444");
248
249 let mut database = Database::new(DatabaseConfig::default());
250 database.meta.custom_icons.extend([
251 (group_icon, custom_icon(group_icon)),
252 (entry_icon, custom_icon(entry_icon)),
253 (history_icon, custom_icon(history_icon)),
254 (orphan_icon, custom_icon(orphan_icon)),
255 ]);
256
257 with_node_mut::<Group, _, _>(&database.root, |root| {
258 root.icon = Icon::Custom(group_icon);
259 })
260 .unwrap();
261
262 let history_entry = Entry {
263 icon: Icon::Custom(history_icon),
264 ..Entry::default()
265 };
266 let entry = Entry {
267 icon: Icon::Custom(entry_icon),
268 history: Some(History {
269 entries: vec![history_entry],
270 }),
271 ..Entry::default()
272 };
273 group_add_child(&database.root, rc_refcell_node(entry), 0).unwrap();
274
275 assert_eq!(database.purge_unused_custom_icons(), 1);
276 assert!(database.meta.custom_icons.contains_key(&group_icon));
277 assert!(database.meta.custom_icons.contains_key(&entry_icon));
278 assert!(database.meta.custom_icons.contains_key(&history_icon));
279 assert!(!database.meta.custom_icons.contains_key(&orphan_icon));
280 assert_eq!(database.purge_unused_custom_icons(), 0);
281 }
282}