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#[derive(Debug, Eq, PartialEq, Clone)]
17#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
18pub enum Icon {
19 BuiltIn(usize),
21
22 Custom(CustomIconId),
24}
25
26#[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 pub fn uuid(&self) -> Uuid {
48 self.0
49 }
50}
51
52#[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 pub name: Option<String>,
63
64 pub last_modification_time: Option<NaiveDateTime>,
66
67 pub data: Vec<u8>,
69}
70
71impl CustomIcon {
72 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
92pub 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 pub fn database(&self) -> &Database {
105 self.database
106 }
107
108 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 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)] 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
143pub 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 pub fn as_ref(&self) -> CustomIconRef<'_> {
156 CustomIconRef {
157 database: self.database,
158 id: self.id,
159 }
160 }
161
162 pub fn edit(&mut self, f: impl FnOnce(&mut CustomIconMut<'_>)) -> &mut Self {
165 f(self);
166 self
167 }
168
169 pub fn database_mut(&mut self) -> &mut Database {
171 self.database
172 }
173
174 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 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 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)] 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)] 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#[derive(Error, Debug)]
252#[error("Custom icon {0} not found")]
253pub struct CustomIconNotFoundError(pub(crate) CustomIconId);