/* Cataract - Static web photo gallery generator * Copyright (C) 2008 Tomas Bzatek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define _XOPEN_SOURCE 600 #include #include #include #include #include #include #include #include #include "items.h" #include "xml-parser.h" #include "gallery-utils.h" #include "atom-writer.h" static int parse_timezone_string (const gchar *str) { int hour = 0; int min = 0; float f; char *endptr = NULL; /* Expecting +-hh:mm format */ if (sscanf (str, "%d:%d", &hour, &min) != 2) { /* Try simple float conversion instead */ f = strtof (str, &endptr); if (endptr == NULL || *endptr != '\0') { log_error ("Invalid timezone string \"%s\", ignoring.\n", str); return 0; } return (int)(f * 60); } return hour * 60 + min; } static time_t parse_datetime_string (const gchar *str) { struct tm tm; char *res; time_t rt; memset (&tm, 0, sizeof (struct tm)); res = strptime (str, "%Y-%m-%d %H:%M:%S", &tm); if (res == NULL || *res != '\0') { res = strptime (str, "%Y:%m:%d %H:%M:%S", &tm); if (res == NULL || *res != '\0') { res = strptime (str, "%Y-%m-%dT%H:%M:%S", &tm); if (res == NULL || res < str + 19) { log_error ("Invalid datetime string \"%s\", ignoring.\n", str); return (time_t) -1; } } } /* EXIF datetime carries no DST information while mktime() will correct * the tm_isdst field resulting in unnecessary +- 1 hour shift. */ rt = mktime (&tm); if (tm.tm_isdst) rt -= 60*60; return rt; } static TCropHint parse_thumbnail_crop_hint (const gchar *str) { g_return_val_if_fail (str != NULL, CROP_HINT_UNDEFINED); if (g_ascii_strcasecmp (str, "center") == 0) return CROP_HINT_CENTER; else if (g_ascii_strcasecmp (str, "left") == 0) return CROP_HINT_LEFT; else if (g_ascii_strcasecmp (str, "right") == 0) return CROP_HINT_RIGHT; else if (g_ascii_strcasecmp (str, "top") == 0) return CROP_HINT_TOP; else if (g_ascii_strcasecmp (str, "bottom") == 0) return CROP_HINT_BOTTOM; else return CROP_HINT_UNDEFINED; } /* * parse_album_xml: XML parser for gallery index.xml files */ TAlbum * parse_album_xml (TGallerySetup *setup, const gchar *filename, TPathInfo *path_info) { TXMLFile *xml; gchar *gallery_type; int count; int i; GList *l; gchar *s, *s2; gchar *node_name; gchar *base_dir; TIndexItem *item; TAtomFeedItem *feed_item; TAlbum *index; TImageSize *image_size; xml = xml_parser_load (filename); if (xml == NULL) return NULL; index = g_malloc0 (sizeof (TAlbum)); index->properties = properties_table_new (); /* Retrieve gallery type */ gallery_type = xml_file_get_node_attribute (xml, "/gallery", "type"); if (strcmp (gallery_type, "index") == 0) index->type = GALLERY_TYPE_INDEX; else if (strcmp (gallery_type, "album") == 0) index->type = GALLERY_TYPE_ALBUM; else { log_error ("Invalid gallery type (%s)\n", gallery_type); g_free (gallery_type); free_album_data (index); return NULL; } g_free (gallery_type); base_dir = g_path_get_dirname (filename); /* Section General */ index->ID = xml_file_get_node_value (xml, "/gallery/general/ID/text()"); index->title = xml_file_get_node_value (xml, "/gallery/general/title/text()"); index->desc = xml_file_get_node_value (xml, "/gallery/general/description/text()"); index->footnote = xml_file_get_node_value (xml, "/gallery/general/footnote/text()"); s = xml_file_get_node_value (xml, "/gallery/general/extra_files/text()"); if (s) { index->extra_files = g_strsplit (s, "\n", -1); g_free (s); } prop_xml_attr_long (index->properties, PROP_QUALITY, xml, "/gallery/general/images", "quality"); prop_xml_attr_long (index->properties, PROP_LANDSCAPE_W, xml, "/gallery/general/images", "landscape_w"); prop_xml_attr_long (index->properties, PROP_LANDSCAPE_H, xml, "/gallery/general/images", "landscape_h"); prop_xml_attr_long (index->properties, PROP_PORTRAIT_W, xml, "/gallery/general/images", "portrait_w"); prop_xml_attr_long (index->properties, PROP_PORTRAIT_H, xml, "/gallery/general/images", "portrait_h"); prop_xml_attr (index->properties, PROP_BORDER_STYLE, xml, "/gallery/general/border", "style"); index->meta_author = xml_file_get_node_value (xml, "/gallery/general/meta/author/text()"); index->meta_description = xml_file_get_node_value (xml, "/gallery/general/meta/description/text()"); index->meta_keywords = xml_file_get_node_value (xml, "/gallery/general/meta/keywords/text()"); s = xml_file_get_node_attribute (xml, "/gallery/general/metadata/timezone", "shift"); if (s != NULL) { properties_table_add_int (index->properties, PROP_METADATA_TZ_SHIFT, parse_timezone_string (s)); g_free (s); } s = xml_file_get_node_attribute (xml, "/gallery/general/metadata/override", "date"); if (s != NULL) { properties_table_add_double (index->properties, PROP_METADATA_OVERRIDE_DATETIME, parse_datetime_string (s)); g_free (s); } prop_xml_attr_double (index->properties, PROP_METADATA_OVERRIDE_APERTURE, xml, "/gallery/general/metadata/override", "aperture"); prop_xml_attr_double (index->properties, PROP_METADATA_OVERRIDE_FOCAL_LENGTH, xml, "/gallery/general/metadata/override", "focal_length"); /* TODO: add support for lens name */ s = xml_file_get_node_attribute (xml, "/gallery/general/thumbnail", "crop"); if (s != NULL) { properties_table_add_int (index->properties, PROP_THUMB_CROP_HINT, parse_thumbnail_crop_hint (s)); g_free (s); } index->nofullsize = xml_file_get_node_present (xml, "/gallery/general/nofullsize"); index->fullsize = xml_file_get_node_present (xml, "/gallery/general/fullsize"); /* News records */ if (news_feed) { count = xml_file_node_get_children_count (xml, "/gallery/general/news"); for (i = 0; i < count; i++) { feed_item = atom_writer_add_item (news_feed); s = g_strdup_printf ("/gallery/general/news[%d]", i + 1); s2 = xml_file_get_node_attribute (xml, s, "title"); atom_feed_item_set_title (feed_item, s2); g_free (s2); s2 = xml_file_get_node_attribute (xml, s, "timestamp"); atom_feed_item_set_date (feed_item, s2); g_free (s2); s2 = xml_file_get_node_attribute (xml, s, "type"); atom_feed_item_set_summary_type (feed_item, s2); g_free (s2); g_free (s); s = g_strdup_printf ("/gallery/general/news[%d]/text()", i + 1); s2 = xml_file_get_node_value (xml, s); g_free (s); atom_feed_item_set_summary (feed_item, s2); g_free (s2); atom_feed_item_set_path (feed_item, path_info->album_path); } } /* Authentication */ index->auth_type = AUTH_TYPE_NONE; s = xml_file_get_node_value (xml, "/gallery/general/auth/type/text()"); if (g_strcmp0 (s, "Basic") == 0) index->auth_type = AUTH_TYPE_BASIC; g_free (s); index->auth_realm = xml_file_get_node_value (xml, "/gallery/general/auth/realm/text()"); index->auth_username = xml_file_get_node_value (xml, "/gallery/general/auth/username/text()"); index->auth_passwd = xml_file_get_node_value (xml, "/gallery/general/auth/password/text()"); if (index->auth_type != AUTH_TYPE_NONE && (index->auth_realm == NULL || index->auth_username == NULL || index->auth_passwd == NULL)) { log_error ("Authentication requested but not all information provided. Ignoring.\n"); index->auth_type = AUTH_TYPE_NONE; } /* Section Items */ count = xml_file_node_get_children_count (xml, "/gallery/items/*"); index->items = g_ptr_array_new (); for (i = 0; i < count; i++) { s = g_strdup_printf ("/gallery/items/*[%d]", i + 1); node_name = xml_file_get_node_name (xml, s); g_free (s); if (! node_name) continue; item = g_malloc0 (sizeof (TIndexItem)); item->properties = properties_table_new (); if (strcmp (node_name, "item") == 0) { item->type = INDEX_ITEM_TYPE_PICTURE; s = g_strdup_printf ("/gallery/items/*[%d]", i + 1); if (index->type == GALLERY_TYPE_INDEX) item->path = xml_file_get_node_attribute (xml, s, "path"); else item->path = xml_file_get_node_attribute (xml, s, "src"); prop_xml_attr_long (item->properties, PROP_QUALITY, xml, s, "quality"); prop_xml_attr_long (item->properties, PROP_WIDTH, xml, s, "width"); prop_xml_attr_long (item->properties, PROP_HEIGHT, xml, s, "height"); prop_xml_attr (item->properties, PROP_BORDER_STYLE, xml, s, "border"); /* custom image size attributes */ for (l = g_list_first (setup->design->image_sizes); l; l = g_list_next (l)) { image_size = l->data; g_assert (image_size != NULL); s2 = xml_file_get_node_attribute (xml, s, image_size->tagname ? image_size->tagname : image_size->name); if (s2) { if (item->image_sizes == NULL) item->image_sizes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); g_assert (g_hash_table_insert (item->image_sizes, g_strdup (image_size->tagname ? image_size->tagname : image_size->name), s2)); } } g_free (s); s = g_strdup_printf ("/gallery/items/*[%d]/title/text()", i + 1); item->title = xml_file_get_node_value (xml, s); g_free (s); s = g_strdup_printf ("/gallery/items/*[%d]/title_description/text()", i + 1); item->title_description = xml_file_get_node_value (xml, s); g_free (s); if (index->type == GALLERY_TYPE_INDEX) { s = g_strdup_printf ("/gallery/items/*[%d]/thumbnail", i + 1); item->thumbnail = xml_file_get_node_attribute (xml, s, "src"); g_free (s); } s = g_strdup_printf ("/gallery/items/*[%d]/nofullsize", i + 1); item->force_nofullsize = (xml_file_get_node_present (xml, s) || item->path == NULL); g_free (s); s = g_strdup_printf ("/gallery/items/*[%d]/fullsize", i + 1); item->force_fullsize = xml_file_get_node_present (xml, s); g_free (s); s = g_strdup_printf ("/gallery/items/*[%d]/hidden", i + 1); item->hidden = (xml_file_get_node_present (xml, s)); g_free (s); /* Retrieve title and description from linked album if not defined here */ if (index->type == GALLERY_TYPE_INDEX && item->title == NULL && item->title_description == NULL) { s = g_build_filename (base_dir, item->path, "index.xml", NULL); get_album_titles (s, &item->title, &item->title_description, NULL); g_free (s); } /* Retrieve thumbnail from linked album if not defined here */ if (index->type == GALLERY_TYPE_INDEX && item->thumbnail == NULL) { s = g_build_filename (base_dir, item->path, "index.xml", NULL); s2 = NULL; get_album_titles (s, NULL, NULL, &s2); if (s2) { item->thumbnail = g_build_filename (item->path, s2, NULL); g_free (s2); } g_free (s); } s = g_strdup_printf ("/gallery/items/*[%d]/metadata/external_exif", i + 1); item->metadata_external_exif = xml_file_get_node_attribute (xml, s, "src"); g_free (s); s = g_strdup_printf ("/gallery/items/*[%d]/metadata/timezone", i + 1); s2 = xml_file_get_node_attribute (xml, s, "shift"); g_free (s); if (s2 != NULL) { properties_table_add_int (item->properties, PROP_METADATA_TZ_SHIFT, parse_timezone_string (s2)); g_free (s2); } s = g_strdup_printf ("/gallery/items/*[%d]/metadata/override", i + 1); s2 = xml_file_get_node_attribute (xml, s, "date"); if (s2 != NULL) { properties_table_add_double (item->properties, PROP_METADATA_OVERRIDE_DATETIME, parse_datetime_string (s2)); g_free (s2); } prop_xml_attr_double (item->properties, PROP_METADATA_OVERRIDE_APERTURE, xml, s, "aperture"); prop_xml_attr_double (item->properties, PROP_METADATA_OVERRIDE_FOCAL_LENGTH, xml, s, "focal_length"); /* TODO: add support for lens name */ g_free (s); s = g_strdup_printf ("/gallery/items/*[%d]/thumbnail", i + 1); s2 = xml_file_get_node_attribute (xml, s, "crop"); g_free (s); if (s2 != NULL) { properties_table_add_int (item->properties, PROP_THUMB_CROP_HINT, parse_thumbnail_crop_hint (s2)); g_free (s2); } if (item->path || item->image_sizes) { g_ptr_array_add (index->items, item); } else { log_error ("%s: No image src specified (title = '%s'), skipping!\n", filename, item->title); free_index_item (item); } } else if (strcmp (node_name, "separator") == 0) { item->type = INDEX_ITEM_TYPE_SEPARATOR; s = g_strdup_printf ("/gallery/items/*[%d]/text()", i + 1); item->title = xml_file_get_node_value (xml, s); g_free (s); g_ptr_array_add (index->items, item); } else if (strcmp (node_name, "interspace") == 0) { item->type = INDEX_ITEM_TYPE_INTERSPACE; s = g_strdup_printf ("/gallery/items/*[%d]/text()", i + 1); item->title = xml_file_get_node_value (xml, s); g_free (s); g_ptr_array_add (index->items, item); } else { /* Free the item if nobody cares */ g_free (item); } g_free (node_name); } xml_parser_free (xml); g_free (base_dir); return index; } /* * dup_index_item: duplicates the item structure or returns NULL if item == NULL */ TIndexItem * dup_index_item (TIndexItem *item) { TIndexItem *i; if (item == NULL) return NULL; i = g_malloc0 (sizeof (TIndexItem)); memcpy (i, item, sizeof (TIndexItem)); i->path = g_strdup (item->path); i->title = g_strdup (item->title); i->title_description = g_strdup (item->title_description); i->thumbnail = g_strdup (item->thumbnail); i->metadata_external_exif = g_strdup (item->metadata_external_exif); i->properties = properties_table_dup (item->properties); if (item->image_sizes) i->image_sizes = clone_string_hash_table (item->image_sizes); return i; } /* * free_index_item: frees all memory used by item */ void free_index_item (TIndexItem *item) { if (item != NULL) { g_free (item->path); g_free (item->title); g_free (item->title_description); g_free (item->thumbnail); g_free (item->metadata_external_exif); properties_table_free (item->properties); if (item->image_sizes) g_hash_table_destroy (item->image_sizes); g_free (item); } } /* * free_album_data: free allocated album data */ void free_album_data (TAlbum *album) { if (album) { g_free (album->ID); g_free (album->title); g_free (album->desc); g_free (album->footnote); g_free (album->meta_author); g_free (album->meta_description); g_free (album->meta_keywords); g_free (album->auth_realm); g_free (album->auth_username); g_free (album->auth_passwd); g_strfreev (album->extra_files); if (album->items) { g_ptr_array_foreach (album->items, (GFunc) free_index_item, NULL); g_ptr_array_free (album->items, TRUE); } properties_table_free (album->properties); g_free (album); } } /* * get_album_info: retrieve number of items and protected status in the specified album */ void get_album_info (const gchar *filename, int *objects_count, gboolean *is_protected) { TXMLFile *xml; int count, hidden; if (objects_count) *objects_count = 0; if (is_protected) *is_protected = FALSE; xml = xml_parser_load (filename); if (xml == NULL) return; count = xml_file_node_get_children_count (xml, "/gallery/items/item"); hidden = xml_file_node_get_children_count (xml, "/gallery/items/item/hidden"); if (objects_count) *objects_count = count - hidden; if (is_protected) *is_protected = xml_file_get_node_present (xml, "/gallery/general/auth/username") && xml_file_get_node_present (xml, "/gallery/general/auth/password"); xml_parser_free (xml); } /* * get_album_titles: retrieve title, description and first thumbnail from specified album */ void get_album_titles (const gchar *filename, gchar **title, gchar **description, gchar **thumbnail) { TXMLFile *xml; xml = xml_parser_load (filename); if (xml == NULL) return; if (title) *title = xml_file_get_node_value (xml, "/gallery/general/title/text()"); if (description) *description = xml_file_get_node_value (xml, "/gallery/general/description/text()"); if (thumbnail) { *thumbnail = xml_file_get_node_attribute (xml, "/gallery/items/item[1]/thumbnail", "src"); if (! *thumbnail) *thumbnail = xml_file_get_node_attribute (xml, "/gallery/items/item[1]", "src"); if (! *thumbnail) *thumbnail = xml_file_get_node_attribute (xml, "/gallery/items/item[1]", "preview"); } xml_parser_free (xml); } /* * get_child_gallery_type: retrieve gallery type from the source XML file */ TGalleryType get_child_gallery_type (const gchar *filename) { TXMLFile *xml; gchar *gallery_type; TGalleryType result; /* fallback return value */ result = GALLERY_TYPE_INDEX; xml = xml_parser_load (filename); if (xml == NULL) return result; gallery_type = xml_file_get_node_attribute (xml, "/gallery", "type"); if (strcmp (gallery_type, "index") == 0) result = GALLERY_TYPE_INDEX; else if (strcmp (gallery_type, "album") == 0) result = GALLERY_TYPE_ALBUM; else { log_error ("Invalid gallery type (%s)\n", gallery_type); } g_free (gallery_type); xml_parser_free (xml); return result; } /* * free_path_info: free allocated pathinfo data */ void free_path_info (TPathInfo *path_info) { if (path_info) { g_free (path_info->templates_root); g_free (path_info->source_root); g_free (path_info->dest_root); g_free (path_info->src_dir); g_free (path_info->dest_dir); g_free (path_info); } } /* * get_item_target_filename: get target item filename */ gchar * get_item_target_filename (TIndexItem *item) { const gchar *s; s = item->path; if (s == NULL && item->image_sizes) s = g_hash_table_lookup (item->image_sizes, "preview"); if (s == NULL) return NULL; return g_path_get_basename (s); } /* * get_prop_*: retrieve attribute value from properties tables, with item taking priority, using items otherwise or falling back to default if not set anywhere */ gchar * get_prop_string (TAlbum *items, TIndexItem *item, PropertyName name, const gchar *_default) { const gchar *s = NULL; if (item) s = properties_table_get_string (item->properties, name); if (!s && items) s = properties_table_get_string (items->properties, name); return g_strdup (s ? s : _default); } long int get_prop_int (TAlbum *items, TIndexItem *item, PropertyName name, const long int _default) { long int i = _default; gboolean res = FALSE; if (item) res = properties_table_get_int (item->properties, name, &i); if (!res && items) res = properties_table_get_int (items->properties, name, &i); return i; } gboolean get_prop_bool (TAlbum *items, TIndexItem *item, PropertyName name, const gboolean _default) { gboolean b = _default; gboolean res = FALSE; if (item) res = properties_table_get_bool (item->properties, name, &b); if (!res && items) res = properties_table_get_bool (items->properties, name, &b); return b; } double get_prop_double (TAlbum *items, TIndexItem *item, PropertyName name, const double _default) { double d = _default; gboolean res = FALSE; if (item) res = properties_table_get_double (item->properties, name, &d); if (!res && items) res = properties_table_get_double (items->properties, name, &d); return d; }