notedeck

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

notedeck.rs (7629B)


      1 #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
      2 // hide console window on Windows in release
      3 
      4 #[cfg(feature = "memory")]
      5 use re_memory::AccountingAllocator;
      6 
      7 #[cfg(feature = "memory")]
      8 #[global_allocator]
      9 static GLOBAL: AccountingAllocator<std::alloc::System> =
     10     AccountingAllocator::new(std::alloc::System);
     11 
     12 use notedeck::{DataPath, DataPathType, Notedeck};
     13 use notedeck_chrome::{setup::generate_native_options, Chrome};
     14 use tracing_appender::non_blocking::WorkerGuard;
     15 use tracing_subscriber::EnvFilter;
     16 
     17 fn setup_logging(path: &DataPath) -> Option<WorkerGuard> {
     18     #[allow(unused_variables)] // need guard to live for lifetime of program
     19     let (maybe_non_blocking, maybe_guard) = {
     20         let log_path = path.path(DataPathType::Log);
     21         // Setup logging to file
     22 
     23         use tracing_appender::{
     24             non_blocking,
     25             rolling::{RollingFileAppender, Rotation},
     26         };
     27 
     28         let file_appender = RollingFileAppender::builder()
     29             .rotation(Rotation::DAILY)
     30             .filename_prefix(format!("notedeck-{}", env!("CARGO_PKG_VERSION")))
     31             .filename_suffix("log")
     32             .max_log_files(3)
     33             .build(log_path)
     34             .expect("failed to initialize rolling file appender");
     35 
     36         let (non_blocking, _guard) = non_blocking(file_appender);
     37 
     38         (Some(non_blocking), Some(_guard))
     39     };
     40 
     41     // Log to stdout (if you run with `RUST_LOG=debug`).
     42     if let Some(non_blocking_writer) = maybe_non_blocking {
     43         use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt};
     44 
     45         let console_layer = fmt::layer().with_target(true).with_writer(std::io::stdout);
     46 
     47         // Create the file layer (writes to the file)
     48         let file_layer = fmt::layer()
     49             .with_ansi(false)
     50             .with_writer(non_blocking_writer);
     51 
     52         let env_filter =
     53             EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("notedeck=info"));
     54 
     55         // Set up the subscriber to combine both layers
     56         tracing_subscriber::registry()
     57             .with(console_layer)
     58             .with(file_layer)
     59             .with(env_filter)
     60             .init();
     61     } else {
     62         tracing_subscriber::fmt()
     63             .with_env_filter(EnvFilter::from_default_env())
     64             .init();
     65     }
     66 
     67     maybe_guard
     68 }
     69 
     70 // Desktop
     71 #[cfg(not(target_arch = "wasm32"))]
     72 #[tokio::main]
     73 async fn main() {
     74     #[cfg(feature = "memory")]
     75     re_memory::accounting_allocator::set_tracking_callstacks(true);
     76 
     77     let base_path = DataPath::default_base_or_cwd();
     78     let path = DataPath::new(base_path.clone());
     79 
     80     // This guard must be scoped for the duration of the entire program so all logs will be written
     81     let _guard = setup_logging(&path);
     82 
     83     let _res = eframe::run_native(
     84         "Damus Notedeck",
     85         generate_native_options(path),
     86         Box::new(|cc| {
     87             let args: Vec<String> = std::env::args().collect();
     88             let ctx = &cc.egui_ctx;
     89 
     90             let mut notedeck_ctx = Notedeck::init(ctx, base_path, &args);
     91             notedeck_ctx.notedeck.setup(ctx);
     92             let chrome = Chrome::new_with_apps(
     93                 cc,
     94                 &args,
     95                 &mut notedeck_ctx.notedeck,
     96                 notedeck_ctx.outbox_session,
     97             )?;
     98             notedeck_ctx.notedeck.set_app(chrome);
     99 
    100             Ok(Box::new(notedeck_ctx.notedeck))
    101         }),
    102     );
    103 }
    104 
    105 /*
    106  * TODO: nostrdb not supported on web
    107  *
    108 #[cfg(target_arch = "wasm32")]
    109 pub fn main() {
    110     // Make sure panics are logged using `console.error`.
    111     console_error_panic_hook::set_once();
    112 
    113     // Redirect tracing to console.log and friends:
    114     tracing_wasm::set_as_global_default();
    115 
    116     wasm_bindgen_futures::spawn_local(async {
    117         let web_options = eframe::WebOptions::default();
    118         eframe::start_web(
    119             "the_canvas_id", // hardcode it
    120             web_options,
    121             Box::new(|cc| Box::new(Damus::new(cc, "."))),
    122         )
    123         .await
    124         .expect("failed to start eframe");
    125     });
    126 }
    127 */
    128 
    129 #[cfg(test)]
    130 mod tests {
    131     use super::Notedeck;
    132     use notedeck_columns::Damus;
    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::init(&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_ctx = Notedeck::init(&ctx, &tmpdir, &args);
    193         let mut app_ctx = notedeck_ctx.notedeck.app_context(&ctx);
    194         let app = Damus::new(&mut app_ctx, &args);
    195 
    196         assert_eq!(app.columns(app_ctx.accounts).columns().len(), 2);
    197 
    198         let tl1 = app
    199             .columns(app_ctx.accounts)
    200             .column(0)
    201             .router()
    202             .top()
    203             .timeline_id()
    204             .unwrap();
    205 
    206         let tl2 = app
    207             .columns(app_ctx.accounts)
    208             .column(1)
    209             .router()
    210             .top()
    211             .timeline_id()
    212             .unwrap();
    213 
    214         assert_eq!(app.timeline_cache.num_timelines(), 2);
    215         assert!(app.timeline_cache.get(&tl1).is_some());
    216         assert!(app.timeline_cache.get(&tl2).is_some());
    217 
    218         rmrf(tmpdir);
    219     }
    220 
    221     #[tokio::test]
    222     async fn test_unknown_args() {
    223         let tmpdir = create_tmp_dir();
    224         let npub = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s";
    225         let args: Vec<String> = [
    226             "--testrunner",
    227             "--no-keystore",
    228             "--unknown-arg", // <-- UNKNOWN
    229             "--pub",
    230             npub,
    231             "-c",
    232             "notifications",
    233             "-c",
    234             "contacts",
    235         ]
    236         .iter()
    237         .map(|s| s.to_string())
    238         .collect();
    239 
    240         let ctx = egui::Context::default();
    241         let mut notedeck_ctx = Notedeck::init(&ctx, &tmpdir, &args);
    242         let app = Damus::new(&mut notedeck_ctx.notedeck.app_context(&ctx), &args);
    243 
    244         // ensure we recognized all the arguments
    245         let completely_unrecognized: Vec<String> = notedeck_ctx
    246             .notedeck
    247             .unrecognized_args()
    248             .intersection(app.unrecognized_args())
    249             .cloned()
    250             .collect();
    251         assert_eq!(completely_unrecognized, ["--unknown-arg"]);
    252 
    253         rmrf(tmpdir);
    254     }
    255 }