You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
252 lines
7.0 KiB
252 lines
7.0 KiB
// #[global_allocator]
|
|
// static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
|
|
|
#[macro_use]
|
|
extern crate smart_default;
|
|
#[macro_use]
|
|
extern crate failure;
|
|
#[macro_use]
|
|
extern crate serde_derive;
|
|
#[macro_use]
|
|
extern crate log;
|
|
|
|
use std::path::{PathBuf, Path};
|
|
use std::collections::HashMap;
|
|
use std::fs::{File, DirEntry, OpenOptions};
|
|
use std::io::{Read, Write};
|
|
use std::{env, fs, io};
|
|
use failure::Fallible;
|
|
use clap;
|
|
|
|
mod hash_dict;
|
|
mod logging;
|
|
mod bread;
|
|
use bread::Bread;
|
|
use crate::hash_dict::HashDict;
|
|
use chrono::Datelike;
|
|
use itertools::Itertools;
|
|
|
|
#[derive(Serialize, Deserialize, Debug, SmartDefault, Clone)]
|
|
#[serde(default)]
|
|
pub struct AppConfig {
|
|
#[default = "info"]
|
|
logging: String,
|
|
#[default = "https://www.ondrovo.com/bread"]
|
|
web_url : String,
|
|
}
|
|
|
|
pub struct GalleryInfo {
|
|
web_path: PathBuf,
|
|
data_path: PathBuf,
|
|
tpl_path: PathBuf,
|
|
thumbs_path: PathBuf,
|
|
base_url: String,
|
|
templates: HashMap<String, String>,
|
|
image_hashes: hash_dict::HashDict,
|
|
latest_year: i32,
|
|
oldest_year: i32,
|
|
}
|
|
|
|
impl GalleryInfo {
|
|
/// Read a named template from file, reusing from cache if possible
|
|
fn template(&mut self, name: &str) -> Fallible<String> {
|
|
if let Some(text) = self.templates.get(name) {
|
|
return Ok(text.clone());
|
|
}
|
|
|
|
let mut tpl = String::new();
|
|
File::open(self.tpl_path.join(name))?.read_to_string(&mut tpl)?;
|
|
self.templates.insert(name.to_string(), tpl.clone());
|
|
Ok(tpl)
|
|
}
|
|
}
|
|
|
|
const CONFIG_FILE: &str = "breadgen.json";
|
|
|
|
fn main() -> Fallible<()> {
|
|
let version = clap::crate_version!();
|
|
let clap =
|
|
clap::App::new("Flowbox RT")
|
|
.version(version)
|
|
.arg(
|
|
clap::Arg::with_name("config")
|
|
.short("c")
|
|
.long("config")
|
|
.value_name("FILE")
|
|
.help("Sets a custom config file (default: breadgen.json)")
|
|
.takes_value(true),
|
|
);
|
|
|
|
let clap = logging::add_clap_args(clap);
|
|
let argv = clap.get_matches();
|
|
|
|
/* Load config */
|
|
let appcfg = {
|
|
let confile = argv.value_of("config")
|
|
.unwrap_or(CONFIG_FILE);
|
|
|
|
println!("Bread gallery builder {}\nrun with -h for help", version);
|
|
println!("config file: {}", confile);
|
|
|
|
let buf = read_file(confile)
|
|
.unwrap_or("{}".to_string());
|
|
|
|
let config: AppConfig = serde_json::from_str(&buf)?;
|
|
|
|
let _ = logging::init(&config.logging, &argv, None);
|
|
config
|
|
};
|
|
|
|
let mut ginfo = {
|
|
let cwd = env::current_dir()?;
|
|
let web_path = cwd.join("web");
|
|
let data_path = web_path.join("data");
|
|
let tpl_path = web_path.join("templates");
|
|
let thumbs_path = web_path.join("thumbs");
|
|
|
|
GalleryInfo {
|
|
web_path,
|
|
data_path,
|
|
tpl_path,
|
|
thumbs_path,
|
|
base_url: appcfg.web_url.clone(),
|
|
templates: HashMap::new(),
|
|
image_hashes: HashDict::load(cwd.join(".hashes.txt"))?,
|
|
latest_year: 0,
|
|
oldest_year: 0,
|
|
}
|
|
};
|
|
|
|
let mut bread_dirs: Vec<DirEntry> = fs::read_dir(&ginfo.data_path)?
|
|
.filter_map(Result::ok)
|
|
.collect();
|
|
|
|
bread_dirs.sort_by(|x, y| {
|
|
x.file_name().cmp(&y.file_name())
|
|
});
|
|
|
|
let mut breads: Vec<Bread> = bread_dirs
|
|
.iter()
|
|
.filter_map(|p| Bread::parse(&ginfo.web_path, &p).ok())
|
|
.collect();
|
|
|
|
ginfo.latest_year = breads.last().unwrap().date.year();
|
|
ginfo.oldest_year = breads.first().unwrap().date.year();
|
|
|
|
for i in 0..breads.len() {
|
|
let preceding = if i <= 0 { None } else {
|
|
match breads.get(i - 1) {
|
|
Some(b) => Some(b.to_link()),
|
|
None => None
|
|
}
|
|
};
|
|
|
|
let following = match breads.get(i + 1) {
|
|
Some(b) => Some(b.to_link()),
|
|
None => None
|
|
};
|
|
|
|
let cur = breads.get_mut(i).unwrap();
|
|
|
|
cur.compile(&mut ginfo, preceding, following)?;
|
|
}
|
|
|
|
let mut channel: rss::Channel = rss::ChannelBuilder::default()
|
|
.title("Piggo's Bread Gallery")
|
|
.link("https://www.ondrovo.com/bread")
|
|
.description("Sourdough feed")
|
|
.build()
|
|
.unwrap();
|
|
|
|
ginfo.image_hashes.save();
|
|
|
|
// rss
|
|
{
|
|
let start = (breads.len() as i32 - 10).max(0) as usize;
|
|
let mut channel_items = vec![];
|
|
for b in &mut breads[start..] {
|
|
channel_items.push(b.rendered.rss_item.take().unwrap());
|
|
}
|
|
|
|
info!("Generating feed...");
|
|
let f = OpenOptions::new()
|
|
.write(true)
|
|
.truncate(true)
|
|
.create(true)
|
|
.open(ginfo.web_path.join("feed.xml"))
|
|
.unwrap();
|
|
|
|
channel.set_items(channel_items);
|
|
channel.pretty_write_to(f, b' ', 2).unwrap();
|
|
}
|
|
|
|
// main page
|
|
{
|
|
// make thumbs go from the newest to the oldest
|
|
//breads.reverse(); // actually don't do that
|
|
|
|
let oldest = ginfo.oldest_year;
|
|
let latest = ginfo.latest_year;
|
|
let base_url = ginfo.base_url.clone();
|
|
let year_pager = |year| {
|
|
let mut buf = String::new();
|
|
for y in oldest..=latest {
|
|
if year == y {
|
|
buf.push_str(&format!(" <li class=\"active\"><a href=\"#\">{y}</a>\n", y=y));
|
|
} else {
|
|
if y == latest {
|
|
buf.push_str(&format!(" <li><a href=\"{u}/index.html\">{y}</a>\n", u=base_url, y=y));
|
|
} else {
|
|
buf.push_str(&format!(" <li><a href=\"{u}/{y}.html\">{y}</a>\n", u=base_url, y=y));
|
|
}
|
|
}
|
|
}
|
|
buf
|
|
};
|
|
|
|
for (year, year_breads) in &breads.iter().group_by(|b| b.date.year()) {
|
|
let mut thumbs_buf = String::new();
|
|
for b in year_breads {
|
|
thumbs_buf.push_str(&b.rendered.thumb);
|
|
}
|
|
|
|
info!("Building the gallery page");
|
|
|
|
let head = ginfo.template("_head.html")?
|
|
.replace("{title}", "Piggo's breads");
|
|
|
|
let main = ginfo.template("index.html")?
|
|
.replace("{breads}", thumbs_buf.trim())
|
|
.replace("{year_pager}", year_pager(year).trim())
|
|
.replace("{head}", head.trim());
|
|
|
|
let fname = if year == ginfo.latest_year {
|
|
"index.html".to_string()
|
|
} else {
|
|
format!("{}.html", year)
|
|
};
|
|
|
|
let mut f = OpenOptions::new()
|
|
.write(true)
|
|
.truncate(true)
|
|
.create(true)
|
|
.open(ginfo.web_path.join(fname))
|
|
.unwrap();
|
|
|
|
f.write(main.as_bytes()).unwrap();
|
|
}
|
|
|
|
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn read_file<P: AsRef<Path>>(path: P) -> io::Result<String> {
|
|
let path = path.as_ref();
|
|
let mut file = File::open(path)?;
|
|
|
|
let mut buf = String::new();
|
|
file.read_to_string(&mut buf)?;
|
|
Ok(buf)
|
|
}
|
|
|