mirror of
https://github.com/Lumither/nocturne.git
synced 2026-10-17 18:36:21 +00:00
feat(blog::components::check+update) update
This commit is contained in:
17 files changed
+234
-86
No files matched your search
@@ -0,0 +1,30 @@
|
||||
use git2::Delta;
|
||||
use std::path::PathBuf;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Change {
|
||||
DELETE {
|
||||
uuid: Uuid,
|
||||
},
|
||||
RENAME {
|
||||
uuid: Uuid,
|
||||
from: PathBuf,
|
||||
to: PathBuf,
|
||||
},
|
||||
UPDATE {
|
||||
uuid: Uuid,
|
||||
content: String,
|
||||
},
|
||||
CREATE {
|
||||
uuid: Uuid,
|
||||
path: PathBuf,
|
||||
content: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DeltaIdx {
|
||||
pub delta_type: Delta,
|
||||
pub content: String,
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum PostIdxError {
|
||||
pub enum Error {
|
||||
#[error("missing essential field `{}` for file `{}`", field, filename)]
|
||||
MissingField { field: String, filename: String },
|
||||
|
||||
@@ -46,4 +46,7 @@ pub enum PostIdxError {
|
||||
id: String,
|
||||
msg: String,
|
||||
},
|
||||
|
||||
#[error("git error")]
|
||||
Git2(#[from] git2::Error),
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::modules::blog::components::check_update::pull::{analyze_deltas, fetch_deltas};
|
||||
use crate::modules::blog::components::check_update::{
|
||||
// index::drop_index,
|
||||
// utils::{expand_path, index_files, search_md},
|
||||
@@ -13,14 +14,18 @@ use crate::modules::blog::components::check_update::{
|
||||
};
|
||||
use crate::scheduler::task_func::AsyncTaskFunc;
|
||||
use sqlx::PgPool;
|
||||
use tracing::{info, warn, Level};
|
||||
use tracing::{info, trace, warn, Level};
|
||||
|
||||
// mod error;
|
||||
// mod index;
|
||||
mod changes;
|
||||
mod error;
|
||||
mod pull;
|
||||
mod utils;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Args {
|
||||
db_connection: PgPool,
|
||||
git_work_dir: PathBuf,
|
||||
git_url: String,
|
||||
git_remote_name: String,
|
||||
@@ -45,7 +50,6 @@ pub fn task(db_connection: PgPool) -> impl AsyncTaskFunc {
|
||||
};
|
||||
|
||||
// clone & check update
|
||||
|
||||
let git_url = match env::var(var_name::BLOG_GIT_URL) {
|
||||
Ok(url) => url,
|
||||
Err(e) => {
|
||||
@@ -63,16 +67,33 @@ pub fn task(db_connection: PgPool) -> impl AsyncTaskFunc {
|
||||
.unwrap_or(default_value::BLOG_GIT_REMOTE_BRANCH.to_string());
|
||||
|
||||
let arc_args = Arc::new(Args {
|
||||
db_connection,
|
||||
git_work_dir,
|
||||
git_url,
|
||||
git_remote_name,
|
||||
git_remote_branch,
|
||||
});
|
||||
|
||||
move || Box::pin(test(arc_args.clone()))
|
||||
move || Box::pin(workflow(arc_args.clone()))
|
||||
}
|
||||
|
||||
async fn test(args: Arc<Args>) {
|
||||
info!("test");
|
||||
dbg!(&args);
|
||||
async fn workflow(args: Arc<Args>) {
|
||||
let deltas = match fetch_deltas(
|
||||
&args.git_url,
|
||||
&args.git_remote_name,
|
||||
&args.git_remote_branch,
|
||||
&args.git_work_dir,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(updates) => updates,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
if deltas.is_empty() {
|
||||
trace!("blog git already up-to-date");
|
||||
return;
|
||||
}
|
||||
|
||||
let updates = analyze_deltas(deltas, &args.git_work_dir).await;
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
use crate::modules::blog::components::check_update::changes::{Change, DeltaIdx};
|
||||
use crate::modules::blog::components::check_update::error::Error;
|
||||
use crate::utils::git;
|
||||
use crate::utils::git::FileDelta;
|
||||
use git2::{Delta, Repository};
|
||||
use markdown::MdFile;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::error;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub async fn fetch_deltas(
|
||||
git_url: &str,
|
||||
git_remote_name: &str,
|
||||
git_branch: &str,
|
||||
git_work_dir: &PathBuf,
|
||||
) -> Result<Vec<FileDelta>, Error> {
|
||||
let repo = match Repository::open(git_work_dir) {
|
||||
Ok(repo) => repo,
|
||||
Err(_) => match Repository::clone(git_url, git_work_dir) {
|
||||
Ok(repo) => repo,
|
||||
Err(e) => {
|
||||
error!("failed to clone blog content git({}): {}", &git_url, e);
|
||||
return Err(e.into());
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let updates = match git::sync(git_remote_name, git_branch, &repo) {
|
||||
Ok(updates) => updates,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"failed to sync blog content git({}) updates: {}",
|
||||
&git_url, e
|
||||
);
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(updates)
|
||||
}
|
||||
|
||||
pub async fn analyze_deltas(file_deltas: Vec<FileDelta>, git_work_dir: &Path) -> Vec<Change> {
|
||||
dbg!(&file_deltas);
|
||||
let mut changes: Vec<Change> = vec![];
|
||||
let mut merge_deltas: Vec<FileDelta> = vec![];
|
||||
|
||||
for delta in file_deltas.into_iter() {
|
||||
if delta.status == Delta::Modified {
|
||||
let file_path = &git_work_dir.join(
|
||||
delta
|
||||
.new_path
|
||||
.expect("cannot parse modified post `new_path`"),
|
||||
);
|
||||
let post = match MdFile::from_file(file_path) {
|
||||
Ok(post) => post,
|
||||
Err(e) => {
|
||||
error!("md file parsing error: {}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let id = match extract_post_id(&post) {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
// todo: add to unresolved error list
|
||||
error!(
|
||||
"failed to parse uuid on post {}",
|
||||
file_path.to_string_lossy()
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
changes.push(Change::UPDATE {
|
||||
uuid: id,
|
||||
content: post.content,
|
||||
})
|
||||
} else {
|
||||
merge_deltas.push(delta)
|
||||
};
|
||||
}
|
||||
|
||||
// merge some addition and remove deltas to rename/move changes
|
||||
let delta_idx = {
|
||||
let mut delta_idx: HashMap<Uuid, Vec<DeltaIdx>> = HashMap::new();
|
||||
for file_delta in merge_deltas.into_iter() {
|
||||
let file_path = &git_work_dir.join(
|
||||
file_delta
|
||||
.new_path
|
||||
.expect("cannot parse modified post `new_path`"),
|
||||
);
|
||||
match file_delta.status {
|
||||
// todo: split impl
|
||||
Delta::Added | Delta::Deleted => {
|
||||
let file_path_str = file_path.to_string_lossy();
|
||||
|
||||
let post = match MdFile::from_file(file_path) {
|
||||
Ok(post) => post,
|
||||
Err(e) => {
|
||||
error!("md file parsing error ({}): {}", file_path_str, e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let id = match extract_post_id(&post) {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
// todo: add to unresolved error list
|
||||
error!("failed to parse uuid on post {}", file_path_str);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let file_delta_idx = DeltaIdx {
|
||||
delta_type: file_delta.status,
|
||||
content: post.content,
|
||||
};
|
||||
delta_idx
|
||||
.entry(id)
|
||||
// todo: change to shallow copy
|
||||
.and_modify(|v| v.push(file_delta_idx.clone()))
|
||||
.or_insert(vec![file_delta_idx]);
|
||||
}
|
||||
_ => {
|
||||
error!("unsupported delta on file {}", file_path.to_string_lossy());
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
delta_idx
|
||||
};
|
||||
|
||||
dbg!(&delta_idx);
|
||||
|
||||
changes
|
||||
}
|
||||
|
||||
fn extract_post_id(post: &MdFile) -> Option<Uuid> {
|
||||
match post.meta["uuid"].as_str() {
|
||||
Some(id) => match Uuid::parse_str(id) {
|
||||
Ok(id) => Some(id),
|
||||
Err(_) => None,
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
@@ -8,48 +8,6 @@ use chrono::{DateTime, FixedOffset, ParseResult};
|
||||
use dirs::home_dir;
|
||||
use tracing::warn;
|
||||
|
||||
// pub fn search_md(entry: PathBuf) -> Vec<PathBuf> {
|
||||
// if entry.ends_with(".git") {
|
||||
// return vec![];
|
||||
// }
|
||||
// if entry.is_dir() {
|
||||
// if let Ok(files) = entry.read_dir() {
|
||||
// files
|
||||
// .flat_map(|file| {
|
||||
// let file_path = file.unwrap().path();
|
||||
// search_md(file_path)
|
||||
// })
|
||||
// .collect()
|
||||
// } else {
|
||||
// vec![]
|
||||
// }
|
||||
// } else if entry.extension().is_some_and(|ext| ext == "md") {
|
||||
// vec![entry]
|
||||
// } else {
|
||||
// vec![]
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn index_files(files: &[PathBuf], db_conn: &Pool<Postgres>) {
|
||||
// let rt = Runtime::new().unwrap();
|
||||
// rt.block_on(async {
|
||||
// let files_clone = files.to_owned();
|
||||
// let handlers = files_clone
|
||||
// .into_iter()
|
||||
// .map(|file: PathBuf| {
|
||||
// let conn_clone = db_conn.clone();
|
||||
// spawn(async move {
|
||||
// if let Err(e) = index_file(&file, &conn_clone).await {
|
||||
// error!("failed to index markdown file `{}`: {}", file.display(), e);
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
//
|
||||
// block_on(join_all(handlers));
|
||||
// });
|
||||
// }
|
||||
|
||||
pub fn expand_path(path: String) -> String {
|
||||
if path.starts_with('~') {
|
||||
home_dir()
|
||||
@@ -75,7 +33,3 @@ pub fn expand_path(path: String) -> String {
|
||||
pub fn parse_date(date_str: &str) -> ParseResult<DateTime<FixedOffset>> {
|
||||
DateTime::parse_from_str(format!("{}T120000", date_str).as_str(), DEFAULT_DATE_FORMAT)
|
||||
}
|
||||
|
||||
// pub fn get_md_file_basename(f_name: &str) -> String {
|
||||
// f_name.replace(".md", "")
|
||||
// }
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod check_update;
|
||||
mod consistency_guard;
|
||||
mod rag;
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in new issue
Block a user