summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2022-08-03 04:56:28 +0000
committerLars Wirzenius <liw@liw.fi>2022-08-03 04:56:28 +0000
commit3b951869505b7f1515b9c4b78f19455a235246bd (patch)
treee46e55b6a97aed6931e01da4695512735006d532
parent65c453b9b8499eb4a273bfd6f013608b9af23844 (diff)
parent6b39def739b0f5174d6c842bd798df179541db60 (diff)
downloadriki-3b951869505b7f1515b9c4b78f19455a235246bd.tar.gz
Merge branch 'mtime' into 'main'
feat: set output file modification times See merge request larswirzenius/riki!30
-rw-r--r--Cargo.toml1
-rw-r--r--riki.md21
-rw-r--r--src/bin/riki.rs3
-rw-r--r--src/error.rs12
-rw-r--r--src/page.rs36
-rw-r--r--src/site.rs11
-rw-r--r--src/util.rs51
7 files changed, 125 insertions, 10 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 5346b03..b6a610b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,7 @@ license = "GPL-3.0-or-later"
anyhow = "1.0.52"
clap = { version = "3.2.10", features = ["derive"] }
html-escape = "0.2.11"
+libc = "0.2.126"
log = "0.4.17"
pretty_env_logger = "0.4.0"
pulldown-cmark = "0.9.0"
diff --git a/riki.md b/riki.md
index e208baa..7adb3ff 100644
--- a/riki.md
+++ b/riki.md
@@ -529,3 +529,24 @@ then stdout doesn't contain ".git"
then stdout doesn't contain "index.mdwn~"
then stdout doesn't contain "#index.mdwn#"
~~~
+
+## Output directory tree
+
+### Output files have source file modification times
+
+_Requirement: Files in the output directory have the same time stamp
+as the corresponding files in the source directory._
+
+Note that due to limitations in the Subplot `lib/files` library, our
+check for modification times is imprecise.
+
+~~~scenario
+given an installed riki
+given file site/index.mdwn from empty
+given file site/index.mdwn has modification time 1970-01-01 00:00:00
+given file site/index.jpg from empty
+given file site/index.jpg has modification time 1970-01-01 00:00:00
+when I run riki build site output
+then file output/index.html has a very old modification time
+then file output/index.jpg has a very old modification time
+~~~
diff --git a/src/bin/riki.rs b/src/bin/riki.rs
index afd43ef..6a9744e 100644
--- a/src/bin/riki.rs
+++ b/src/bin/riki.rs
@@ -2,7 +2,7 @@ use clap::Parser;
use log::{debug, error, info};
use riki::error::SiteError;
use riki::site::Site;
-use riki::util::{canonicalize, copy_file_from_source, mkdir};
+use riki::util::{canonicalize, copy_file_from_source, mkdir, set_mtime};
use std::error::Error;
use std::path::PathBuf;
@@ -83,6 +83,7 @@ impl Build {
let output = page.meta().destination_filename(&destdir);
debug!("writing: {}", output.display());
htmlpage.write(&output)?;
+ set_mtime(&output, page.meta().mtime())?;
}
for file in site.files() {
diff --git a/src/error.rs b/src/error.rs
index 756d7f0..aee8252 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -52,4 +52,16 @@ pub enum SiteError {
#[error("attempt to use definition lists in Markdown: {0:?}")]
DefinitionList(String),
+
+ #[error("failed to get file metadata: {0}")]
+ FileMetadata(PathBuf, #[source] std::io::Error),
+
+ #[error("failed to get file modification time: {0}")]
+ FileMtime(PathBuf, #[source] std::io::Error),
+
+ #[error("failed to set modification time for file {0}")]
+ Utimensat(PathBuf, #[source] std::io::Error),
+
+ #[error("failed to convert time to Unix time")]
+ UnixTime(#[source] std::time::SystemTimeError),
}
diff --git a/src/page.rs b/src/page.rs
index d2f144a..41a8584 100644
--- a/src/page.rs
+++ b/src/page.rs
@@ -2,10 +2,11 @@ use crate::error::SiteError;
use crate::html::{parse, Content, Element, ElementTag, HtmlPage};
use crate::parser::WikitextParser;
use crate::site::Site;
-use crate::util::{join_subpath, make_path_relative_to};
+use crate::util::{get_mtime, join_subpath, make_path_relative_to};
use crate::wikitext::Snippet;
use log::{info, trace};
use std::path::{Path, PathBuf};
+use std::time::SystemTime;
#[derive(Debug, Eq, PartialEq)]
pub struct WikitextPage {
@@ -29,7 +30,12 @@ impl WikitextPage {
.to_string();
let data = std::fs::read(filename).map_err(|e| SiteError::FileRead(filename.into(), e))?;
let wikitext = String::from_utf8(data).map_err(|e| SiteError::Utf8(filename.into(), e))?;
- let meta = MetaBuilder::default().name(name).path(absolute).build();
+ let mtime = get_mtime(filename)?;
+ let meta = MetaBuilder::default()
+ .name(name)
+ .path(absolute)
+ .mtime(mtime)
+ .build();
Ok(Self::new(meta, wikitext))
}
@@ -122,17 +128,24 @@ pub struct PageMeta {
name: String,
title: Option<String>,
path: PathBuf,
+ mtime: SystemTime,
}
impl PageMeta {
- fn new(name: String, title: Option<String>, path: PathBuf) -> Self {
+ fn new(name: String, title: Option<String>, path: PathBuf, mtime: SystemTime) -> Self {
trace!(
- "PageMeta: name={:?} title={:?} path={:?}",
+ "PageMeta: name={:?} title={:?} path={:?} mtime={:?}",
name,
title,
- path
+ path,
+ mtime,
);
- Self { name, title, path }
+ Self {
+ name,
+ title,
+ path,
+ mtime,
+ }
}
pub fn destination_filename(&self, destdir: &Path) -> PathBuf {
@@ -159,6 +172,10 @@ impl PageMeta {
pub fn path(&self) -> &Path {
&self.path
}
+
+ pub fn mtime(&self) -> SystemTime {
+ self.mtime
+ }
}
#[derive(Debug, Default)]
@@ -166,6 +183,7 @@ pub struct MetaBuilder {
name: String,
title: Option<String>,
path: Option<PathBuf>,
+ mtime: Option<SystemTime>,
}
impl MetaBuilder {
@@ -174,6 +192,7 @@ impl MetaBuilder {
self.name,
self.title,
self.path.expect("path set on MetaBuilder"),
+ self.mtime.expect("mtime set on MetaBuilder"),
)
}
@@ -191,4 +210,9 @@ impl MetaBuilder {
self.path = Some(path);
self
}
+
+ pub fn mtime(mut self, mtime: SystemTime) -> Self {
+ self.mtime = Some(mtime);
+ self
+ }
}
diff --git a/src/site.rs b/src/site.rs
index d48d94c..fd28a2b 100644
--- a/src/site.rs
+++ b/src/site.rs
@@ -258,10 +258,17 @@ impl PageSet {
mod test {
use super::{Site, SiteError, WikitextPage};
use crate::page::MetaBuilder;
- use std::path::{Path, PathBuf};
+ use std::{
+ path::{Path, PathBuf},
+ time::SystemTime,
+ };
fn page(path: &str) -> WikitextPage {
- let meta = MetaBuilder::default().path(PathBuf::from(path)).build();
+ let mtime = SystemTime::now();
+ let meta = MetaBuilder::default()
+ .path(PathBuf::from(path))
+ .mtime(mtime)
+ .build();
WikitextPage::new(meta, "".into())
}
diff --git a/src/util.rs b/src/util.rs
index e1f82b3..1f69523 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,6 +1,10 @@
use crate::error::SiteError;
-use log::{debug, trace};
+use libc::{timespec, utimensat, AT_FDCWD, AT_SYMLINK_NOFOLLOW};
+use log::{debug, error, trace};
+use std::ffi::CString;
+use std::os::unix::ffi::OsStrExt;
use std::path::{Component, Path, PathBuf};
+use std::time::{SystemTime, UNIX_EPOCH};
pub fn canonicalize(path: &Path) -> Result<PathBuf, SiteError> {
path.canonicalize()
@@ -16,6 +20,36 @@ pub fn mkdir(path: &Path) -> Result<(), SiteError> {
pub fn copy(src: &Path, dest: &Path) -> Result<(), SiteError> {
trace!("copying: {} -> {}", src.display(), dest.display());
std::fs::copy(src, dest).map_err(|e| SiteError::CopyFile(src.into(), dest.into(), e))?;
+ let mtime = get_mtime(src)?;
+ set_mtime(dest, mtime)?;
+ Ok(())
+}
+
+pub fn get_mtime(src: &Path) -> Result<SystemTime, SiteError> {
+ let metadata = std::fs::metadata(src).map_err(|e| SiteError::FileMetadata(src.into(), e))?;
+ let mtime = metadata
+ .modified()
+ .map_err(|e| SiteError::FileMtime(src.into(), e))?;
+ Ok(mtime)
+}
+
+pub fn set_mtime(filename: &Path, mtime: SystemTime) -> Result<(), SiteError> {
+ let mtime = timespec(mtime)?;
+ let times = [mtime, mtime];
+ let times: *const timespec = &times[0];
+
+ let pathbuf = filename.to_path_buf();
+ let path = path_to_cstring(filename);
+
+ // We have to use unsafe here to be able call the libc functions
+ // below.
+ unsafe {
+ if utimensat(AT_FDCWD, path.as_ptr(), times, AT_SYMLINK_NOFOLLOW) == -1 {
+ let error = std::io::Error::last_os_error();
+ error!("utimensat failed on {:?}", path);
+ return Err(SiteError::Utimensat(pathbuf, error));
+ }
+ }
Ok(())
}
@@ -76,6 +110,21 @@ pub fn make_path_absolute(path: &Path) -> PathBuf {
Path::new("/").join(&path)
}
+fn timespec(time: SystemTime) -> Result<timespec, SiteError> {
+ let dur = time
+ .duration_since(UNIX_EPOCH)
+ .map_err(SiteError::UnixTime)?;
+ let tv_sec = dur.as_secs() as libc::time_t;
+ let tv_nsec = dur.subsec_nanos() as libc::c_long;
+ Ok(timespec { tv_sec, tv_nsec })
+}
+
+fn path_to_cstring(path: &Path) -> CString {
+ let path = path.as_os_str();
+ let path = path.as_bytes();
+ CString::new(path).unwrap()
+}
+
#[cfg(test)]
mod test {
use super::{