From a6721357934c5a7eb72a893373c144272fd03dfc Mon Sep 17 00:00:00 2001 From: Fries Date: Mon, 3 Jul 2023 01:29:03 -0700 Subject: [PATCH 1/3] add cursed hot reloading support im sorry if you have to look at the sites.rs file. --- Cargo.lock | 100 ++++++++++++++++++++++++++++++++++++-- Cargo.toml | 5 ++ shared/src/directories.rs | 5 ++ shared/src/errors.rs | 3 +- src/main.rs | 15 +++--- src/routes.rs | 24 ++++----- src/sites.rs | 39 +++++++++++++++ src/watcher.rs | 29 +++++++++++ 8 files changed, 197 insertions(+), 23 deletions(-) create mode 100644 src/sites.rs create mode 100644 src/watcher.rs diff --git a/Cargo.lock b/Cargo.lock index 16629f3..2cbac99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -425,6 +425,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "filetime" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", +] + [[package]] name = "fnv" version = "1.0.7" @@ -659,6 +671,26 @@ version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -697,6 +729,26 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +[[package]] +name = "kqueue" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8fc60ba15bf51257aa9807a48a61013db043fcf3a78cb0d916e8e396dcad98" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -768,6 +820,7 @@ dependencies = [ "askama", "askama_rocket", "log", + "notify", "rocket", "rust-embed", "serde", @@ -814,6 +867,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -848,6 +902,22 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "notify" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51" +dependencies = [ + "bitflags 1.3.2", + "filetime", + "inotify", + "kqueue", + "libc", + "mio", + "walkdir", + "windows-sys 0.45.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -915,7 +985,7 @@ dependencies = [ "libc", "redox_syscall 0.3.5", "smallvec", - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -1787,7 +1857,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -1805,13 +1875,37 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 40bebfb..5d883a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = ["cli", "shared"] name = "meowy-webring" version = "0.1.0" edition = "2021" +rust-version = "1.70" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -45,3 +46,7 @@ path = "./shared" [dependencies.simple_logger] version = "4" default-features = false + +[dependencies.notify] +version = "6" +default-features = false diff --git a/shared/src/directories.rs b/shared/src/directories.rs index 8f56795..a746fa6 100644 --- a/shared/src/directories.rs +++ b/shared/src/directories.rs @@ -26,6 +26,11 @@ pub fn get_names_path() -> Result { return get_file_from_directory(directory.data_dir(), "names.json"); } +pub fn get_names_project_path() -> Result { + let directory = get_project_dir()?; + return Ok(directory.data_dir().to_path_buf()); +} + fn create_directory(path: &Path) -> Result<(), Error> { match std::fs::create_dir_all(path) { Ok(_) => { diff --git a/shared/src/errors.rs b/shared/src/errors.rs index aa744fe..a8647ce 100644 --- a/shared/src/errors.rs +++ b/shared/src/errors.rs @@ -5,7 +5,8 @@ pub enum ErrorStatus { DirectoriesError, LoggerInitializationError, NotFoundError, - AlreadyExistsError + AlreadyExistsError, + GenericError } pub struct Error { diff --git a/src/main.rs b/src/main.rs index 505df51..cbbd994 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ -use shared::{directories, names}; +use crate::watcher::hot_reloading; +use rocket::tokio; +use sites::init_names; #[macro_use] extern crate rocket; @@ -6,16 +8,15 @@ extern crate rocket; mod assets; mod links; mod routes; +mod sites; +mod watcher; #[launch] -fn rocket() -> _ { - let names_path = directories::get_names_path().unwrap(); - println!("names.json path: {}", names_path.display()); - let names_file = names::read_names_file(&names_path).unwrap(); - let names = names::load_names(names_file).unwrap(); +async fn rocket() -> _ { + init_names().unwrap(); + tokio::task::spawn_blocking(hot_reloading); rocket::build() - .manage(names) .mount( "/", routes![routes::index, routes::previous, routes::next, routes::name], diff --git a/src/routes.rs b/src/routes.rs index 576669b..28292f1 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,12 +1,14 @@ -use crate::{links::{next_url, previous_url}, assets::ErrorTemplate}; +use crate::{ + assets::ErrorTemplate, + links::{next_url, previous_url}, + sites::{ Names}, +}; use rocket::{ http::Status, response::Redirect, serde::{json::Json, Serialize}, - State, }; -use shared::names::Site; const NOT_FOUND_ERROR: ErrorTemplate = ErrorTemplate { error: "Not Found", @@ -26,27 +28,25 @@ pub fn index() -> &'static str { } #[get("/previous?")] -pub fn previous(source_url: String, names: &State>) -> Result { - let names = names.inner(); - match previous_url(&source_url, names) { +pub fn previous(source_url: String) -> Result { + match previous_url(&source_url, &Names::global()) { Some(url) => Ok(Redirect::to(format!("https://{}", url))), None => Err(Status::NotFound), } } #[get("/next?")] -pub fn next(source_url: String, names: &State>) -> Result { - let names = names.inner(); - match next_url(&source_url, names) { +pub fn next(source_url: String) -> Result { + match next_url(&source_url, &Names::global()) { Some(url) => Ok(Redirect::to(format!("https://{}", url))), None => Err(Status::NotFound), } } #[get("/name?")] -pub fn name(source_url: String, names: &State>) -> Result, Status> { - let previous_site_name = previous_url(&source_url, names); - let next_site_name = next_url(&source_url, names); +pub fn name(source_url: String) -> Result, Status> { + let previous_site_name = previous_url(&source_url, &Names::global()); + let next_site_name = next_url(&source_url, &Names::global()); if previous_site_name.is_none() && next_site_name.is_none() { return Err(Status::NotFound); diff --git a/src/sites.rs b/src/sites.rs new file mode 100644 index 0000000..6509ea8 --- /dev/null +++ b/src/sites.rs @@ -0,0 +1,39 @@ +use shared::{ + directories, + errors::{Error, ErrorStatus}, + names::{self, Site}, +}; +use std::sync::{Mutex, OnceLock}; + +pub struct Names {} + +impl Names { + pub fn global() -> Vec { + NAMES.get().unwrap().lock().unwrap().clone() + } + pub fn set() { + *NAMES.get().unwrap().lock().unwrap() = get_names(); + } +} + +static NAMES: OnceLock>> = OnceLock::new(); + +pub fn init_names() -> Result<(), Error> { + println!( + "names.json path: {}", + directories::get_names_path().unwrap().display() + ); + match NAMES.set(Mutex::new(get_names())) { + Ok(_) => Ok(()), + Err(_) => Err(Error { + status: ErrorStatus::GenericError, + data: "an error has occured while trying to get the names.json file".into(), + }), + } +} + +fn get_names() -> Vec { + let names_path = directories::get_names_path().unwrap(); + let names_file = names::read_names_file(&names_path).unwrap(); + names::load_names(names_file).unwrap() +} diff --git a/src/watcher.rs b/src/watcher.rs new file mode 100644 index 0000000..b908de7 --- /dev/null +++ b/src/watcher.rs @@ -0,0 +1,29 @@ +use notify::{Result, Watcher}; +use shared::directories; + +use crate::sites::Names; + +pub(crate) fn hot_reloading() { + let (tx, rx) = std::sync::mpsc::channel(); + let names_path = directories::get_names_project_path().unwrap(); + let mut watcher = notify::recommended_watcher(tx).unwrap(); + + watcher + .watch(&names_path, notify::RecursiveMode::NonRecursive) + .unwrap(); + + for res in rx { + watch(res); + } +} + +fn watch(res: Result) { + match res { + Ok(event) => { + if event.kind.is_modify() { + Names::set(); + } + } + Err(err) => println!("Error: {}", err), + } +} From 93bd3540e8e86c67ee5761dadcd1d99e11d6e8e3 Mon Sep 17 00:00:00 2001 From: Fries Date: Mon, 3 Jul 2023 22:14:10 -0700 Subject: [PATCH 2/3] use async mutex looks like the best way to do the state thing after all is global so i should use async mutexes so stuff doesn't block. --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + src/routes.rs | 16 ++++++++-------- src/sites.rs | 16 +++++++--------- src/watcher.rs | 5 +++-- 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2cbac99..cf0cf9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,6 +443,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures" version = "0.3.28" @@ -910,6 +919,7 @@ checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51" dependencies = [ "bitflags 1.3.2", "filetime", + "fsevent-sys", "inotify", "kqueue", "libc", diff --git a/Cargo.toml b/Cargo.toml index 5d883a3..79e9b30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,3 +50,4 @@ default-features = false [dependencies.notify] version = "6" default-features = false +features = ["macos_fsevent"] diff --git a/src/routes.rs b/src/routes.rs index 28292f1..5c4c95b 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,7 +1,7 @@ use crate::{ assets::ErrorTemplate, links::{next_url, previous_url}, - sites::{ Names}, + sites::get_global_names, }; use rocket::{ @@ -28,25 +28,25 @@ pub fn index() -> &'static str { } #[get("/previous?")] -pub fn previous(source_url: String) -> Result { - match previous_url(&source_url, &Names::global()) { +pub async fn previous(source_url: String) -> Result { + match previous_url(&source_url, &get_global_names().await) { Some(url) => Ok(Redirect::to(format!("https://{}", url))), None => Err(Status::NotFound), } } #[get("/next?")] -pub fn next(source_url: String) -> Result { - match next_url(&source_url, &Names::global()) { +pub async fn next(source_url: String) -> Result { + match next_url(&source_url, &get_global_names().await) { Some(url) => Ok(Redirect::to(format!("https://{}", url))), None => Err(Status::NotFound), } } #[get("/name?")] -pub fn name(source_url: String) -> Result, Status> { - let previous_site_name = previous_url(&source_url, &Names::global()); - let next_site_name = next_url(&source_url, &Names::global()); +pub async fn name(source_url: String) -> Result, Status> { + let previous_site_name = previous_url(&source_url, &get_global_names().await); + let next_site_name = next_url(&source_url, &get_global_names().await); if previous_site_name.is_none() && next_site_name.is_none() { return Err(Status::NotFound); diff --git a/src/sites.rs b/src/sites.rs index 6509ea8..94b6b04 100644 --- a/src/sites.rs +++ b/src/sites.rs @@ -1,19 +1,17 @@ +use rocket::tokio::sync::Mutex; use shared::{ directories, errors::{Error, ErrorStatus}, names::{self, Site}, }; -use std::sync::{Mutex, OnceLock}; +use std::sync::{OnceLock}; -pub struct Names {} +pub async fn get_global_names() -> Vec { + NAMES.get().unwrap().lock().await.clone() +} -impl Names { - pub fn global() -> Vec { - NAMES.get().unwrap().lock().unwrap().clone() - } - pub fn set() { - *NAMES.get().unwrap().lock().unwrap() = get_names(); - } +pub fn set_names() { + *NAMES.get().unwrap().blocking_lock() = get_names(); } static NAMES: OnceLock>> = OnceLock::new(); diff --git a/src/watcher.rs b/src/watcher.rs index b908de7..34d589b 100644 --- a/src/watcher.rs +++ b/src/watcher.rs @@ -1,7 +1,8 @@ use notify::{Result, Watcher}; use shared::directories; -use crate::sites::Names; +use crate::sites; + pub(crate) fn hot_reloading() { let (tx, rx) = std::sync::mpsc::channel(); @@ -21,7 +22,7 @@ fn watch(res: Result) { match res { Ok(event) => { if event.kind.is_modify() { - Names::set(); + sites::set_names(); } } Err(err) => println!("Error: {}", err), From 33f0b6a5e93558d27352510134a71ad2b919926e Mon Sep 17 00:00:00 2001 From: Fries Date: Mon, 3 Jul 2023 22:41:32 -0700 Subject: [PATCH 3/3] don't panic on parsing error with running watcher this means the watcher doesn't stop working if someone makes an edit to names.json that results in a parser error. it will just print out the error and keep the old configuration. if you restart the program and the file still has an error, the program won't start. --- cli/src/commands/print.rs | 6 +++--- cli/src/commands/remove.rs | 2 +- cli/src/commands/utils.rs | 2 +- src/sites.rs | 17 ++++++++++------- src/watcher.rs | 10 ++++++---- 5 files changed, 21 insertions(+), 16 deletions(-) diff --git a/cli/src/commands/print.rs b/cli/src/commands/print.rs index ca60815..bb67e65 100644 --- a/cli/src/commands/print.rs +++ b/cli/src/commands/print.rs @@ -19,7 +19,7 @@ pub(crate) fn print( if let Some(filter) = filter { names.retain(|f| &f.url == filter); - if names.len() == 0 { + if names.is_empty() { return Err(Error { status: ErrorStatus::NotFoundError, data: "this url was not found in names.json".into(), @@ -50,8 +50,8 @@ fn filter_site( json_printing(site)?; return Ok(()); } - - return Ok(printing(separator, site, group)); + printing(separator, site, group); + Ok(()) } fn json_printing(site: &Site) -> Result<(), Error> { diff --git a/cli/src/commands/remove.rs b/cli/src/commands/remove.rs index 603d94f..ebf68b7 100644 --- a/cli/src/commands/remove.rs +++ b/cli/src/commands/remove.rs @@ -11,7 +11,7 @@ pub(crate) fn remove(path: &Path, url: &String, separator: &String) -> Result<() if &site.url == url { println!( "removing {} from names.json", - site_string(&site, PrintOptions::All, separator) + site_string(site, PrintOptions::All, separator) ); } &site.url != url diff --git a/cli/src/commands/utils.rs b/cli/src/commands/utils.rs index 61ab040..f772cf0 100644 --- a/cli/src/commands/utils.rs +++ b/cli/src/commands/utils.rs @@ -24,5 +24,5 @@ pub(super) fn site_string(site: &Site, options: PrintOptions, separator: &String } } - return string; + string } diff --git a/src/sites.rs b/src/sites.rs index 94b6b04..ead801c 100644 --- a/src/sites.rs +++ b/src/sites.rs @@ -4,14 +4,17 @@ use shared::{ errors::{Error, ErrorStatus}, names::{self, Site}, }; -use std::sync::{OnceLock}; +use std::sync::OnceLock; pub async fn get_global_names() -> Vec { NAMES.get().unwrap().lock().await.clone() } pub fn set_names() { - *NAMES.get().unwrap().blocking_lock() = get_names(); + match get_names() { + Ok(names) => *NAMES.get().unwrap().blocking_lock() = names, + Err(err) => println!("{:?}", err), + } } static NAMES: OnceLock>> = OnceLock::new(); @@ -21,7 +24,7 @@ pub fn init_names() -> Result<(), Error> { "names.json path: {}", directories::get_names_path().unwrap().display() ); - match NAMES.set(Mutex::new(get_names())) { + match NAMES.set(Mutex::new(get_names().unwrap())) { Ok(_) => Ok(()), Err(_) => Err(Error { status: ErrorStatus::GenericError, @@ -30,8 +33,8 @@ pub fn init_names() -> Result<(), Error> { } } -fn get_names() -> Vec { - let names_path = directories::get_names_path().unwrap(); - let names_file = names::read_names_file(&names_path).unwrap(); - names::load_names(names_file).unwrap() +fn get_names() -> Result, Error> { + let names_path = directories::get_names_path()?; + let names_file = names::read_names_file(&names_path)?; + names::load_names(names_file) } diff --git a/src/watcher.rs b/src/watcher.rs index 34d589b..d444c76 100644 --- a/src/watcher.rs +++ b/src/watcher.rs @@ -1,9 +1,11 @@ -use notify::{Result, Watcher}; +use notify::{ + event::{DataChange, ModifyKind}, + EventKind, Result, Watcher, +}; use shared::directories; use crate::sites; - pub(crate) fn hot_reloading() { let (tx, rx) = std::sync::mpsc::channel(); let names_path = directories::get_names_project_path().unwrap(); @@ -21,8 +23,8 @@ pub(crate) fn hot_reloading() { fn watch(res: Result) { match res { Ok(event) => { - if event.kind.is_modify() { - sites::set_names(); + if event.kind == EventKind::Modify(ModifyKind::Data(DataChange::Any)) { + sites::set_names(); } } Err(err) => println!("Error: {}", err),