diff options
author | Yaroslav de la Peña Smirnov <yps@yaroslavps.com> | 2021-11-07 02:02:45 +0300 |
---|---|---|
committer | Yaroslav de la Peña Smirnov <yps@yaroslavps.com> | 2021-11-07 02:02:45 +0300 |
commit | e3a41da5a0a3d70ac53591f2b66144f2be2b3871 (patch) | |
tree | 789cc69b05f2447c11f04dbb6ae972ffa0acd1c9 /src/site.c | |
download | revela-e3a41da5a0a3d70ac53591f2b66144f2be2b3871.tar.gz revela-e3a41da5a0a3d70ac53591f2b66144f2be2b3871.zip |
Initial commit.
Almost functional but still missing features and lacking testing.
Diffstat (limited to 'src/site.c')
-rw-r--r-- | src/site.c | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/src/site.c b/src/site.c new file mode 100644 index 0000000..1acdc7c --- /dev/null +++ b/src/site.c @@ -0,0 +1,269 @@ +#include "site.h" + +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <limits.h> + +#include "fs.h" +#include "log.h" +#include "hashmap.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 bool +wand_passfail(MagickWand *wand, MagickPassFail status) +{ + if (status != MagickPass) { + char *desc; + ExceptionType severity; + desc = MagickGetException(wand, &severity); + log_printl(LOG_FATAL, "GraphicsMagick error: %.1024s severity: %d\n", + desc, severity); + return false; + } + + return true; +} + +#define TRYWAND(w, f) if (!wand_passfail(w, f)) goto magick_fail + +static bool +optimize_image(MagickWand *wand, const char *dst, + const struct image_config *conf, + const struct timespec *srcmtim, bool dry) +{ + int update = file_is_uptodate(dst, srcmtim); + if (update == -1) return false; + if (update == 1) return true; + + log_print(LOG_DETAIL, "Converting %s...", dst); + if (dry) goto out; + + unsigned long nx = conf->max_width, ny = conf->max_height; + if (conf->strip) { + TRYWAND(wand, MagickStripImage(wand)); + } + TRYWAND(wand, MagickSetCompressionQuality(wand, conf->quality)); + unsigned long x = MagickGetImageWidth(wand), + y = MagickGetImageHeight(wand); + /* Resize only if the image is actually bigger. No point in making small + * images bigger. */ + if (x > nx || y > ny) { + if (conf->smart_resize) { + double ratio = (double)x / y; + if (x > y) { + ny = ny / ratio; + } else { + nx = nx * ratio; + } + } + TRYWAND(wand, MagickResizeImage(wand, nx, ny, LanczosFilter, 0)); + } + TRYWAND(wand, MagickWriteImage(wand, dst)); + setdatetime(dst, srcmtim); + +out: + log_printf(LOG_DETAIL, " done.\n"); + return true; +magick_fail: + return false; +} + +static bool +images_walk(struct bstnode *node, void *data) +{ + struct site *site = data; + struct image *image = node->value; + struct stat dstat; + + log_printl(LOG_DEBUG, "Image: %s, datetime %s", image->basename, + image->datestr); + + if (!nmkdir(image->dst, &dstat, site->dry_run)) return false; + + if (!site->dry_run) { + TRYWAND(site->wand, MagickReadImage(site->wand, image->source)); + } + if (!optimize_image(site->wand, image->dst_image, &site->config->images, + &image->modtime, site->dry_run)) { + goto magick_fail; + } + if (!optimize_image(site->wand, image->dst_thumb, &site->config->thumbnails, + &image->modtime, site->dry_run)) { + goto magick_fail; + } + if (!site->dry_run) { + MagickRemoveImage(site->wand); + } + + char htmlpath[PATH_MAX]; + joinpathb(htmlpath, image->dst, "index.html"); + return render_make_image(&site->render, htmlpath, image); +magick_fail: + return false; +} + +static bool +albums_walk(struct bstnode *node, void *data) +{ + struct site *site = data; + struct album *album = node->value; + struct stat dstat; + if (!nmkdir(album->slug, &dstat, site->dry_run)) 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); + return bstree_inorder_walk(album->images->root, images_walk, site); +} + +/* + * Recursively traverse the content directory. If there are images in the + * directory, "create" an album. If an album.ini was found, then the title and + * description in that file are used. Otherwise, the date of the album is used + * as its title. If the images are in the root of the content directory, then a + * special "unorganized" album will be created. The title and description will + * be used, but the slug will always be "unorganized". + */ +static bool +traverse(struct site *site, const char *path, struct stat *dstat) +{ + bool ok = true; + DIR *dir = opendir(path); + if (!dir) { + log_printl_errno(LOG_FATAL, "Can't open directory %s", path); + return false; + } + struct dirent *ent; + struct album_config *album_conf = calloc(1, sizeof *album_conf); + struct album *album = album_new(album_conf, site->config, path, + path + site->rel_content_dir, dstat); + if (album == NULL) { + closedir(dir); + return false; + } + while ((ent = readdir(dir))) { + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) { + continue; + } + + struct stat fstats; + char *subpath = joinpath(path, ent->d_name); + if (stat(subpath, &fstats)) { + log_printl_errno(LOG_FATAL, "Can't read %s", subpath); + ok = false; + goto fail; + } + + if (S_ISDIR(fstats.st_mode)) { + ok = traverse(site, subpath, &fstats); + if (!ok) goto fail; + } else if (!strcmp(ent->d_name, ALBUM_CONF)) { + ok = album_config_read_ini(subpath, album_conf); + if (!ok) goto fail; + } else if (isimage(subpath)) { + struct image *image = image_new(subpath, &fstats, album); + if (image == NULL) { + free(subpath); + goto fail; + } + album_add_image(album, image); + continue; + } + free(subpath); + } + + if (album->images->root != NULL) { + bstree_add(site->albums, album); + closedir(dir); + return true; + } + +fail: + album_destroy(album); + closedir(dir); + return ok; +} + +bool +site_build(struct site *site) +{ + struct stat dstat; + if (!nmkdir(site->output_dir, &dstat, false)) return false; + + if (chdir(site->output_dir)) { + log_printl_errno(LOG_FATAL, "Can't change to directory %s", + site->output_dir); + return false; + } + + if (!render_make_index(&site->render, "index.html")) return false; + + if (!bstree_inorder_walk(site->albums->root, albums_walk, (void *)site)) { + return false; + } + /* TODO: static files and css */ + chdir(site->root_dir); + return true; +} + +bool +site_load(struct site *site) +{ + struct stat cstat; + if (stat(site->content_dir, &cstat)) { + log_printl_errno(LOG_FATAL, "Can't read %s", site->content_dir); + return false; + } + + if (!traverse(site, site->content_dir, &cstat)) return false; + + return render_init(&site->render, site->root_dir, site->config, site->albums); +} + +bool +site_init(struct site *site) +{ + site->config = site_config_init(); + if (!site_config_read_ini(site->root_dir, site->config)) return false; + site->albums = bstree_new(album_cmp, album_destroy); + + if (site->root_dir == NULL) { + site->root_dir = malloc(PATH_MAX); + if (getcwd(site->root_dir, PATH_MAX) == NULL) { + log_printl_errno(LOG_FATAL, "Couldn't get working directory"); + return false; + } + } + + site->content_dir = joinpath(site->root_dir, CONTENTDIR); + site->rel_content_dir = strlen(site->root_dir) + 1; + InitializeMagick(NULL); + site->wand = NewMagickWand(); + site->render.dry_run = site->dry_run; + + return true; +} + +void +site_deinit(struct site *site) +{ + if (site->albums) bstree_destroy(site->albums); + site_config_destroy(site->config); + free(site->content_dir); + free(site->root_dir); + if (site->wand != NULL) { + DestroyMagickWand(site->wand); + DestroyMagick(); + } + if (!site->dry_run) { + render_deinit(&site->render); + } +} |