Skip to main content

keepass/db/types/
icon.rs

1use std::{
2    collections::HashSet,
3    ops::{Deref, DerefMut},
4};
5
6use chrono::NaiveDateTime;
7use thiserror::Error;
8use uuid::Uuid;
9
10use crate::{
11    db::{EntryId, EntryMut, EntryRef, GroupId, GroupMut, GroupRef},
12    Database,
13};
14
15/// Icon specification for an [Entry][crate::db::Entry] or [Group][crate::db::Group].
16#[derive(Debug, Eq, PartialEq, Clone)]
17#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
18pub enum Icon {
19    /// The icon is a built-in icon specified by an index
20    BuiltIn(usize),
21
22    /// The icon is a custom icon specified by a [CustomIconId]
23    Custom(CustomIconId),
24}
25
26/// A unique identifier for a [CustomIcon]
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
29pub struct CustomIconId(Uuid);
30
31impl std::fmt::Display for CustomIconId {
32    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33        write!(f, "{}", self.0)
34    }
35}
36
37impl CustomIconId {
38    pub(crate) fn new() -> Self {
39        Self(Uuid::new_v4())
40    }
41
42    pub(crate) const fn from_uuid(uuid: Uuid) -> Self {
43        Self(uuid)
44    }
45
46    /// Get the Uuid contained inside
47    pub fn uuid(&self) -> Uuid {
48        self.0
49    }
50}
51
52/// A custom icon stored in the database, containing raw image data.
53#[derive(Debug, Clone, PartialEq, Eq)]
54#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
55pub struct CustomIcon {
56    pub(crate) id: CustomIconId,
57
58    pub(crate) entries: HashSet<(EntryId, Option<usize>)>,
59    pub(crate) groups: HashSet<GroupId>,
60
61    /// Filename for the icon
62    pub name: Option<String>,
63
64    /// Last modification timestamp
65    pub last_modification_time: Option<NaiveDateTime>,
66
67    /// The raw image data
68    pub data: Vec<u8>,
69}
70
71impl CustomIcon {
72    /// Get the ID of this custom icon
73    pub fn id(&self) -> CustomIconId {
74        self.id
75    }
76}
77
78impl Deref for CustomIcon {
79    type Target = Vec<u8>;
80
81    fn deref(&self) -> &Self::Target {
82        &self.data
83    }
84}
85
86impl DerefMut for CustomIcon {
87    fn deref_mut(&mut self) -> &mut Self::Target {
88        &mut self.data
89    }
90}
91
92/// An immutable reference to a [CustomIcon]. Implements [Deref] to [&CustomIcon][CustomIcon]
93pub struct CustomIconRef<'a> {
94    database: &'a Database,
95    id: CustomIconId,
96}
97
98impl CustomIconRef<'_> {
99    pub(crate) fn new(database: &Database, id: CustomIconId) -> CustomIconRef<'_> {
100        CustomIconRef { database, id }
101    }
102
103    /// Get an immutable reference to the database that owns this custom icon.
104    pub fn database(&self) -> &Database {
105        self.database
106    }
107
108    /// Get an iterator over the entries that reference this custom icon.
109    ///
110    /// If `include_historical` is false, only returns entries that currently reference this
111    /// icon. If `include_historical` is true, also returns old versions of entries that
112    /// reference this icon, even if they have been modified to no longer reference it.
113    pub fn entries(&self, include_historical: bool) -> impl Iterator<Item = EntryRef<'_>> {
114        self.entries.iter().filter_map(move |&(id, history_index)| {
115            if !include_historical && history_index.is_some() {
116                return None;
117            }
118
119            Some(EntryRef::new_historical(self.database, id, history_index))
120        })
121    }
122
123    /// Get an iterator over the groups that reference this custom icon.
124    pub fn groups(&self) -> impl Iterator<Item = GroupRef<'_>> {
125        self.groups
126            .iter()
127            .map(move |&id| GroupRef::new(self.database, id))
128    }
129}
130
131impl Deref for CustomIconRef<'_> {
132    type Target = CustomIcon;
133
134    #[allow(clippy::expect_used)] // CustomIconRef should only be created with valid CustomIconIds
135    fn deref(&self) -> &Self::Target {
136        self.database
137            .custom_icons
138            .get(&self.id)
139            .expect("Custom icon ID always valid")
140    }
141}
142
143/// A mutable reference to a [CustomIcon]. Implements [DerefMut] to [&mut CustomIcon][CustomIcon]
144pub struct CustomIconMut<'a> {
145    database: &'a mut Database,
146    id: CustomIconId,
147}
148
149impl CustomIconMut<'_> {
150    pub(crate) fn new(database: &mut Database, id: CustomIconId) -> CustomIconMut<'_> {
151        CustomIconMut { database, id }
152    }
153
154    /// Get an immutable reference to this custom icon.
155    pub fn as_ref(&self) -> CustomIconRef<'_> {
156        CustomIconRef {
157            database: self.database,
158            id: self.id,
159        }
160    }
161
162    /// Edit this custom icon using a closure. The closure is passed a mutable reference to this
163    /// custom icon.
164    pub fn edit(&mut self, f: impl FnOnce(&mut CustomIconMut<'_>)) -> &mut Self {
165        f(self);
166        self
167    }
168
169    /// Get a mutable reference to the database that owns this custom icon.
170    pub fn database_mut(&mut self) -> &mut Database {
171        self.database
172    }
173
174    /// Apply a closure to each entry that references this custom icon.
175    ///
176    /// The closure is passed a mutable reference to each entry. If `include_historical` is false,
177    /// only applies the closure to entries that currently reference this icon.
178    /// If `include_historical` is true, also applies the closure to old versions of entries that
179    /// reference this icon, even if they have been modified to no longer reference it.
180    pub fn foreach_entry_mut<F>(&mut self, mut f: F, include_historical: bool)
181    where
182        F: FnMut(EntryMut<'_>),
183    {
184        let entries: Vec<(EntryId, Option<usize>)> = self.entries.iter().copied().collect();
185        for (id, history_index) in entries {
186            if !include_historical && history_index.is_some() {
187                continue;
188            }
189
190            f(EntryMut::new_historical(self.database, id, history_index));
191        }
192    }
193
194    /// Apply a closure to each group that references this custom icon.
195    pub fn foreach_group_mut<F>(&mut self, mut f: F)
196    where
197        F: FnMut(GroupMut<'_>),
198    {
199        let groups: Vec<GroupId> = self.groups.iter().copied().collect();
200        for id in groups {
201            f(GroupMut::new(self.database, id));
202        }
203    }
204
205    /// Remove this custom icon from the database, and all references to it
206    pub fn remove(mut self) {
207        let id = self.id;
208
209        self.foreach_entry_mut(
210            |mut entry| {
211                if entry.icon == Some(Icon::Custom(id)) {
212                    entry.icon = None;
213                }
214            },
215            true,
216        );
217
218        self.foreach_group_mut(|mut group| {
219            if group.icon == Some(Icon::Custom(id)) {
220                group.icon = None;
221            }
222        });
223
224        self.database.custom_icons.remove(&id);
225    }
226}
227
228impl Deref for CustomIconMut<'_> {
229    type Target = CustomIcon;
230
231    #[allow(clippy::expect_used)] // CustomIconMut should only be created with valid CustomIconIds
232    fn deref(&self) -> &Self::Target {
233        self.database
234            .custom_icons
235            .get(&self.id)
236            .expect("Custom icon ID always valid")
237    }
238}
239
240impl DerefMut for CustomIconMut<'_> {
241    #[allow(clippy::expect_used)] // CustomIconMut should only be created with valid CustomIconIds
242    fn deref_mut(&mut self) -> &mut Self::Target {
243        self.database
244            .custom_icons
245            .get_mut(&self.id)
246            .expect("Custom icon ID always valid")
247    }
248}
249
250/// Error type for when a [CustomIconId] is provided that does not exist in the database
251#[derive(Error, Debug)]
252#[error("Custom icon {0} not found")]
253pub struct CustomIconNotFoundError(pub(crate) CustomIconId);