app.rs (8395B)
1 use crate::{app_size::AppSizeHandler, persist_zoom::ZoomHandler, setup::setup_cc, theme}; 2 3 use notedeck::{ 4 Accounts, AppContext, Args, DataPath, DataPathType, Directory, FileKeyStorage, ImageCache, 5 KeyStorageType, NoteCache, ThemeHandler, UnknownIds, 6 }; 7 8 use enostr::RelayPool; 9 use nostrdb::{Config, Ndb, Transaction}; 10 use notedeck_columns::ui::relay_debug::RelayDebugView; 11 use std::cell::RefCell; 12 use std::path::Path; 13 use std::rc::Rc; 14 use tracing::{error, info}; 15 16 /// Our browser app state 17 pub struct Notedeck { 18 ndb: Ndb, 19 img_cache: ImageCache, 20 unknown_ids: UnknownIds, 21 pool: RelayPool, 22 note_cache: NoteCache, 23 accounts: Accounts, 24 path: DataPath, 25 args: Args, 26 theme: ThemeHandler, 27 tabs: Tabs, 28 app_rect_handler: AppSizeHandler, 29 zoom_handler: ZoomHandler, 30 } 31 32 fn margin_top(narrow: bool) -> f32 { 33 #[cfg(target_os = "android")] 34 { 35 // FIXME - query the system bar height and adjust more precisely 36 let _ = narrow; // suppress compiler warning on android 37 40.0 38 } 39 #[cfg(not(target_os = "android"))] 40 { 41 if narrow { 42 50.0 43 } else { 44 0.0 45 } 46 } 47 } 48 49 /// Our chrome, which is basically nothing 50 fn main_panel(style: &egui::Style, narrow: bool) -> egui::CentralPanel { 51 let inner_margin = egui::Margin { 52 top: margin_top(narrow), 53 left: 0.0, 54 right: 0.0, 55 bottom: 0.0, 56 }; 57 egui::CentralPanel::default().frame(egui::Frame { 58 inner_margin, 59 fill: style.visuals.panel_fill, 60 ..Default::default() 61 }) 62 } 63 64 impl eframe::App for Notedeck { 65 /// Called by the frame work to save state before shutdown. 66 fn save(&mut self, _storage: &mut dyn eframe::Storage) { 67 //eframe::set_value(storage, eframe::APP_KEY, self); 68 } 69 70 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 71 // TODO: render chrome 72 #[cfg(feature = "profiling")] 73 puffin::GlobalProfiler::lock().new_frame(); 74 75 main_panel(&ctx.style(), notedeck::ui::is_narrow(ctx)).show(ctx, |ui| { 76 // render app 77 if let Some(app) = &self.tabs.app { 78 let app = app.clone(); 79 app.borrow_mut().update(&mut self.app_context(), ui); 80 } 81 }); 82 83 self.app_rect_handler.try_save_app_size(ctx); 84 self.zoom_handler.try_save_zoom_factor(ctx); 85 86 if self.args.relay_debug { 87 if self.pool.debug.is_none() { 88 self.pool.use_debug(); 89 } 90 91 if let Some(debug) = &mut self.pool.debug { 92 RelayDebugView::window(ctx, debug); 93 } 94 } 95 96 #[cfg(feature = "profiling")] 97 puffin_egui::profiler_window(ctx); 98 } 99 } 100 101 #[cfg(feature = "profiling")] 102 fn setup_profiling() { 103 puffin::set_scopes_on(true); // tell puffin to collect data 104 } 105 106 impl Notedeck { 107 pub fn new<P: AsRef<Path>>(ctx: &egui::Context, data_path: P, args: &[String]) -> Self { 108 #[cfg(feature = "profiling")] 109 setup_profiling(); 110 111 let parsed_args = Args::parse(args); 112 let is_mobile = parsed_args 113 .is_mobile 114 .unwrap_or(notedeck::ui::is_compiled_as_mobile()); 115 116 // Some people have been running notedeck in debug, let's catch that! 117 if !parsed_args.tests && cfg!(debug_assertions) && !parsed_args.debug { 118 println!("--- WELCOME TO DAMUS NOTEDECK! ---"); 119 println!("It looks like are running notedeck in debug mode, unless you are a developer, this is not likely what you want."); 120 println!("If you are a developer, run `cargo run -- --debug` to skip this message."); 121 println!("For everyone else, try again with `cargo run --release`. Enjoy!"); 122 println!("---------------------------------"); 123 panic!(); 124 } 125 126 setup_cc(ctx, is_mobile, parsed_args.light); 127 128 let data_path = parsed_args 129 .datapath 130 .clone() 131 .unwrap_or(data_path.as_ref().to_str().expect("db path ok").to_string()); 132 let path = DataPath::new(&data_path); 133 let dbpath_str = parsed_args 134 .dbpath 135 .clone() 136 .unwrap_or_else(|| path.path(DataPathType::Db).to_str().unwrap().to_string()); 137 138 let _ = std::fs::create_dir_all(&dbpath_str); 139 140 let imgcache_dir = path.path(DataPathType::Cache).join(ImageCache::rel_dir()); 141 let _ = std::fs::create_dir_all(imgcache_dir.clone()); 142 143 let mapsize = if cfg!(target_os = "windows") { 144 // 16 Gib on windows because it actually creates the file 145 1024usize * 1024usize * 1024usize * 16usize 146 } else { 147 // 1 TiB for everything else since its just virtually mapped 148 1024usize * 1024usize * 1024usize * 1024usize 149 }; 150 151 let theme = ThemeHandler::new(&path); 152 ctx.options_mut(|o| { 153 let cur_theme = theme.load(); 154 info!("Loaded theme {:?} from disk", cur_theme); 155 o.theme_preference = cur_theme; 156 }); 157 ctx.set_visuals_of( 158 egui::Theme::Dark, 159 theme::dark_mode(notedeck::ui::is_compiled_as_mobile()), 160 ); 161 ctx.set_visuals_of(egui::Theme::Light, theme::light_mode()); 162 163 let config = Config::new().set_ingester_threads(4).set_mapsize(mapsize); 164 165 let keystore = if parsed_args.use_keystore { 166 let keys_path = path.path(DataPathType::Keys); 167 let selected_key_path = path.path(DataPathType::SelectedKey); 168 KeyStorageType::FileSystem(FileKeyStorage::new( 169 Directory::new(keys_path), 170 Directory::new(selected_key_path), 171 )) 172 } else { 173 KeyStorageType::None 174 }; 175 176 let mut accounts = Accounts::new(keystore, parsed_args.relays.clone()); 177 178 let num_keys = parsed_args.keys.len(); 179 180 let mut unknown_ids = UnknownIds::default(); 181 let ndb = Ndb::new(&dbpath_str, &config).expect("ndb"); 182 183 { 184 let txn = Transaction::new(&ndb).expect("txn"); 185 for key in &parsed_args.keys { 186 info!("adding account: {}", &key.pubkey); 187 accounts 188 .add_account(key.clone()) 189 .process_action(&mut unknown_ids, &ndb, &txn); 190 } 191 } 192 193 if num_keys != 0 { 194 accounts.select_account(0); 195 } 196 197 // AccountManager will setup the pool on first update 198 let mut pool = RelayPool::new(); 199 { 200 let ctx = ctx.clone(); 201 if let Err(err) = pool.add_multicast_relay(move || ctx.request_repaint()) { 202 error!("error setting up multicast relay: {err}"); 203 } 204 } 205 206 let img_cache = ImageCache::new(imgcache_dir); 207 let note_cache = NoteCache::default(); 208 let unknown_ids = UnknownIds::default(); 209 let tabs = Tabs::new(None); 210 let app_rect_handler = AppSizeHandler::new(&path); 211 let zoom_handler = ZoomHandler::new(&path); 212 213 if let Some(zoom_factor) = zoom_handler.get_zoom_factor() { 214 ctx.set_zoom_factor(zoom_factor); 215 } 216 217 // migrate 218 if let Err(e) = img_cache.migrate_v0() { 219 error!("error migrating image cache: {e}"); 220 } 221 222 Self { 223 ndb, 224 img_cache, 225 app_rect_handler, 226 unknown_ids, 227 pool, 228 note_cache, 229 accounts, 230 path: path.clone(), 231 args: parsed_args, 232 theme, 233 tabs, 234 zoom_handler, 235 } 236 } 237 238 pub fn app_context(&mut self) -> AppContext<'_> { 239 AppContext { 240 ndb: &mut self.ndb, 241 img_cache: &mut self.img_cache, 242 unknown_ids: &mut self.unknown_ids, 243 pool: &mut self.pool, 244 note_cache: &mut self.note_cache, 245 accounts: &mut self.accounts, 246 path: &self.path, 247 args: &self.args, 248 theme: &mut self.theme, 249 } 250 } 251 252 pub fn add_app<T: notedeck::App + 'static>(&mut self, app: T) { 253 self.tabs.app = Some(Rc::new(RefCell::new(app))); 254 } 255 } 256 257 struct Tabs { 258 app: Option<Rc<RefCell<dyn notedeck::App>>>, 259 } 260 261 impl Tabs { 262 pub fn new(app: Option<Rc<RefCell<dyn notedeck::App>>>) -> Self { 263 Self { app } 264 } 265 }