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/components.c | |
download | revela-e3a41da5a0a3d70ac53591f2b66144f2be2b3871.tar.gz revela-e3a41da5a0a3d70ac53591f2b66144f2be2b3871.zip |
Initial commit.
Almost functional but still missing features and lacking testing.
Diffstat (limited to 'src/components.c')
-rw-r--r-- | src/components.c | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/src/components.c b/src/components.c new file mode 100644 index 0000000..082eab9 --- /dev/null +++ b/src/components.c @@ -0,0 +1,239 @@ +#include "components.h" + +#include <stdio.h> +#include <ctype.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> + +#include "fs.h" +#include "log.h" +#include "site.h" + +#define MAXTIME \ + ((unsigned long long)1 << ((sizeof(time_t) * CHAR_BIT) - 1)) - 1 + +#define TSTAMP_CMP(T, a, b) \ + (((T *)a)->tstamp > ((T *)b)->tstamp) - (((T *)a)->tstamp < ((T *)b)->tstamp) + +/* + * Reverses the order of the parent directories, changes '/' and spaces to '-', + * lowers the case of letters. E.g. "2019/Spain/Santiago de Compostela" -> + * "santiago-de-compostela-spain-2019". Returns pointer to slug inside of url. + */ +static const char * +slugify(const char *path, const char *base_url, char **url) +{ + size_t baselen = strlen(base_url), + pathlen, totlen; + + if (!strcmp(path, CONTENTDIR)) { + pathlen = strlen(DEFAULTALBUM); + totlen = baselen + pathlen + 2; + *url = malloc(totlen); + strncpy(*url, base_url, baselen); + (*url)[baselen] = '/'; + strncpy(*url + baselen + 1, DEFAULTALBUM, pathlen + 1); + + return *url + baselen + 1; + } + + path += strlen(CONTENTDIR) + 1; + pathlen = strlen(path); + totlen = baselen + pathlen + 2; + const char *start = path; + const char *end = strchr(start, '/'); + *url = malloc(totlen); + strncpy(*url, base_url, baselen); + (*url)[baselen] = '/'; + char *slug = *url + baselen + 1; + size_t offset = pathlen; + while (end != NULL) { + offset -= end - start; + memcpy(slug + offset, start, end - start); + slug[--offset] = '-'; + start = end + 1; + end = strchr(start, '/'); + } + memcpy(slug, start, path + pathlen - start); + for (size_t i = 0; i < pathlen; i++) { + if (isspace(slug[i])) { + slug[i] = '-'; + continue; + } + slug[i] = tolower(slug[i]); + } + (*url)[totlen - 1] = '\0'; + return slug; +} + +static void +image_date_from_stat(struct image *image, const struct stat *pstat, + struct tm *date) +{ + image->tstamp = pstat->st_ctim.tv_sec; + localtime_r(&image->tstamp, date); +} + +/* + * If exif data is present and either the tag DateTimeOriginal or + * CreateDate/DateTimeDigitized is present, then the date and time are taken + * from either (DateTimeOriginal takes precedence). Otherwise it uses the file's + * creation time (st_ctim). + */ +static void +image_set_date(struct image *image, const struct stat *pstat) +{ + struct tm date = {0}; + + if (image->exif_data == NULL) { + log_printl(LOG_DEBUG, "No exif data present in %s", image->source); + log_printl(LOG_DEBUG, "Using date from stat for file %s", image->source); + image_date_from_stat(image, pstat, &date); + goto out; + } + + ExifEntry *entry = exif_content_get_entry( + image->exif_data->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_ORIGINAL); + if (entry == NULL) { + entry = exif_content_get_entry( + image->exif_data->ifd[EXIF_IFD_EXIF], EXIF_TAG_DATE_TIME_DIGITIZED); + if (entry == NULL) { + log_printl(LOG_DEBUG, "No date exif tags present in %s", + image->source); + log_printl(LOG_DEBUG, "Using date from stat for file %s", + image->source); + image_date_from_stat(image, pstat, &date); + goto out; + } + } + + char buf[32]; + exif_entry_get_value(entry, buf, 32); + if (strptime(buf, "%Y:%m:%d %H:%M:%S", &date) == NULL) { + image_date_from_stat(image, pstat, &date); + goto out; + } + image->tstamp = mktime(&date); + +out: + /* TODO: honor user's locale and/or give an option to set the date format */ + strftime(image->datestr, 24, "%Y-%m-%d %H:%M:%S", &date); +} + +struct image * +image_new(char *src, const struct stat *pstat, struct album *album) +{ + struct image *image = calloc(1, sizeof *image); + if (image == NULL) { + log_printl_errno(LOG_FATAL, "Memory allocation error"); + return NULL; + } + char noext[NAME_MAX + 1]; + + image->album = album; + image->source = src; + image->basename = rbasename(src); + + if ((image->ext = delext(image->basename, noext, NAME_MAX + 1)) == NULL) { + log_printl(LOG_FATAL, "Can't read %s, file name too long", + image->basename); + free(image); + return NULL; + } + + size_t relstart = album->slug - album->url; + image->url = joinpath(album->url, noext); + image->url_image = joinpath(image->url, image->basename); + image->url_thumb = malloc(strlen(image->url) + strlen(PHOTO_THUMB_SUFFIX) + + strlen(image->basename) + 2); + image->dst = image->url + relstart; + image->dst_image = image->url_image + relstart; + image->dst_thumb = image->url_thumb + relstart; + sprintf(image->url_thumb, "%s/%s" PHOTO_THUMB_SUFFIX "%s", image->url, + noext, image->ext); + + image->exif_data = exif_data_new_from_file(image->source); + image->modtime = pstat->st_mtim; + image_set_date(image, pstat); + image->map = hashmap_new_with_cap(8); + image->thumb = hashmap_new_with_cap(4); + + return image; +} + +int +image_cmp(const void *a, const void *b) +{ + return TSTAMP_CMP(struct image, a, b); +} + +void +image_destroy(void *data) +{ + struct image *image = data; + free(image->source); + free(image->url); + free(image->url_image); + free(image->url_thumb); + if (image->exif_data) { + exif_data_unref(image->exif_data); + } + 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, + const char *rsrc, const struct stat *dstat) +{ + struct album *album = calloc(1, sizeof *album); + if (album == NULL) { + log_printl_errno(LOG_FATAL, "Memory allocation error"); + return NULL; + } + album->config = conf; + album->source = strdup(src); + album->slug = slugify(rsrc, sconf->base_url, &album->url); + album->images = bstree_new(image_cmp, image_destroy); + album->tstamp = MAXTIME; + album->map = hashmap_new_with_cap(8); + album->thumbs = vector_new(128); + album->previews = vector_new(sconf->max_previews); + + return album; +} + +int +album_cmp(const void *a, const void *b) +{ + return TSTAMP_CMP(struct album, a, b); +} + +void +album_add_image(struct album *album, struct image *image) +{ + if (image->tstamp < album->tstamp) { + album->tstamp = image->tstamp; + album->datestr = image->datestr; + } + bstree_add(album->images, image); +} + +void +album_destroy(void *data) +{ + struct album *album = data; + if (album->config != NULL) { + album_config_destroy(album->config); + } + free(album->source); + free(album->url); + bstree_destroy(album->images); + hashmap_free(album->map); + vector_free(album->thumbs); + vector_free(album->previews); + free(album); +} |