From 8e07de9d952f8f27273296f1b6ae58550ce1e98b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaroslav=20de=20la=20Pe=C3=B1a=20Smirnov?= Date: Mon, 29 Nov 2021 19:53:34 +0300 Subject: Update adjacent images to inserted/removed Now the program checks if the html of the next and previous images was updated if a new image was inserted or an old image removed, and updates them if needed. Also make rmextra and filesync respect dry run mode. --- README.md | 5 +- include/components.h | 6 ++- include/fs.h | 9 ++-- include/site.h | 1 + src/components.c | 32 +++++++++--- src/fs.c | 37 ++++++++------ src/render.c | 64 ++++++++++++++---------- src/site.c | 137 ++++++++++++++++++++++++++++++++++++++++++++------- 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); } @@ -122,6 +122,18 @@ out: strftime(image->datestr, 24, "%Y-%m-%d %H:%M:%S", &date); } +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) { @@ -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; } -- cgit v1.2.3