mirror of
https://codeberg.org/postscriptum/snac2nex.git
synced 2026-04-01 05:35:27 +00:00
174 lines
6.2 KiB
Rust
174 lines
6.2 KiB
Rust
use anyhow::{Result, bail};
|
|
use chrono::{DateTime, Utc};
|
|
use std::{collections::HashMap, fs, path::PathBuf, str::FromStr};
|
|
|
|
pub enum Source {
|
|
Url(String),
|
|
File(PathBuf),
|
|
}
|
|
|
|
pub struct Nex {
|
|
filename: String,
|
|
pattern: String,
|
|
time_format: String,
|
|
users: HashMap<String, PathBuf>,
|
|
}
|
|
|
|
impl Nex {
|
|
pub fn init(
|
|
target_dir: String,
|
|
filename: String,
|
|
time_format: String,
|
|
pattern: String,
|
|
user_names: &Vec<String>,
|
|
) -> Result<Self> {
|
|
// init data export location
|
|
let target = PathBuf::from_str(&target_dir)?.canonicalize()?;
|
|
if !target.is_dir() {
|
|
bail!("Target location is not directory!");
|
|
}
|
|
// init locations for each user
|
|
let mut users = HashMap::with_capacity(user_names.len());
|
|
for u in user_names {
|
|
let mut p = PathBuf::from(&target);
|
|
p.push(u);
|
|
fs::create_dir_all(&p)?;
|
|
users.insert(u.clone(), p);
|
|
}
|
|
|
|
Ok(Self {
|
|
filename,
|
|
time_format,
|
|
pattern,
|
|
users,
|
|
})
|
|
}
|
|
|
|
// @TODO: This function requires the following improvements:
|
|
// * validate attachments before updating
|
|
// * apply the post time to the files
|
|
/// Update destination with given data (typically received from Snac)
|
|
pub fn sync(
|
|
&self,
|
|
name: &str,
|
|
content: String,
|
|
link: String,
|
|
attachments: Option<Vec<(String, String, Source)>>,
|
|
tags: Option<Vec<String>>,
|
|
(published, updated): (DateTime<Utc>, Option<DateTime<Utc>>),
|
|
) -> Result<usize> {
|
|
// prepare destination
|
|
let mut p = PathBuf::from(self.users.get(name).unwrap());
|
|
p.push(published.format("%Y").to_string());
|
|
p.push(published.format("%m").to_string());
|
|
p.push(published.format("%d").to_string());
|
|
fs::create_dir_all(&p)?;
|
|
|
|
// init storage directory for the post state file and attachments
|
|
// * by the current implementation, its name starts with dot
|
|
// as usually hidden but accessible (in servers like Nexy)
|
|
let mut d = PathBuf::from(&p);
|
|
d.push(format!(".{}", published.format(&self.filename)));
|
|
fs::create_dir_all(&d)?;
|
|
|
|
// create system meta file to track post time updated
|
|
// if this meta file exists and has timestamp changed, also cleanup attachment files to update
|
|
let mut s = PathBuf::from(&d);
|
|
s.push(".state");
|
|
if !s.exists() || updated.is_some_and(|u| u.to_string() != fs::read_to_string(&s).unwrap())
|
|
{
|
|
fs::remove_dir_all(&d)?;
|
|
fs::create_dir_all(&d)?;
|
|
fs::write(s, updated.unwrap_or(published).to_string())?
|
|
} else {
|
|
println!("\t\t\tpost is up to date.");
|
|
return Ok(0);
|
|
}
|
|
|
|
// format content pattern
|
|
let c = self
|
|
.pattern
|
|
.replace(
|
|
"{content}",
|
|
&if self.pattern.contains("{tags}") {
|
|
content.replace("#", "")
|
|
} else {
|
|
content
|
|
},
|
|
)
|
|
.replace(
|
|
"{attachments}",
|
|
&attachments
|
|
.map(|a| {
|
|
let mut b = Vec::with_capacity(a.len());
|
|
b.push("\n".to_string());
|
|
for (i, (name, media_type, source)) in a.into_iter().enumerate() {
|
|
let mut t = Vec::with_capacity(3);
|
|
t.push(format!(
|
|
"=> {}",
|
|
match source {
|
|
Source::Url(url) => url,
|
|
Source::File(from) => {
|
|
let mut to = PathBuf::from(&d);
|
|
let f = format!(
|
|
"{}{}",
|
|
i + 1,
|
|
from.extension()
|
|
.map(|e| format!(".{}", e.to_string_lossy()))
|
|
.unwrap_or_default(),
|
|
);
|
|
to.push(&f);
|
|
if !to.exists() {
|
|
fs::copy(&from, &to).unwrap();
|
|
println!(
|
|
"\t\t\tcopy attachment file `{}`",
|
|
to.to_string_lossy()
|
|
)
|
|
}
|
|
format!("{}/{f}", d.file_name().unwrap().to_string_lossy())
|
|
}
|
|
}
|
|
));
|
|
if !name.is_empty() {
|
|
t.push(name)
|
|
}
|
|
if !media_type.is_empty() {
|
|
t.push(format!("({media_type})"))
|
|
}
|
|
b.push(t.join(" "))
|
|
}
|
|
b.join("\n")
|
|
})
|
|
.unwrap_or_default(),
|
|
)
|
|
.replace(
|
|
"{tags}",
|
|
&tags
|
|
.map(|t| format!("\n\n{}", t.join(", ")))
|
|
.unwrap_or_default(),
|
|
)
|
|
.replace("{link}", &format!("\n\n=> {link}"))
|
|
.replace(
|
|
"{updated}",
|
|
&updated
|
|
.map(|t| format!("\n\n✏ {}", t.format(&self.time_format)))
|
|
.unwrap_or_default(),
|
|
);
|
|
|
|
// save post file
|
|
p.push(
|
|
published
|
|
.format(self.filename.trim_matches(std::path::MAIN_SEPARATOR))
|
|
.to_string(),
|
|
);
|
|
let f = p.to_string_lossy();
|
|
if fs::exists(&p)? {
|
|
println!("\t\t\tpost file `{f}` update with new content.")
|
|
} else {
|
|
println!("\t\t\tcreate new post file `{f}`.")
|
|
}
|
|
fs::write(&p, c)?;
|
|
|
|
Ok(1)
|
|
}
|
|
}
|