notedeck

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

decks.rs (12335B)


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