notedeck

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

decks.rs (12303B)


      1 use std::{collections::HashMap, fmt, str::FromStr};
      2 
      3 use enostr::Pubkey;
      4 use nostrdb::{Ndb, Transaction};
      5 use serde::{Deserialize, Serialize};
      6 use tracing::{debug, error};
      7 
      8 use crate::{
      9     column::{Columns, IntermediaryRoute},
     10     decks::{Deck, Decks, DecksCache},
     11     route::Route,
     12     timeline::{TimelineCache, TimelineKind},
     13     Error,
     14 };
     15 
     16 use notedeck::{storage, DataPath, DataPathType, Directory, Localization};
     17 use tokenator::{ParseError, TokenParser, TokenWriter};
     18 
     19 pub static DECKS_CACHE_FILE: &str = "decks_cache.json";
     20 
     21 pub fn load_decks_cache(
     22     path: &DataPath,
     23     ndb: &Ndb,
     24     timeline_cache: &mut TimelineCache,
     25     i18n: &mut Localization,
     26 ) -> Option<DecksCache> {
     27     let data_path = path.path(DataPathType::Setting);
     28 
     29     let decks_cache_str = match Directory::new(data_path).get_file(DECKS_CACHE_FILE.to_owned()) {
     30         Ok(s) => s,
     31         Err(e) => {
     32             error!(
     33                 "Could not read decks cache from file {}:  {}",
     34                 DECKS_CACHE_FILE, e
     35             );
     36             return None;
     37         }
     38     };
     39 
     40     let serializable_decks_cache =
     41         serde_json::from_str::<SerializableDecksCache>(&decks_cache_str).ok()?;
     42 
     43     serializable_decks_cache
     44         .decks_cache(ndb, timeline_cache, i18n)
     45         .ok()
     46 }
     47 
     48 pub fn save_decks_cache(path: &DataPath, decks_cache: &DecksCache) {
     49     let serialized_decks_cache =
     50         match serde_json::to_string(&SerializableDecksCache::to_serializable(decks_cache)) {
     51             Ok(s) => s,
     52             Err(e) => {
     53                 error!("Could not serialize decks cache: {}", e);
     54                 return;
     55             }
     56         };
     57 
     58     let data_path = path.path(DataPathType::Setting);
     59 
     60     if let Err(e) = storage::write_file(
     61         &data_path,
     62         DECKS_CACHE_FILE.to_string(),
     63         &serialized_decks_cache,
     64     ) {
     65         error!(
     66             "Could not write decks cache to file {}: {}",
     67             DECKS_CACHE_FILE, e
     68         );
     69     } else {
     70         debug!("Successfully wrote decks cache to {}", DECKS_CACHE_FILE);
     71     }
     72 }
     73 
     74 #[derive(Serialize, Deserialize)]
     75 struct SerializableDecksCache {
     76     #[serde(serialize_with = "serialize_map", deserialize_with = "deserialize_map")]
     77     decks_cache: HashMap<Pubkey, SerializableDecks>,
     78 }
     79 
     80 impl SerializableDecksCache {
     81     fn to_serializable(decks_cache: &DecksCache) -> Self {
     82         SerializableDecksCache {
     83             decks_cache: decks_cache
     84                 .get_mapping()
     85                 .iter()
     86                 .map(|(k, v)| (*k, SerializableDecks::from_decks(v)))
     87                 .collect(),
     88         }
     89     }
     90 
     91     pub fn decks_cache(
     92         self,
     93         ndb: &Ndb,
     94         timeline_cache: &mut TimelineCache,
     95         i18n: &mut Localization,
     96     ) -> Result<DecksCache, Error> {
     97         let account_to_decks = self
     98             .decks_cache
     99             .into_iter()
    100             .map(|(pubkey, serializable_decks)| {
    101                 serializable_decks
    102                     .decks(ndb, timeline_cache, &pubkey)
    103                     .map(|decks| (pubkey, decks))
    104             })
    105             .collect::<Result<HashMap<Pubkey, Decks>, Error>>()?;
    106 
    107         Ok(DecksCache::new(account_to_decks, i18n))
    108     }
    109 }
    110 
    111 fn serialize_map<S>(
    112     map: &HashMap<Pubkey, SerializableDecks>,
    113     serializer: S,
    114 ) -> Result<S::Ok, S::Error>
    115 where
    116     S: serde::Serializer,
    117 {
    118     let stringified_map: HashMap<String, &SerializableDecks> =
    119         map.iter().map(|(k, v)| (k.hex(), v)).collect();
    120     stringified_map.serialize(serializer)
    121 }
    122 
    123 fn deserialize_map<'de, D>(deserializer: D) -> Result<HashMap<Pubkey, SerializableDecks>, D::Error>
    124 where
    125     D: serde::Deserializer<'de>,
    126 {
    127     let stringified_map: HashMap<String, SerializableDecks> = HashMap::deserialize(deserializer)?;
    128 
    129     stringified_map
    130         .into_iter()
    131         .map(|(k, v)| {
    132             let key = Pubkey::from_hex(&k).map_err(serde::de::Error::custom)?;
    133             Ok((key, v))
    134         })
    135         .collect()
    136 }
    137 
    138 #[derive(Serialize, Deserialize)]
    139 struct SerializableDecks {
    140     active_deck: usize,
    141     decks: Vec<SerializableDeck>,
    142 }
    143 
    144 impl SerializableDecks {
    145     pub fn from_decks(decks: &Decks) -> Self {
    146         Self {
    147             active_deck: decks.active_index(),
    148             decks: decks
    149                 .decks()
    150                 .iter()
    151                 .map(SerializableDeck::from_deck)
    152                 .collect(),
    153         }
    154     }
    155 
    156     fn decks(
    157         self,
    158         ndb: &Ndb,
    159         timeline_cache: &mut TimelineCache,
    160         deck_key: &Pubkey,
    161     ) -> Result<Decks, Error> {
    162         Ok(Decks::from_decks(
    163             self.active_deck,
    164             self.decks
    165                 .into_iter()
    166                 .map(|d| d.deck(ndb, timeline_cache, deck_key))
    167                 .collect::<Result<_, _>>()?,
    168         ))
    169     }
    170 }
    171 
    172 #[derive(Serialize, Deserialize)]
    173 struct SerializableDeck {
    174     metadata: Vec<String>,
    175     columns: Vec<Vec<String>>,
    176 }
    177 
    178 #[derive(PartialEq, Clone)]
    179 enum MetadataKeyword {
    180     Icon,
    181     Name,
    182 }
    183 
    184 impl MetadataKeyword {
    185     const MAPPING: &'static [(&'static str, MetadataKeyword)] = &[
    186         ("icon", MetadataKeyword::Icon),
    187         ("name", MetadataKeyword::Name),
    188     ];
    189 }
    190 impl fmt::Display for MetadataKeyword {
    191     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    192         if let Some(name) = MetadataKeyword::MAPPING
    193             .iter()
    194             .find(|(_, keyword)| keyword == self)
    195             .map(|(name, _)| *name)
    196         {
    197             write!(f, "{name}")
    198         } else {
    199             write!(f, "UnknownMetadataKeyword")
    200         }
    201     }
    202 }
    203 
    204 impl FromStr for MetadataKeyword {
    205     type Err = Error;
    206 
    207     fn from_str(serialized: &str) -> Result<Self, Self::Err> {
    208         MetadataKeyword::MAPPING
    209             .iter()
    210             .find(|(name, _)| *name == serialized)
    211             .map(|(_, keyword)| keyword.clone())
    212             .ok_or(Error::Generic(
    213                 "Could not convert string to Keyword enum".to_owned(),
    214             ))
    215     }
    216 }
    217 
    218 struct MetadataPayload {
    219     keyword: MetadataKeyword,
    220     value: String,
    221 }
    222 
    223 impl MetadataPayload {
    224     fn new(keyword: MetadataKeyword, value: String) -> Self {
    225         Self { keyword, value }
    226     }
    227 }
    228 
    229 fn serialize_metadata(payloads: Vec<MetadataPayload>) -> Vec<String> {
    230     payloads
    231         .into_iter()
    232         .map(|payload| format!("{}:{}", payload.keyword, payload.value))
    233         .collect()
    234 }
    235 
    236 fn deserialize_metadata(serialized_metadatas: Vec<String>) -> Option<Vec<MetadataPayload>> {
    237     let mut payloads = Vec::new();
    238     for serialized_metadata in serialized_metadatas {
    239         let cur_split: Vec<&str> = serialized_metadata.split(':').collect();
    240         if cur_split.len() != 2 {
    241             continue;
    242         }
    243 
    244         if let Ok(keyword) = MetadataKeyword::from_str(cur_split.first().unwrap()) {
    245             payloads.push(MetadataPayload {
    246                 keyword,
    247                 value: cur_split.get(1).unwrap().to_string(),
    248             });
    249         }
    250     }
    251 
    252     if payloads.is_empty() {
    253         None
    254     } else {
    255         Some(payloads)
    256     }
    257 }
    258 
    259 impl SerializableDeck {
    260     pub fn from_deck(deck: &Deck) -> Self {
    261         let columns = serialize_columns(deck.columns());
    262 
    263         let metadata = serialize_metadata(vec![
    264             MetadataPayload::new(MetadataKeyword::Icon, deck.icon.to_string()),
    265             MetadataPayload::new(MetadataKeyword::Name, deck.name.clone()),
    266         ]);
    267 
    268         SerializableDeck { metadata, columns }
    269     }
    270 
    271     pub fn deck(
    272         self,
    273         ndb: &Ndb,
    274         timeline_cache: &mut TimelineCache,
    275         deck_user: &Pubkey,
    276     ) -> Result<Deck, Error> {
    277         let columns = deserialize_columns(ndb, timeline_cache, deck_user, self.columns);
    278         let deserialized_metadata = deserialize_metadata(self.metadata)
    279             .ok_or(Error::Generic("Could not deserialize metadata".to_owned()))?;
    280 
    281         let icon = deserialized_metadata
    282             .iter()
    283             .find(|p| p.keyword == MetadataKeyword::Icon)
    284             .map_or_else(|| "🇩", |f| &f.value);
    285         let name = deserialized_metadata
    286             .iter()
    287             .find(|p| p.keyword == MetadataKeyword::Name)
    288             .map_or_else(|| "Deck", |f| &f.value)
    289             .to_string();
    290 
    291         Ok(Deck::new_with_columns(
    292             icon.parse::<char>()
    293                 .map_err(|_| Error::Generic("could not convert String -> char".to_owned()))?,
    294             name,
    295             columns,
    296         ))
    297     }
    298 }
    299 
    300 fn serialize_columns(columns: &Columns) -> Vec<Vec<String>> {
    301     let mut cols_serialized: Vec<Vec<String>> = Vec::new();
    302 
    303     for column in columns.columns() {
    304         let mut column_routes = Vec::new();
    305         for route in column.router().routes() {
    306             let mut writer = TokenWriter::default();
    307             route.serialize_tokens(&mut writer);
    308             column_routes.push(writer.str().to_string());
    309         }
    310         cols_serialized.push(column_routes);
    311     }
    312 
    313     cols_serialized
    314 }
    315 
    316 fn deserialize_columns(
    317     ndb: &Ndb,
    318     timeline_cache: &mut TimelineCache,
    319     deck_user: &Pubkey,
    320     columns: Vec<Vec<String>>,
    321 ) -> Columns {
    322     let mut cols = Columns::new();
    323     for column in columns {
    324         let Some(route) = column.first() else {
    325             continue;
    326         };
    327 
    328         let tokens: Vec<&str> = route.split(":").collect();
    329         let mut parser = TokenParser::new(&tokens);
    330 
    331         match CleanIntermediaryRoute::parse(&mut parser, deck_user) {
    332             Ok(route_intermediary) => {
    333                 if let Some(ir) = route_intermediary.into_intermediary_route(ndb) {
    334                     cols.insert_intermediary_routes(timeline_cache, vec![ir]);
    335                 }
    336             }
    337             Err(err) => {
    338                 error!("could not turn tokens to RouteIntermediary: {:?}", err);
    339             }
    340         }
    341     }
    342 
    343     cols
    344 }
    345 
    346 enum CleanIntermediaryRoute {
    347     ToTimeline(TimelineKind),
    348     ToRoute(Route),
    349 }
    350 
    351 impl CleanIntermediaryRoute {
    352     fn into_intermediary_route(self, ndb: &Ndb) -> Option<IntermediaryRoute> {
    353         match self {
    354             CleanIntermediaryRoute::ToTimeline(timeline_kind) => {
    355                 let txn = Transaction::new(ndb).unwrap();
    356                 Some(IntermediaryRoute::Timeline(Box::new(
    357                     timeline_kind.into_timeline(&txn, ndb)?,
    358                 )))
    359             }
    360             CleanIntermediaryRoute::ToRoute(route) => Some(IntermediaryRoute::Route(route)),
    361         }
    362     }
    363 
    364     fn parse<'a>(
    365         parser: &mut TokenParser<'a>,
    366         deck_author: &Pubkey,
    367     ) -> Result<Self, ParseError<'a>> {
    368         let timeline = parser.try_parse(|p| {
    369             Ok(CleanIntermediaryRoute::ToTimeline(TimelineKind::parse(
    370                 p,
    371                 deck_author,
    372             )?))
    373         });
    374         if timeline.is_ok() {
    375             return timeline;
    376         }
    377 
    378         parser.try_parse(|p| {
    379             Ok(CleanIntermediaryRoute::ToRoute(Route::parse(
    380                 p,
    381                 deck_author,
    382             )?))
    383         })
    384     }
    385 }
    386 
    387 #[cfg(test)]
    388 mod tests {
    389     //use enostr::Pubkey;
    390 
    391     //use crate::{route::Route, timeline::TimelineRoute};
    392 
    393     //use super::deserialize_columns;
    394 
    395     /* TODO: re-enable once we have test_app working again
    396     #[test]
    397     fn test_deserialize_columns() {
    398         let serialized = vec![
    399             vec!["universe".to_owned()],
    400             vec![
    401                 "notifs:explicit:aa733081e4f0f79dd43023d8983265593f2b41a988671cfcef3f489b91ad93fe"
    402                     .to_owned(),
    403             ],
    404         ];
    405 
    406         let user =
    407             Pubkey::from_hex("aa733081e4f0f79dd43023d8983265593f2b41a988671cfcef3f489b91ad93fe")
    408                 .unwrap();
    409 
    410         let app = test_app();
    411         let cols = deserialize_columns(&app.ndb, user.bytes(), serialized);
    412 
    413         assert_eq!(cols.columns().len(), 2);
    414         let router = cols.column(0).router();
    415         assert_eq!(router.routes().len(), 1);
    416 
    417         if let Route::Timeline(TimelineRoute::Timeline(_)) = router.routes().first().unwrap() {
    418         } else {
    419             panic!("The first router route is not a TimelineRoute::Timeline variant");
    420         }
    421 
    422         let router = cols.column(1).router();
    423         assert_eq!(router.routes().len(), 1);
    424         if let Route::Timeline(TimelineRoute::Timeline(_)) = router.routes().first().unwrap() {
    425         } else {
    426             panic!("The second router route is not a TimelineRoute::Timeline variant");
    427         }
    428     }
    429     */
    430 }