aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--include/components.h6
-rw-r--r--include/fs.h9
-rw-r--r--include/site.h1
-rw-r--r--src/components.c32
-rw-r--r--src/fs.c37
-rw-r--r--src/render.c64
-rw-r--r--src/site.c137
8 files changed, 219 insertions, 72 deletions
diff --git a/README.md b/README.md
index da6663f..1bf6ac0 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ A static web image gallery generator. It optimizes images for the web and
generates HTML files to create a photo/image gallery web site ready to be served
by an HTML server.
-EARLY STAGES! It's functional but still rough around the edges.
+Early stages. It's functional but a little rough around the edges.
## Building
@@ -23,9 +23,6 @@ DEBUG=1 make
## TODO:
-* Fix template generation on outdated html files. Right now if, for example, a
- new image was added, the templates for the next and prev images, and the album
- are not updated accordingly.
* Add exif tags to template hashmap.
* Improve unja or find better alternative.
* Get rid of previews variable. It is a temporary hack to limit the amount of
diff --git a/include/components.h b/include/components.h
index e65a66a..9e568ee 100644
--- a/include/components.h
+++ b/include/components.h
@@ -40,9 +40,11 @@ struct image {
struct hashmap *map;
/* Hashmap with values to be passed to thumbs and previews vectors */
struct hashmap *thumb;
+ bool modified;
};
struct album {
+ struct site *site;
struct album_config *config;
/* The path to the source directory */
char *source;
@@ -69,11 +71,13 @@ struct album {
struct image *image_new(char *src, const struct stat *, struct album *);
+struct image *image_old(struct stat *istat);
+
int image_cmp(const void *a, const void *b);
void image_destroy(void *data);
-struct album *album_new(struct album_config *, struct site_config *,
+struct album *album_new(struct album_config *, struct site *,
const char *src, const char *rsrc, const struct stat *);
int album_cmp(const void *a, const void *b);
diff --git a/include/fs.h b/include/fs.h
index 218958b..d48ac9f 100644
--- a/include/fs.h
+++ b/include/fs.h
@@ -6,6 +6,8 @@
#include "hashmap.h"
+typedef bool (*preremove_fn)(const char *path, void *data);
+
enum nmkdir_res {
NMKDIR_ERROR,
NMKDIR_NOOP,
@@ -49,7 +51,7 @@ int file_is_uptodate(const char *path, const struct timespec *srcmtim);
*/
void setdatetime(const char *path, const struct timespec *mtim);
-bool rmentry(const char *path);
+bool rmentry(const char *path, bool dry);
/*
* Recursively deletes extaneous files from directory, keeping files in the
@@ -57,7 +59,8 @@ bool rmentry(const char *path);
* The number is not the total number of files on all subdirectories, but only
* the number of files/dirs deleted from the directory pointed by path.
*/
-ssize_t rmextra(const char *path, struct hashmap *preserved);
+ssize_t rmextra(const char *path, struct hashmap *preserved, preremove_fn,
+ void *data, bool dry);
/*
* Copies file(s) truncating and overwritting the file(s) in the destination
@@ -67,6 +70,6 @@ ssize_t rmextra(const char *path, struct hashmap *preserved);
* timestamps do not match.
*/
bool filesync(const char *restrict srcpath, const char *restrict dstpath,
- struct hashmap *preserved);
+ struct hashmap *preserved, bool dry);
#endif
diff --git a/include/site.h b/include/site.h
index 6ddac82..db82517 100644
--- a/include/site.h
+++ b/include/site.h
@@ -34,6 +34,7 @@ struct site {
struct hashmap *album_dirs;
struct render render;
bool dry_run;
+ size_t albums_updated;
};
bool site_build(struct site *);
diff --git a/src/components.c b/src/components.c
index 2e3bc5e..876cb39 100644
--- a/src/components.c
+++ b/src/components.c
@@ -72,7 +72,7 @@ static void
image_date_from_stat(struct image *image, const struct stat *pstat,
struct tm *date)
{
- image->tstamp = pstat->st_ctim.tv_sec;
+ image->tstamp = pstat->st_mtim.tv_sec;
localtime_r(&image->tstamp, date);
}
@@ -123,6 +123,18 @@ out:
}
struct image *
+image_old(struct stat *istat)
+{
+ struct image *image = calloc(1, sizeof *image);
+ if (image == NULL) {
+ log_printl_errno(LOG_FATAL, "Memory allocation error");
+ return NULL;
+ }
+ image->tstamp = istat->st_mtim.tv_sec;
+ return image;
+}
+
+struct image *
image_new(char *src, const struct stat *pstat, struct album *album)
{
struct image *image = calloc(1, sizeof *image);
@@ -159,6 +171,7 @@ image_new(char *src, const struct stat *pstat, struct album *album)
image_set_date(image, pstat);
image->map = hashmap_new_with_cap(8);
image->thumb = hashmap_new_with_cap(4);
+ image->modified = false;
return image;
}
@@ -180,13 +193,15 @@ image_destroy(void *data)
if (image->exif_data) {
exif_data_unref(image->exif_data);
}
- hashmap_free(image->map);
- hashmap_free(image->thumb);
+ if (image->map) {
+ hashmap_free(image->map);
+ hashmap_free(image->thumb);
+ }
free(image);
}
struct album *
-album_new(struct album_config *conf, struct site_config *sconf, const char *src,
+album_new(struct album_config *conf, struct site *site, const char *src,
const char *rsrc, const struct stat *dstat)
{
struct album *album = calloc(1, sizeof *album);
@@ -194,15 +209,16 @@ album_new(struct album_config *conf, struct site_config *sconf, const char *src,
log_printl_errno(LOG_FATAL, "Memory allocation error");
return NULL;
}
+ album->site = site;
album->config = conf;
album->source = strdup(src);
- album->slug = slugify(rsrc, sconf->base_url, &album->url);
+ album->slug = slugify(rsrc, site->config->base_url, &album->url);
album->images = bstree_new(image_cmp, image_destroy);
album->tstamp = MAXTIME;
- album->image_dirs = hashmap_new();
+ album->preserved = hashmap_new();
album->map = hashmap_new_with_cap(16);
album->thumbs = vector_new(128);
- album->previews = vector_new(sconf->max_previews);
+ album->previews = vector_new(site->config->max_previews);
return album;
}
@@ -242,7 +258,7 @@ album_destroy(void *data)
free(album->source);
free(album->url);
bstree_destroy(album->images);
- hashmap_free(album->image_dirs);
+ hashmap_free(album->preserved);
hashmap_free(album->map);
vector_free(album->thumbs);
vector_free(album->previews);
diff --git a/src/fs.c b/src/fs.c
index 58bb43d..49d6e0d 100644
--- a/src/fs.c
+++ b/src/fs.c
@@ -157,7 +157,7 @@ setdatetime(const char *path, const struct timespec *mtim)
}
bool
-rmentry(const char *path)
+rmentry(const char *path, bool dry)
{
struct stat st;
if (stat(path, &st)) {
@@ -175,21 +175,23 @@ rmentry(const char *path)
char target[PATH_MAX];
sprintf(target, "%s/%s", path, ent->d_name);
- if (!rmentry(target)) {
+ if (!rmentry(target, dry)) {
closedir(dir);
return false;
}
}
closedir(dir);
+ if (dry) goto success;
if (rmdir(path)) goto error;
goto success;
}
+ if (dry) goto success;
if (unlink(path)) goto error;
success:
- log_printl(LOG_DETAIL, "Deleted %s", path);
+ log_printl(LOG_DETAIL, "Deleting %s", path);
return true;
error:
log_printl_errno(LOG_ERROR, "Can't delete %s", path);
@@ -197,7 +199,8 @@ error:
}
ssize_t
-rmextra(const char *path, struct hashmap *preserved)
+rmextra(const char *path, struct hashmap *preserved, preremove_fn cb,
+ void *data, bool dry)
{
ssize_t removed = 0;
DIR *dir = opendir(path);
@@ -213,7 +216,10 @@ rmextra(const char *path, struct hashmap *preserved)
char target[PATH_MAX];
sprintf(target, "%s/%s", path, ent->d_name);
- if (!rmentry(target)) {
+ if (cb != NULL) {
+ if (!cb(target, data)) return -1;
+ }
+ if (!rmentry(target, dry)) {
closedir(dir);
return -1;
}
@@ -226,7 +232,7 @@ rmextra(const char *path, struct hashmap *preserved)
bool
filesync(const char *restrict srcpath, const char *restrict dstpath,
- struct hashmap *preserved)
+ struct hashmap *preserved, bool dry)
{
int fdsrc;
struct stat stsrc;
@@ -235,23 +241,23 @@ filesync(const char *restrict srcpath, const char *restrict dstpath,
fdsrc = open(srcpath, O_RDONLY);
if (fdsrc < 0) {
- log_printl_errno(LOG_ERROR, "Failed to open %s", srcpath);
+ log_printl_errno(LOG_ERROR, "Couldn't open %s", srcpath);
return false;
}
if (fstat(fdsrc, &stsrc)) {
- log_printl_errno(LOG_ERROR, "Failed to stat %s", srcpath);
+ log_printl_errno(LOG_ERROR, "Couldn't stat %s", srcpath);
goto dir_error;
}
if (S_ISDIR(stsrc.st_mode)) {
if (mkdir(dstpath, 0755)) {
if (errno != EEXIST) {
- log_printl_errno(LOG_ERROR, "Failed to create directory %s",
+ log_printl_errno(LOG_ERROR, "Couldn't create directory %s",
dstpath);
goto dir_error;
}
if (stat(dstpath, &stsrc)) {
- log_printl_errno(LOG_ERROR, "Failed to stat %s", dstpath);
+ log_printl_errno(LOG_ERROR, "Couldn't stat %s", dstpath);
goto dir_error;
}
if (!S_ISDIR(stsrc.st_mode)){
@@ -292,14 +298,14 @@ filesync(const char *restrict srcpath, const char *restrict dstpath,
vector_push(own, name);
}
}
- if (!filesync(entsrc, entdst, NULL)) {
+ if (!filesync(entsrc, entdst, NULL, dry)) {
closedir(dir);
return false;
}
}
if (cleanup) {
- rmextra(dstpath, preserved);
+ rmextra(dstpath, preserved, NULL, NULL, dry);
if (own) {
for (size_t i = 0; i < own->size; i++) {
free(own->values[i]);
@@ -320,6 +326,11 @@ filesync(const char *restrict srcpath, const char *restrict dstpath,
} else if (uptodate < 0) {
goto dir_error;
}
+
+ log_printl(LOG_DETAIL, "Copying %s", srcpath);
+
+ if (dry) goto success;
+
fddst = open(dstpath, O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fddst < 0) {
log_printl_errno(LOG_ERROR, "Failed to open/create %s", dstpath);
@@ -358,8 +369,6 @@ filesync(const char *restrict srcpath, const char *restrict dstpath,
};
futimens(fddst, tms);
- log_printl(LOG_DETAIL, "Copied %s", srcpath);
-
close(fddst);
success:
close(fdsrc);
diff --git a/src/render.c b/src/render.c
index 79fb543..7008e50 100644
--- a/src/render.c
+++ b/src/render.c
@@ -107,6 +107,12 @@ render_set_album_vars(struct render *r, struct album *album)
years_push_album(r->years, album);
vector_push(r->albums, album->map);
+ /*
+ * The common vars for the images in this album; used by both the album
+ * template and the image template.
+ */
+ hashmap_insert(r->common_vars, "album", album->map);
+
return true;
}
@@ -127,24 +133,19 @@ render(struct env *env, const char *tmpl, const char *opath,
return ok;
}
-#define RENDER_MAKE_START \
- bool ok = true; \
- int isupdate = file_is_uptodate(path, &r->modtime); \
- if (isupdate == -1) return false; \
- if (isupdate == 1) return true; \
- log_print(LOG_INFO, "Rendering %s...", path); \
- if (r->dry_run) goto done
-
-#define RENDER_MAKE_END \
- setdatetime(path, &r->modtime); \
-done: \
- log_printf(LOG_INFO, " done.\n"); \
- return ok
-
bool
render_make_index(struct render *r, const char *path)
{
- RENDER_MAKE_START;
+ bool ok = true;
+ if (r->albums_updated == 0) {
+ int isupdate = file_is_uptodate(path, &r->modtime);
+ if (isupdate == -1) return false;
+ if (isupdate == 1) return true;
+ }
+
+ log_printl(LOG_INFO, "Rendering %s", path);
+
+ if (r->dry_run) goto done;
hashmap_insert(r->common_vars, "years", r->years);
hashmap_insert(r->common_vars, "albums", r->years);
@@ -152,35 +153,48 @@ render_make_index(struct render *r, const char *path)
hashmap_remove(r->common_vars, "years");
hashmap_remove(r->common_vars, "albums");
- RENDER_MAKE_END;
+ setdatetime(path, &r->modtime);
+done:
+ return ok;
}
bool
render_make_album(struct render *r, const char *path, const struct album *album)
{
- if (!r->dry_run) hashmap_insert(r->common_vars, "album", album->map);
+ bool ok = true;
+ if (album->images_updated == 0) {
+ int isupdate = file_is_uptodate(path, &r->modtime);
+ if (isupdate == -1) return false;
+ if (isupdate == 1) return true;
+ }
+
+ log_printl(LOG_INFO, "Rendering %s", path);
- RENDER_MAKE_START;
+ if (r->dry_run) goto done;
ok = render(r->env, "album.html", path, r->common_vars);
- /*
- * Since we actually still want this album's map for the image inside the
- * album, we don't remove it.
- */
- RENDER_MAKE_END;
+ setdatetime(path, &r->modtime);
+done:
+ return ok;
}
bool
render_make_image(struct render *r, const char *path, const struct image *image)
{
- RENDER_MAKE_START;
+ bool ok = true;
+
+ log_printl(LOG_INFO, "Rendering %s", path);
+
+ if (r->dry_run) goto done;
hashmap_insert(r->common_vars, "image", image->map);
ok = render(r->env, "image.html", path, r->common_vars);
hashmap_remove(r->common_vars, "image");
- RENDER_MAKE_END;
+ setdatetime(path, &r->modtime);
+done:
+ return ok;
}
bool
diff --git a/src/site.c b/src/site.c
index 6fe8d85..a47d16e 100644
--- a/src/site.c
+++ b/src/site.c
@@ -9,15 +9,61 @@
#include "fs.h"
#include "log.h"
#include "hashmap.h"
-#include "render.h"
-/* TODO: Probably shouldn't use PATH_MAX, but i'll leave it for now */
/* TODO: handle error cases for paths that are too long */
#define THUMB_SUFFIX "_thumb"
static const char *index_html = "index.html";
+/*
+ * Checks where the removed image used to be based on the modification time of
+ * the image dir and updates the html of the images between which it used to be,
+ * if they haven't been updated yet.
+ */
+static bool
+prerm_imagedir(const char *path, void *data)
+{
+ struct stat st;
+ struct album *album = data;
+ char htmlpath[PATH_MAX];
+
+ if (stat(path, &st)) {
+ log_printl_errno(LOG_ERROR, "Couldn't stat %s", path);
+ return false;
+ }
+ if (S_ISDIR(st.st_mode)) {
+ struct image *old = image_old(&st);
+ struct bstnode *imnode = bstree_add(album->images, old),
+ *prev = bstree_predecessor(imnode),
+ *next = bstree_successor(imnode);
+ if (prev) {
+ struct image *imprev = (struct image *)prev->value;
+ if (!imprev->modified) {
+ joinpathb(htmlpath, imprev->dst, index_html);
+ if (!render_make_image(&album->site->render, htmlpath, imprev)) {
+ goto fail;
+ }
+ }
+ }
+ if (next) {
+ struct image *imnext = (struct image *)next->value;
+ if (!imnext->modified) {
+ joinpathb(htmlpath, imnext->dst, index_html);
+ if (!render_make_image(&album->site->render, htmlpath, imnext)) {
+ goto fail;
+ }
+ }
+ }
+ bstree_remove(album->images, imnode);
+ return true;
+fail:
+ bstree_remove(album->images, imnode);
+ return false;
+ }
+ return true;
+}
+
static bool
wand_passfail(MagickWand *wand, MagickPassFail status)
{
@@ -44,7 +90,7 @@ optimize_image(MagickWand *wand, const char *dst,
if (update == -1) return false;
if (update == 1) return true;
- log_print(LOG_DETAIL, "Converting %s...", dst);
+ log_printl(LOG_DETAIL, "Converting %s", dst);
if (dry) goto out;
unsigned long nx = conf->max_width, ny = conf->max_height;
@@ -60,18 +106,17 @@ optimize_image(MagickWand *wand, const char *dst,
if (conf->smart_resize) {
double ratio = (double)x / y;
if (x > y) {
- ny = ny / ratio;
+ ny = nx / ratio;
} else {
- nx = nx * ratio;
+ nx = ny * ratio;
}
}
- TRYWAND(wand, MagickResizeImage(wand, nx, ny, LanczosFilter, 0));
+ TRYWAND(wand, MagickResizeImage(wand, nx, ny, GaussianFilter, 0));
}
TRYWAND(wand, MagickWriteImage(wand, dst));
setdatetime(dst, srcmtim);
out:
- log_printf(LOG_DETAIL, " done.\n");
return true;
magick_fail:
return false;
@@ -83,6 +128,7 @@ images_walk(struct bstnode *node, void *data)
struct site *site = data;
struct image *image = node->value;
struct stat dstat;
+ struct timespec ddate = { .tv_sec = image->tstamp, .tv_nsec = 0 };
char htmlpath[PATH_MAX];
const char *base = rbasename(image->dst);
@@ -108,7 +154,54 @@ images_walk(struct bstnode *node, void *data)
joinpathb(htmlpath, image->dst, index_html);
hashmap_insert(image->album->preserved, base, (char *)base);
- return render_make_image(&site->render, htmlpath, image);
+
+ int isupdate = file_is_uptodate(htmlpath, &site->render.modtime);
+ if (isupdate == -1) return false;
+ if (isupdate == 0) {
+ if (!render_make_image(&site->render, htmlpath, image)) {
+ return false;
+ }
+ image->modified = true;
+ image->album->images_updated++;
+ /* Check if previous image wasn't updated, if so, render it */
+ struct bstnode *prev = bstree_predecessor(node);
+ if (prev) {
+ struct image *iprev = prev->value;
+ if (!iprev->modified) {
+ joinpathb(htmlpath, iprev->dst, index_html);
+ if (!render_make_image(&site->render, htmlpath, iprev)) {
+ return false;
+ }
+ goto success;
+ }
+ }
+
+ goto success;
+ }
+
+ /*
+ * Render anyway if next image doesn't exist yet in directory or if previous
+ * image was updated.
+ */
+ struct bstnode *next = bstree_successor(node), *prev = NULL;
+ if (next) {
+ struct image *inext = next->value;
+ if (access(inext->dst, F_OK) != 0) {
+ image->album->images_updated++;
+ return render_make_image(&site->render, htmlpath, image);
+ }
+ }
+ if ((prev = bstree_predecessor(node)) != NULL) {
+ struct image *iprev = prev->value;
+ if (iprev->modified) {
+ image->album->images_updated++;
+ return render_make_image(&site->render, htmlpath, image);
+ }
+ }
+
+success:
+ setdatetime(image->dst, &ddate);
+ return true;
magick_fail:
return false;
}
@@ -124,11 +217,8 @@ albums_walk(struct bstnode *node, void *data)
if (!site->dry_run) {
hashmap_insert(site->album_dirs, album->slug, (char *)album->slug);
if (!render_set_album_vars(&site->render, album)) return false;
- }
- char htmlpath[PATH_MAX];
- joinpathb(htmlpath, album->slug, index_html);
- if (!render_make_album(&site->render, htmlpath, album)) return false;
+ }
log_printl(LOG_DEBUG, "Album: %s, datetime %s", album->slug, album->datestr);
if (!bstree_inorder_walk(album->images->root, images_walk, site)) {
@@ -136,11 +226,23 @@ albums_walk(struct bstnode *node, void *data)
}
hashmap_insert(album->preserved, index_html, (char *)index_html);
- if (rmextra(album->slug, album->preserved) < 0) {
+ ssize_t deleted = rmextra(album->slug, album->preserved, prerm_imagedir,
+ album, site->dry_run);
+ if (deleted < 0) {
log_printl_errno(LOG_ERROR,
"Something happened while deleting extraneous files");
+ } else {
+ album->images_updated += deleted;
+ }
+ if (album->images_updated > 0) {
+ site->render.albums_updated++;
}
+ char htmlpath[PATH_MAX];
+ joinpathb(htmlpath, album->slug, index_html);
+ if (!render_make_album(&site->render, htmlpath, album)) return false;
+
+
return true;
}
@@ -163,7 +265,7 @@ traverse(struct site *site, const char *path, struct stat *dstat)
}
struct dirent *ent;
struct album_config *album_conf = calloc(1, sizeof *album_conf);
- struct album *album = album_new(album_conf, site->config, path,
+ struct album *album = album_new(album_conf, site, path,
path + site->rel_content_dir, dstat);
if (album == NULL) {
closedir(dir);
@@ -240,12 +342,13 @@ site_build(struct site *site)
log_printl_errno(LOG_FATAL, "Couldn't read static dir");
return false;
}
- if (rmextra(site->output_dir, site->album_dirs) < 0) {
+ if (rmextra(site->output_dir, site->album_dirs, NULL, NULL,
+ site->dry_run) < 0) {
log_printl_errno(LOG_ERROR,
"Something happened while deleting extraneous files");
}
- } else if (!site->dry_run
- && !filesync(staticp, site->output_dir, site->album_dirs)) {
+ } else if (!filesync(staticp, site->output_dir, site->album_dirs,
+ site->dry_run)) {
log_printl(LOG_FATAL, "Can't copy static files");
return false;
}