feat: fetch from cache if existed
Signed-off-by: Pakin <pakin.t@forth.co.th>
This commit is contained in:
parent
3043f30012
commit
03263815e6
2 changed files with 98 additions and 31 deletions
63
src/app.rs
63
src/app.rs
|
|
@ -8,10 +8,10 @@ use axum::{
|
||||||
};
|
};
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
use git2::{Cred, FetchOptions, PushOptions, RemoteCallbacks, Repository};
|
use git2::{Cred, FetchOptions, PushOptions, RemoteCallbacks, Repository};
|
||||||
use libtbr::{models, recipe_functions::common};
|
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use redis::TypedCommands;
|
use redis::TypedCommands;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Deserialize;
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
|
@ -23,6 +23,8 @@ pub struct AppState {
|
||||||
configures: gcm::Configure,
|
configures: gcm::Configure,
|
||||||
repo: Arc<Mutex<Repository>>,
|
repo: Arc<Mutex<Repository>>,
|
||||||
redis: Arc<Mutex<redis::Client>>,
|
redis: Arc<Mutex<redis::Client>>,
|
||||||
|
// save already fetched path, further calls should fetch from redis instead.
|
||||||
|
fetched: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
|
|
@ -45,11 +47,30 @@ struct CheckoutParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn checkout_handler(
|
async fn checkout_handler(
|
||||||
State(state): State<AppState>,
|
State(mut state): State<AppState>,
|
||||||
Query(param): Query<CheckoutParams>,
|
Query(param): Query<CheckoutParams>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let mut response: HashMap<String, Value> = HashMap::new();
|
let mut response: HashMap<String, Value> = 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");
|
let repo_path = state.get_config("GIT_REPO_LOCAL_DEST");
|
||||||
if repo_path.is_none() {
|
if repo_path.is_none() {
|
||||||
response.insert(
|
response.insert(
|
||||||
|
|
@ -105,12 +126,22 @@ async fn checkout_handler(
|
||||||
if let Some(blob) = obj.as_blob() {
|
if let Some(blob) = obj.as_blob() {
|
||||||
let content = unsafe { str::from_utf8_unchecked(blob.content()) };
|
let content = unsafe { str::from_utf8_unchecked(blob.content()) };
|
||||||
response.insert("result".to_string(), json!(content.to_string()));
|
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() {
|
} else if let Some(tree) = obj.as_tree() {
|
||||||
let dir_list = tree
|
let dir_list = tree
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.name().unwrap_or("").to_string())
|
.map(|x| x.name().unwrap_or("").to_string())
|
||||||
.collect::<Vec<String>>();
|
.collect::<Vec<String>>();
|
||||||
response.insert("result".to_string(), json!(dir_list));
|
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 {
|
} else {
|
||||||
let error_log = "not obj nor tree";
|
let error_log = "not obj nor tree";
|
||||||
error!("{error_log}");
|
error!("{error_log}");
|
||||||
|
|
@ -123,7 +154,7 @@ async fn checkout_handler(
|
||||||
|
|
||||||
async fn fetch_handler(State(state): State<AppState>) -> impl IntoResponse {
|
async fn fetch_handler(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
let mut response: HashMap<String, Value> = HashMap::new();
|
let mut response: HashMap<String, Value> = 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 rpath = repo_path.clone();
|
||||||
|
|
||||||
let repo = match Repository::open_bare(rpath) {
|
let repo = match Repository::open_bare(rpath) {
|
||||||
|
|
@ -192,12 +223,33 @@ async fn fetch_handler(State(state): State<AppState>) -> impl IntoResponse {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let _ = broadcast_need_update_new_data_after_fetch(state.clone()).await;
|
||||||
|
});
|
||||||
|
|
||||||
(
|
(
|
||||||
axum::http::StatusCode::OK,
|
axum::http::StatusCode::OK,
|
||||||
Json(json!({"result": "fetch success"})),
|
Json(json!({"result": "fetch success"})),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// refetch data after request `/fetch`
|
||||||
|
async fn broadcast_need_update_new_data_after_fetch(
|
||||||
|
state: AppState,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// 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: {
|
// { path: "/path/to/file", signature: {
|
||||||
// username: "", email: ""
|
// username: "", email: ""
|
||||||
// }, patch_key: "edit_id_from_redis"}
|
// }, 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,
|
Ok(ss) => ss,
|
||||||
Err(_) => String::new(),
|
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_URI").unwrap_or(&"".to_string()),
|
||||||
config.get("REDIS_PORT").unwrap_or(&"".to_string())
|
config.get("REDIS_PORT").unwrap_or(&"".to_string())
|
||||||
))?)),
|
))?)),
|
||||||
|
fetched: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
|
|
|
||||||
66
src/main.rs
66
src/main.rs
|
|
@ -4,7 +4,10 @@ use env_logger::Builder;
|
||||||
use libtbr::recipe_functions::common;
|
use libtbr::recipe_functions::common;
|
||||||
use log::info;
|
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 app;
|
||||||
mod gcm;
|
mod gcm;
|
||||||
|
|
@ -12,49 +15,60 @@ mod git;
|
||||||
mod reg;
|
mod reg;
|
||||||
|
|
||||||
fn setup_log(config: gcm::Configure) -> gcm::StandardResult {
|
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"))
|
Builder::from_env(env_logger::Env::default().default_filter_or("debug"))
|
||||||
.target(env_logger::Target::Pipe(Box::new(logfile))).init();
|
.target(env_logger::Target::Pipe(Box::new(logfile)))
|
||||||
|
.init();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup_registry(config: gcm::Configure) -> gcm::StandardResult {
|
async fn setup_registry(config: gcm::Configure) -> gcm::StandardResult {
|
||||||
let cfg_clone = config.clone();
|
let cfg_clone = config.clone();
|
||||||
let register_server_address = cfg_clone.get("REGISTRY_SERVER").expect("not found registry server");
|
let register_server_address = cfg_clone
|
||||||
let name = cfg_clone.get("SERVICE_NAME").expect("missing service name");
|
.get("REGISTRY_SERVER")
|
||||||
let url = cfg_clone.get("SERVICE_URL").expect("missing service url");
|
.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");
|
let token = cfg_clone.get("REGISTRY_TOKEN").expect("token not provided");
|
||||||
unsafe {
|
unsafe {
|
||||||
env::set_var("REGISTRY_TOKEN", token);
|
env::set_var("REGISTRY_TOKEN", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut client = RegistryClient::connect(register_server_address.clone()).await?;
|
let mut client = RegistryClient::connect(register_server_address.clone()).await?;
|
||||||
reg::register_service(&mut client, name, url).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]
|
#[tokio::main]
|
||||||
async fn main() -> gcm::StandardResult {
|
async fn main() -> gcm::StandardResult {
|
||||||
let config: gcm::Configure = common::get_config();
|
let config: gcm::Configure = common::get_config();
|
||||||
setup_log(config.clone())?;
|
setup_log(config.clone())?;
|
||||||
|
|
||||||
match std::fs::read_dir(config.get("GIT_REPO_LOCAL_DEST").expect("not exist")) {
|
match std::fs::read_dir(config.get("GIT_REPO_LOCAL_DEST").expect("not exist")) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("GIT_REPO `{dest}` already setup", dest = config.get("GIT_REPO_LOCAL_DEST").unwrap())
|
info!(
|
||||||
},
|
"GIT_REPO `{dest}` already setup",
|
||||||
Err(_) => {
|
dest = config.get("GIT_REPO_LOCAL_DEST").unwrap()
|
||||||
setup_git_repo(config.clone())?;
|
)
|
||||||
}
|
}
|
||||||
|
Err(_) => {
|
||||||
|
setup_git_repo(config.clone())?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"APP VERSION: {}",
|
||||||
|
config
|
||||||
|
.get("CONFIG_VERSION")
|
||||||
|
.expect("config version not defined")
|
||||||
|
);
|
||||||
|
|
||||||
setup_registry(config.clone()).await?;
|
setup_registry(config.clone()).await?;
|
||||||
app::run(config.clone()).await?;
|
app::run(config.clone()).await?;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue