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 }