unit.rs (9389B)
1 use std::collections::{BTreeMap, HashSet}; 2 3 use enostr::Pubkey; 4 use nostrdb::ProfileKey; 5 use notedeck::NoteRef; 6 7 use crate::timeline::note_units::{CompositeKey, CompositeType, UnitKey}; 8 9 /// A `NoteUnit` represents a cohesive piece of data derived from notes 10 #[derive(Debug, Clone)] 11 pub enum NoteUnit { 12 Single(NoteRef), // A single note 13 Composite(CompositeUnit), 14 } 15 16 impl NoteUnit { 17 pub fn key(&self) -> UnitKey { 18 match self { 19 NoteUnit::Single(note_ref) => UnitKey::Single(note_ref.key), 20 NoteUnit::Composite(clustered_entry) => UnitKey::Composite(clustered_entry.key()), 21 } 22 } 23 24 pub fn get_underlying_noteref(&self) -> &NoteRef { 25 match self { 26 NoteUnit::Single(note_ref) => note_ref, 27 NoteUnit::Composite(clustered) => match clustered { 28 CompositeUnit::Reaction(reaction_entry) => &reaction_entry.note_reacted_to, 29 CompositeUnit::Repost(repost_unit) => &repost_unit.note_reposted, 30 }, 31 } 32 } 33 34 pub fn get_latest_ref(&self) -> &NoteRef { 35 match self { 36 NoteUnit::Single(note_ref) => note_ref, 37 NoteUnit::Composite(composite_unit) => composite_unit.get_latest_ref(), 38 } 39 } 40 } 41 42 impl Ord for NoteUnit { 43 fn cmp(&self, other: &Self) -> std::cmp::Ordering { 44 self.get_latest_ref().cmp(other.get_latest_ref()) 45 } 46 } 47 48 impl PartialEq for NoteUnit { 49 fn eq(&self, other: &Self) -> bool { 50 self.get_latest_ref() == other.get_latest_ref() 51 } 52 } 53 54 impl Eq for NoteUnit {} 55 56 impl PartialOrd for NoteUnit { 57 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { 58 Some(self.cmp(other)) 59 } 60 } 61 62 /// Combines potentially many notes into one cohesive piece of data 63 #[derive(Debug, Clone)] 64 pub enum CompositeUnit { 65 Reaction(ReactionUnit), 66 Repost(RepostUnit), 67 } 68 69 impl CompositeUnit { 70 pub fn get_latest_ref(&self) -> &NoteRef { 71 match self { 72 CompositeUnit::Reaction(reaction_unit) => reaction_unit.get_latest_ref(), 73 CompositeUnit::Repost(repost_unit) => repost_unit.get_latest_ref(), 74 } 75 } 76 } 77 78 impl PartialEq for CompositeUnit { 79 fn eq(&self, other: &Self) -> bool { 80 match (self, other) { 81 (Self::Reaction(l0), Self::Reaction(r0)) => l0 == r0, 82 (Self::Repost(l0), Self::Repost(r0)) => l0 == r0, 83 _ => false, 84 } 85 } 86 } 87 88 impl CompositeUnit { 89 pub fn key(&self) -> CompositeKey { 90 match self { 91 CompositeUnit::Reaction(reaction_entry) => CompositeKey { 92 key: reaction_entry.note_reacted_to.key, 93 composite_type: CompositeType::Reaction, 94 }, 95 CompositeUnit::Repost(repost_unit) => CompositeKey { 96 key: repost_unit.note_reposted.key, 97 composite_type: CompositeType::Repost, 98 }, 99 } 100 } 101 } 102 103 impl From<CompositeFragment> for CompositeUnit { 104 fn from(value: CompositeFragment) -> Self { 105 match value { 106 CompositeFragment::Reaction(reaction_fragment) => { 107 CompositeUnit::Reaction(reaction_fragment.into()) 108 } 109 CompositeFragment::Repost(repost_fragment) => { 110 CompositeUnit::Repost(repost_fragment.into()) 111 } 112 } 113 } 114 } 115 116 /// Represents all the reactions to a specific note `ReactionUnit::note_reacted_to` 117 #[derive(Debug, PartialEq, Eq, Clone)] 118 pub struct ReactionUnit { 119 pub note_reacted_to: NoteRef, // NOTE: this should not be modified after it's created 120 pub reactions: BTreeMap<NoteRef, Reaction>, 121 pub senders: HashSet<Pubkey>, // useful for making sure the same user can't add more than one reaction to a note 122 } 123 124 impl ReactionUnit { 125 pub fn get_latest_ref(&self) -> &NoteRef { 126 self.reactions 127 .first_key_value() 128 .map(|(r, _)| r) 129 .unwrap_or(&self.note_reacted_to) 130 } 131 } 132 133 impl From<ReactionFragment> for ReactionUnit { 134 fn from(frag: ReactionFragment) -> Self { 135 let mut senders = HashSet::new(); 136 senders.insert(frag.reaction.sender); 137 138 let mut reactions = BTreeMap::new(); 139 reactions.insert(frag.reaction_note_ref, frag.reaction); 140 141 Self { 142 note_reacted_to: frag.noteref_reacted_to, 143 reactions, 144 senders, 145 } 146 } 147 } 148 149 #[derive(Debug, PartialEq, Eq, Clone)] 150 pub struct RepostUnit { 151 pub note_reposted: NoteRef, 152 pub reposts: BTreeMap<NoteRef, Pubkey>, // repost note to sender 153 pub senders: HashSet<Pubkey>, 154 } 155 156 impl RepostUnit { 157 pub fn get_latest_ref(&self) -> &NoteRef { 158 self.reposts 159 .first_key_value() 160 .map(|(r, _)| r) 161 .unwrap_or(&self.note_reposted) 162 } 163 } 164 165 impl From<RepostFragment> for RepostUnit { 166 fn from(value: RepostFragment) -> Self { 167 let mut reposts = BTreeMap::new(); 168 reposts.insert(value.repost_noteref, value.reposter); 169 170 let mut senders = HashSet::new(); 171 senders.insert(value.reposter); 172 173 Self { 174 note_reposted: value.reposted_noteref, 175 reposts, 176 senders, 177 } 178 } 179 } 180 181 #[derive(Clone)] 182 pub enum NoteUnitFragment { 183 Single(NoteRef), 184 Composite(CompositeFragment), 185 } 186 187 #[derive(Debug, Clone)] 188 pub enum CompositeFragment { 189 Reaction(ReactionFragment), 190 Repost(RepostFragment), 191 } 192 193 impl CompositeFragment { 194 pub fn fold_into(self, unit: &mut CompositeUnit) { 195 match self { 196 CompositeFragment::Reaction(reaction_fragment) => { 197 let CompositeUnit::Reaction(reaction_unit) = unit else { 198 tracing::error!("Attempting to fold a reaction fragment into a unit which isn't ReactionUnit. Doing nothing, this should never occur"); 199 return; 200 }; 201 202 reaction_fragment.fold_into(reaction_unit); 203 } 204 CompositeFragment::Repost(repost_fragment) => { 205 let CompositeUnit::Repost(repost_unit) = unit else { 206 tracing::error!("Attempting to fold a repost fragment into a unit which isn't RepostUnit. Doing nothing, this should never occur"); 207 return; 208 }; 209 210 repost_fragment.fold_into(repost_unit); 211 } 212 } 213 } 214 215 pub fn key(&self) -> CompositeKey { 216 match self { 217 CompositeFragment::Reaction(reaction) => CompositeKey { 218 key: reaction.noteref_reacted_to.key, 219 composite_type: CompositeType::Reaction, 220 }, 221 CompositeFragment::Repost(repost) => CompositeKey { 222 key: repost.reposted_noteref.key, 223 composite_type: CompositeType::Repost, 224 }, 225 } 226 } 227 228 pub fn get_underlying_noteref(&self) -> &NoteRef { 229 match self { 230 CompositeFragment::Reaction(reaction_fragment) => &reaction_fragment.noteref_reacted_to, 231 CompositeFragment::Repost(repost_fragment) => &repost_fragment.reposted_noteref, 232 } 233 } 234 235 pub fn get_latest_ref(&self) -> &NoteRef { 236 match self { 237 CompositeFragment::Reaction(reaction_fragment) => &reaction_fragment.reaction_note_ref, 238 CompositeFragment::Repost(repost_fragment) => &repost_fragment.repost_noteref, 239 } 240 } 241 242 pub fn get_type(&self) -> CompositeType { 243 match self { 244 CompositeFragment::Reaction(_) => CompositeType::Reaction, 245 CompositeFragment::Repost(_) => CompositeType::Repost, 246 } 247 } 248 } 249 250 /// A singluar reaction to a note 251 #[derive(Debug, Clone)] 252 pub struct ReactionFragment { 253 pub noteref_reacted_to: NoteRef, 254 pub reaction_note_ref: NoteRef, 255 pub reaction: Reaction, 256 } 257 258 impl ReactionFragment { 259 /// Add all the contents of Self into `CompositeUnit` 260 pub fn fold_into(self, unit: &mut ReactionUnit) { 261 if self.noteref_reacted_to != unit.note_reacted_to { 262 tracing::error!("Attempting to fold a reaction fragment into a ReactionUnit which as a different note reacted to: {:?} != {:?}. This should never occur", self.noteref_reacted_to, unit.note_reacted_to); 263 return; 264 } 265 266 if unit.senders.contains(&self.reaction.sender) { 267 return; 268 } 269 270 unit.senders.insert(self.reaction.sender); 271 unit.reactions.insert(self.reaction_note_ref, self.reaction); 272 } 273 } 274 275 #[derive(Debug, PartialEq, Eq, Clone)] 276 pub struct Reaction { 277 pub reaction: String, // can't use char because some emojis are 'grapheme clusters' 278 pub sender: Pubkey, 279 pub sender_profilekey: Option<ProfileKey>, 280 } 281 282 /// Represents a singular repost 283 #[derive(Debug, Clone)] 284 pub struct RepostFragment { 285 pub reposted_noteref: NoteRef, 286 pub repost_noteref: NoteRef, 287 pub reposter: Pubkey, 288 } 289 290 impl RepostFragment { 291 pub fn fold_into(self, unit: &mut RepostUnit) { 292 if self.reposted_noteref != unit.note_reposted { 293 tracing::error!("Attempting to fold a repost fragment into a RepostUnit which has a different note reposted: {:?} != {:?}. This should never occur", self.reposted_noteref, unit.note_reposted); 294 return; 295 } 296 297 if unit.senders.contains(&self.reposter) { 298 return; 299 } 300 301 unit.senders.insert(self.reposter); 302 unit.reposts.insert(self.repost_noteref, self.reposter); 303 } 304 }