diff --git a/src/app.rs b/src/app.rs index 6a87c2a..5a6dcc6 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,10 +8,10 @@ use axum::{ }; use axum_macros::debug_handler; use git2::{Cred, FetchOptions, PushOptions, RemoteCallbacks, Repository}; -use libtbr::{models, recipe_functions::common}; + use log::{error, info, warn}; use redis::TypedCommands; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use serde_json::{Value, json}; use tokio::sync::Mutex; @@ -23,6 +23,8 @@ pub struct AppState { configures: gcm::Configure, repo: Arc>, redis: Arc>, + // save already fetched path, further calls should fetch from redis instead. + fetched: Vec, } impl AppState { @@ -45,11 +47,30 @@ struct CheckoutParams { } async fn checkout_handler( - State(state): State, + State(mut state): State, Query(param): Query, ) -> impl IntoResponse { let mut response: HashMap = HashMap::new(); + // quick response from redis + if let Ok(mut rcli) = state.redis.try_lock() + && state + .fetched + .contains(¶m.path.clone().as_str().to_string()) + { + if let Ok(res) = rcli.get(param.path.clone().as_str().to_string()) { + match res { + Some(res) => { + response.insert("result".to_string(), json!(res)); + return (axum::http::StatusCode::OK, Json(json!(response))); + } + None => { + // continue + } + } + } + } + let repo_path = state.get_config("GIT_REPO_LOCAL_DEST"); if repo_path.is_none() { response.insert( @@ -105,12 +126,22 @@ async fn checkout_handler( if let Some(blob) = obj.as_blob() { let content = unsafe { str::from_utf8_unchecked(blob.content()) }; response.insert("result".to_string(), json!(content.to_string())); + + if let Ok(mut rcli) = state.redis.try_lock() { + let _ = rcli.set(legit_path.to_string(), content.to_string()); + state.fetched.push(legit_path.to_string()); + } } else if let Some(tree) = obj.as_tree() { let dir_list = tree .iter() .map(|x| x.name().unwrap_or("").to_string()) .collect::>(); response.insert("result".to_string(), json!(dir_list)); + + if let Ok(mut rcli) = state.redis.try_lock() { + let _ = rcli.set(legit_path.to_string(), dir_list.join(",")); + state.fetched.push(legit_path.to_string()); + } } else { let error_log = "not obj nor tree"; error!("{error_log}"); @@ -123,7 +154,7 @@ async fn checkout_handler( async fn fetch_handler(State(state): State) -> impl IntoResponse { let mut response: HashMap = HashMap::new(); - if let Some(repo_path) = state.get_config("GIT_REPO_LOCAL_DEST") { + if let Some(repo_path) = state.clone().get_config("GIT_REPO_LOCAL_DEST") { let rpath = repo_path.clone(); let repo = match Repository::open_bare(rpath) { @@ -192,12 +223,33 @@ async fn fetch_handler(State(state): State) -> impl IntoResponse { ); } + tokio::spawn(async move { + let _ = broadcast_need_update_new_data_after_fetch(state.clone()).await; + }); + ( axum::http::StatusCode::OK, Json(json!({"result": "fetch success"})), ) } +// refetch data after request `/fetch` +async fn broadcast_need_update_new_data_after_fetch( + state: AppState, +) -> Result<(), Box> { + // clear out saved state, user must request again + // state.fetched.clear(); // ----> background fetch into redis + // + // + let state_cl = state.clone(); + let mut rcli = state_cl.redis.try_lock()?; + for s in state.fetched { + let _ = rcli.publish("recipe_repo_news", s); + } + + Ok(()) +} + // { path: "/path/to/file", signature: { // username: "", email: "" // }, patch_key: "edit_id_from_redis"} @@ -382,7 +434,7 @@ async fn apply_patch_to_file( } } - match serde_json::to_string(¤t_full_map.clone()) { + match serde_json::to_string_pretty(¤t_full_map.clone()) { Ok(ss) => ss, Err(_) => String::new(), } @@ -521,6 +573,7 @@ pub async fn run(config: gcm::Configure) -> gcm::StandardResult { config.get("REDIS_URI").unwrap_or(&"".to_string()), config.get("REDIS_PORT").unwrap_or(&"".to_string()) ))?)), + fetched: Vec::new(), }; let app = Router::new() diff --git a/src/main.rs b/src/main.rs index 4ed980a..a201bfd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,10 @@ use env_logger::Builder; use libtbr::recipe_functions::common; use log::info; -use crate::{git::setup_git_repo, reg::{heartbeat_loop, registry::registry_client::RegistryClient}}; +use crate::{ + git::setup_git_repo, + reg::{heartbeat_loop, registry::registry_client::RegistryClient}, +}; mod app; mod gcm; @@ -12,49 +15,60 @@ mod git; mod reg; fn setup_log(config: gcm::Configure) -> gcm::StandardResult { - let logfile = File::create(config.get("LOG_NAME").unwrap_or(&"run.log".to_string()))?; + let logfile = File::create(config.get("LOG_NAME").unwrap_or(&"run.log".to_string()))?; - Builder::from_env(env_logger::Env::default().default_filter_or("debug")) - .target(env_logger::Target::Pipe(Box::new(logfile))).init(); + Builder::from_env(env_logger::Env::default().default_filter_or("debug")) + .target(env_logger::Target::Pipe(Box::new(logfile))) + .init(); - - Ok(()) + Ok(()) } async fn setup_registry(config: gcm::Configure) -> gcm::StandardResult { - let cfg_clone = config.clone(); - let register_server_address = cfg_clone.get("REGISTRY_SERVER").expect("not found registry server"); - let name = cfg_clone.get("SERVICE_NAME").expect("missing service name"); - let url = cfg_clone.get("SERVICE_URL").expect("missing service url"); + let cfg_clone = config.clone(); + let register_server_address = cfg_clone + .get("REGISTRY_SERVER") + .expect("not found registry server"); + let name = cfg_clone.get("SERVICE_NAME").expect("missing service name"); + let url = cfg_clone.get("SERVICE_URL").expect("missing service url"); - let token = cfg_clone.get("REGISTRY_TOKEN").expect("token not provided"); - unsafe { - env::set_var("REGISTRY_TOKEN", token); - } + let token = cfg_clone.get("REGISTRY_TOKEN").expect("token not provided"); + unsafe { + env::set_var("REGISTRY_TOKEN", token); + } - let mut client = RegistryClient::connect(register_server_address.clone()).await?; - reg::register_service(&mut client, name, url).await; + let mut client = RegistryClient::connect(register_server_address.clone()).await?; + reg::register_service(&mut client, name, url).await; - tokio::spawn(heartbeat_loop(name.to_string(), url.to_string())); + tokio::spawn(heartbeat_loop(name.to_string(), url.to_string())); - Ok(()) + Ok(()) } - - #[tokio::main] async fn main() -> gcm::StandardResult { let config: gcm::Configure = common::get_config(); setup_log(config.clone())?; match std::fs::read_dir(config.get("GIT_REPO_LOCAL_DEST").expect("not exist")) { - Ok(_) => { - info!("GIT_REPO `{dest}` already setup", dest = config.get("GIT_REPO_LOCAL_DEST").unwrap()) - }, - Err(_) => { - setup_git_repo(config.clone())?; - } + Ok(_) => { + info!( + "GIT_REPO `{dest}` already setup", + dest = config.get("GIT_REPO_LOCAL_DEST").unwrap() + ) + } + Err(_) => { + setup_git_repo(config.clone())?; + } } + + info!( + "APP VERSION: {}", + config + .get("CONFIG_VERSION") + .expect("config version not defined") + ); + setup_registry(config.clone()).await?; app::run(config.clone()).await?;