notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }