notedeck.rs (8043B)
1 #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 2 // hide console window on Windows in release 3 use notedeck_chrome::setup::{generate_native_options, setup_chrome}; 4 5 use notedeck::{DataPath, DataPathType, Notedeck}; 6 use notedeck_columns::Damus; 7 use tracing_appender::non_blocking::WorkerGuard; 8 use tracing_subscriber::EnvFilter; 9 10 // Entry point for wasm 11 //#[cfg(target_arch = "wasm32")] 12 //use wasm_bindgen::prelude::*; 13 14 fn setup_logging(path: &DataPath) -> Option<WorkerGuard> { 15 #[allow(unused_variables)] // need guard to live for lifetime of program 16 let (maybe_non_blocking, maybe_guard) = { 17 let log_path = path.path(DataPathType::Log); 18 // Setup logging to file 19 20 use tracing_appender::{ 21 non_blocking, 22 rolling::{RollingFileAppender, Rotation}, 23 }; 24 25 let file_appender = RollingFileAppender::new( 26 Rotation::DAILY, 27 log_path, 28 format!("notedeck-{}.log", env!("CARGO_PKG_VERSION")), 29 ); 30 31 let (non_blocking, _guard) = non_blocking(file_appender); 32 33 (Some(non_blocking), Some(_guard)) 34 }; 35 36 // Log to stdout (if you run with `RUST_LOG=debug`). 37 if let Some(non_blocking_writer) = maybe_non_blocking { 38 use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt}; 39 40 let console_layer = fmt::layer().with_target(true).with_writer(std::io::stdout); 41 42 // Create the file layer (writes to the file) 43 let file_layer = fmt::layer() 44 .with_ansi(false) 45 .with_writer(non_blocking_writer); 46 47 let env_filter = 48 EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("notedeck=info")); 49 50 // Set up the subscriber to combine both layers 51 tracing_subscriber::registry() 52 .with(console_layer) 53 .with(file_layer) 54 .with(env_filter) 55 .init(); 56 } else { 57 tracing_subscriber::fmt() 58 .with_env_filter(EnvFilter::from_default_env()) 59 .init(); 60 } 61 62 maybe_guard 63 } 64 65 // Desktop 66 #[cfg(not(target_arch = "wasm32"))] 67 #[tokio::main] 68 async fn main() { 69 let base_path = DataPath::default_base_or_cwd(); 70 let path = DataPath::new(base_path.clone()); 71 72 // This guard must be scoped for the duration of the entire program so all logs will be written 73 let _guard = setup_logging(&path); 74 75 let _res = eframe::run_native( 76 "Damus Notedeck", 77 generate_native_options(path), 78 Box::new(|cc| { 79 let args: Vec<String> = std::env::args().collect(); 80 let ctx = &cc.egui_ctx; 81 let mut notedeck = Notedeck::new(ctx, base_path, &args); 82 setup_chrome(ctx, notedeck.args(), notedeck.theme()); 83 84 let damus = Damus::new(&mut notedeck.app_context(), &args); 85 86 // ensure we recognized all the arguments 87 let completely_unrecognized: Vec<String> = notedeck 88 .unrecognized_args() 89 .intersection(damus.unrecognized_args()) 90 .cloned() 91 .collect(); 92 assert!( 93 completely_unrecognized.is_empty(), 94 "unrecognized args: {:?}", 95 completely_unrecognized 96 ); 97 98 // TODO: move "chrome" frame over Damus app somehow 99 notedeck.set_app(damus); 100 101 Ok(Box::new(notedeck)) 102 }), 103 ); 104 } 105 106 /* 107 * TODO: nostrdb not supported on web 108 * 109 #[cfg(target_arch = "wasm32")] 110 pub fn main() { 111 // Make sure panics are logged using `console.error`. 112 console_error_panic_hook::set_once(); 113 114 // Redirect tracing to console.log and friends: 115 tracing_wasm::set_as_global_default(); 116 117 wasm_bindgen_futures::spawn_local(async { 118 let web_options = eframe::WebOptions::default(); 119 eframe::start_web( 120 "the_canvas_id", // hardcode it 121 web_options, 122 Box::new(|cc| Box::new(Damus::new(cc, "."))), 123 ) 124 .await 125 .expect("failed to start eframe"); 126 }); 127 } 128 */ 129 130 #[cfg(test)] 131 mod tests { 132 use super::{Damus, Notedeck}; 133 use std::path::{Path, PathBuf}; 134 135 fn create_tmp_dir() -> PathBuf { 136 tempfile::TempDir::new() 137 .expect("tmp path") 138 .path() 139 .to_path_buf() 140 } 141 142 fn rmrf(path: impl AsRef<Path>) { 143 let _ = std::fs::remove_dir_all(path); 144 } 145 146 /// Ensure dbpath actually sets the dbpath correctly. 147 #[tokio::test] 148 async fn test_dbpath() { 149 let datapath = create_tmp_dir(); 150 let dbpath = create_tmp_dir(); 151 let args: Vec<String> = [ 152 "--testrunner", 153 "--datapath", 154 &datapath.to_str().unwrap(), 155 "--dbpath", 156 &dbpath.to_str().unwrap(), 157 ] 158 .iter() 159 .map(|s| s.to_string()) 160 .collect(); 161 162 let ctx = egui::Context::default(); 163 let _app = Notedeck::new(&ctx, &datapath, &args); 164 165 assert!(Path::new(&dbpath.join("data.mdb")).exists()); 166 assert!(Path::new(&dbpath.join("lock.mdb")).exists()); 167 assert!(!Path::new(&datapath.join("db")).exists()); 168 169 rmrf(datapath); 170 rmrf(dbpath); 171 } 172 173 #[tokio::test] 174 async fn test_column_args() { 175 let tmpdir = create_tmp_dir(); 176 let npub = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"; 177 let args: Vec<String> = [ 178 "--testrunner", 179 "--no-keystore", 180 "--pub", 181 npub, 182 "-c", 183 "notifications", 184 "-c", 185 "contacts", 186 ] 187 .iter() 188 .map(|s| s.to_string()) 189 .collect(); 190 191 let ctx = egui::Context::default(); 192 let mut notedeck = Notedeck::new(&ctx, &tmpdir, &args); 193 let unrecognized_args = notedeck.unrecognized_args().clone(); 194 let mut app_ctx = notedeck.app_context(); 195 let app = Damus::new(&mut app_ctx, &args); 196 197 // ensure we recognized all the arguments 198 let completely_unrecognized: Vec<String> = unrecognized_args 199 .intersection(app.unrecognized_args()) 200 .cloned() 201 .collect(); 202 assert!( 203 completely_unrecognized.is_empty(), 204 "unrecognized args: {:?}", 205 completely_unrecognized 206 ); 207 208 assert_eq!(app.columns(app_ctx.accounts).columns().len(), 2); 209 210 let tl1 = app 211 .columns(app_ctx.accounts) 212 .column(0) 213 .router() 214 .top() 215 .timeline_id() 216 .unwrap(); 217 218 let tl2 = app 219 .columns(app_ctx.accounts) 220 .column(1) 221 .router() 222 .top() 223 .timeline_id() 224 .unwrap(); 225 226 assert_eq!(app.timeline_cache.timelines.len(), 2); 227 assert!(app.timeline_cache.timelines.get(&tl1).is_some()); 228 assert!(app.timeline_cache.timelines.get(&tl2).is_some()); 229 230 rmrf(tmpdir); 231 } 232 233 #[tokio::test] 234 async fn test_unknown_args() { 235 let tmpdir = create_tmp_dir(); 236 let npub = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"; 237 let args: Vec<String> = [ 238 "--testrunner", 239 "--no-keystore", 240 "--unknown-arg", // <-- UNKNOWN 241 "--pub", 242 npub, 243 "-c", 244 "notifications", 245 "-c", 246 "contacts", 247 ] 248 .iter() 249 .map(|s| s.to_string()) 250 .collect(); 251 252 let ctx = egui::Context::default(); 253 let mut notedeck = Notedeck::new(&ctx, &tmpdir, &args); 254 let mut app_ctx = notedeck.app_context(); 255 let app = Damus::new(&mut app_ctx, &args); 256 257 // ensure we recognized all the arguments 258 let completely_unrecognized: Vec<String> = notedeck 259 .unrecognized_args() 260 .intersection(app.unrecognized_args()) 261 .cloned() 262 .collect(); 263 assert_eq!(completely_unrecognized, ["--unknown-arg"]); 264 265 rmrf(tmpdir); 266 } 267 }