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 Some(uuid) = node.get_custom_icon_uuid() {
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 Some(uuid) = history_entry.custom_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 new(config: DatabaseConfig) -> Database {
107 Self {
108 config,
109 root: rc_refcell_node(Group::new("Root")).into(),
110 deleted_objects: Default::default(),
111 meta: Meta::new(),
112 }
113 }
114
115 pub fn node_get_parents(&self, node: &NodePtr) -> Vec<Uuid> {
116 let mut parents = Vec::new();
117 let mut parent_uuid = node.borrow().get_parent();
118 while let Some(uuid) = parent_uuid {
119 parents.push(uuid);
120 let parent_node = search_node_by_uuid_with_specific_type::<Group>(&self.root, uuid);
121 parent_uuid = parent_node.and_then(|node| node.borrow().get_parent());
122 }
123 parents
124 }
125
126 pub fn set_recycle_bin_enabled(&mut self, enabled: bool) {
127 self.meta.set_recycle_bin_enabled(enabled);
128 }
129
130 pub fn recycle_bin_enabled(&self) -> bool {
131 self.meta.recycle_bin_enabled()
132 }
133
134 pub fn node_is_recycle_bin(&self, node: &NodePtr) -> bool {
135 let uuid = node.borrow().get_uuid();
136 node_is_group(node) && self.get_recycle_bin().is_some_and(|bin| bin.borrow().get_uuid() == uuid)
137 }
138
139 pub fn node_is_in_recycle_bin(&self, node: Uuid) -> bool {
140 if let Some(node) = search_node_by_uuid(&self.root, node) {
141 let parents = self.node_get_parents(&node);
142 self.get_recycle_bin()
143 .map(|bin| bin.borrow().get_uuid())
144 .is_some_and(|uuid| parents.contains(&uuid))
145 } else {
146 false
147 }
148 }
149
150 pub fn get_recycle_bin(&self) -> Option<NodePtr> {
151 if !self.recycle_bin_enabled() {
152 return None;
153 }
154 let uuid = self.meta.recyclebin_uuid?;
155 group_get_children(&self.root).and_then(|children| {
156 children
157 .into_iter()
158 .find(|child| child.borrow().get_uuid() == uuid && node_is_group(child))
159 })
160 }
161
162 pub fn create_recycle_bin(&mut self) -> crate::Result<NodePtr> {
163 use crate::error::Error;
164 if !self.recycle_bin_enabled() {
165 return Err(Error::RecycleBinDisabled);
166 }
167 if self.get_recycle_bin().is_some() {
168 return Err(Error::RecycleBinAlreadyExists);
169 }
170 let recycle_bin = rc_refcell_node(Group::new("Recycle Bin"));
171 recycle_bin.borrow_mut().set_icon_id(Some(IconId::RECYCLE_BIN));
172 self.meta.recyclebin_uuid = Some(recycle_bin.borrow().get_uuid());
173 let count = group_get_children(&self.root).ok_or("")?.len();
174 group_add_child(&self.root, recycle_bin.clone(), count)?;
175 Ok(recycle_bin)
176 }
177
178 pub fn remove_node_by_uuid(&mut self, uuid: Uuid) -> crate::Result<NodePtr> {
179 if !self.recycle_bin_enabled() {
180 let node = group_remove_node_by_uuid(&self.root, uuid)?;
181 self.deleted_objects.insert(uuid, Some(Times::now()));
182 return Ok(node);
183 }
184 let node_in_recycle_bin = self.node_is_in_recycle_bin(uuid);
185 let recycle_bin = self.get_recycle_bin().ok_or("").or_else(|_| self.create_recycle_bin())?;
186 let recycle_bin_uuid = recycle_bin.borrow().get_uuid();
187 let node = group_remove_node_by_uuid(&self.root, uuid)?;
189 self.deleted_objects.insert(uuid, Some(Times::now()));
190 if uuid != recycle_bin_uuid && !node_in_recycle_bin {
191 group_add_child(&recycle_bin, node.clone(), 0)?;
192 }
193 self.meta.set_recycle_bin_changed();
194 Ok(node)
195 }
196
197 pub fn search_node_by_uuid(&self, uuid: Uuid) -> Option<NodePtr> {
198 search_node_by_uuid(&self.root, uuid)
199 }
200
201 fn create_new_node<T: Node + Default>(&self, parent: Uuid, index: usize) -> crate::Result<NodePtr> {
202 let new_node = rc_refcell_node(T::default());
203 let parent = search_node_by_uuid_with_specific_type::<Group>(&self.root, parent)
204 .or_else(|| Some(self.root.clone().into()))
205 .ok_or("No parent node")?;
206 with_node_mut::<Group, _, _>(&parent, |parent| {
207 parent.add_child(new_node.clone(), index);
208 });
209 Ok(new_node)
210 }
211
212 pub fn create_new_entry(&self, parent: Uuid, index: usize) -> crate::Result<NodePtr> {
213 self.create_new_node::<Entry>(parent, index)
214 }
215
216 pub fn create_new_group(&self, parent: Uuid, index: usize) -> crate::Result<NodePtr> {
217 self.create_new_node::<Group>(parent, index)
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use uuid::uuid;
225
226 fn custom_icon(id: Uuid) -> CustomIcon {
227 CustomIcon {
228 id,
229 name: None,
230 last_modification_time: None,
231 data: vec![1, 2, 3],
232 }
233 }
234
235 #[test]
236 fn purge_unused_custom_icons_keeps_all_referenced_icons() {
237 let group_icon = uuid!("11111111111111111111111111111111");
238 let entry_icon = uuid!("22222222222222222222222222222222");
239 let history_icon = uuid!("33333333333333333333333333333333");
240 let orphan_icon = uuid!("44444444444444444444444444444444");
241
242 let mut database = Database::new(DatabaseConfig::default());
243 database.meta.custom_icons.extend([
244 (group_icon, custom_icon(group_icon)),
245 (entry_icon, custom_icon(entry_icon)),
246 (history_icon, custom_icon(history_icon)),
247 (orphan_icon, custom_icon(orphan_icon)),
248 ]);
249
250 with_node_mut::<Group, _, _>(&database.root, |root| {
251 root.custom_icon_uuid = Some(group_icon);
252 })
253 .unwrap();
254
255 let history_entry = Entry {
256 custom_icon: Some(history_icon),
257 ..Entry::default()
258 };
259 let entry = Entry {
260 custom_icon: Some(entry_icon),
261 history: Some(History {
262 entries: vec![history_entry],
263 }),
264 ..Entry::default()
265 };
266 group_add_child(&database.root, rc_refcell_node(entry), 0).unwrap();
267
268 assert_eq!(database.purge_unused_custom_icons(), 1);
269 assert!(database.meta.custom_icons.contains_key(&group_icon));
270 assert!(database.meta.custom_icons.contains_key(&entry_icon));
271 assert!(database.meta.custom_icons.contains_key(&history_icon));
272 assert!(!database.meta.custom_icons.contains_key(&orphan_icon));
273 assert_eq!(database.purge_unused_custom_icons(), 0);
274 }
275}