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 }