/* Cataract - Static web photo gallery generator * Copyright (C) 2009 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. */ #include #include #include #include #include #include #include #include #include "setup.h" #include "items.h" #include "gallery-utils.h" #include "generators.h" #include "stats.h" #define DEFAULT_DATA_DIR_MODE S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH #define AUTH_PASSWD_FILE ".htpasswd" typedef struct { TGallerySetup *setup; TAlbum *items; GList *job_items; TPathInfo *path_info; gboolean force_update; int total_items; } TJob; typedef struct { TIndexItem *item; gboolean gen_done; int index; /* processed image index */ int real_index; /* absolute index in the list */ } TJobItem; static void mirror_files (TGallerySetup *setup, gchar **files, const gchar *src_tree, const gchar *dst_dir, const gchar *label) { gchar **extra; gchar *s1, *s2, *s3; int processed = 0; if (files && g_strv_length (files) > 0) { extra = files; while (*extra) { s1 = g_strstrip (*extra); if (strlen (s1) > 0) { /* First create target directory if it doesn't exist */ s2 = g_build_filename (dst_dir, s1, NULL); s3 = g_path_get_dirname (s2); g_free (s2); if (g_mkdir_with_parents (s3, DEFAULT_DATA_DIR_MODE)) { log_error ("error making target extra directory '%s': %s\n", s3, strerror (errno)); g_free (s3); continue; } g_free (s3); /* Copy the file */ s2 = g_build_filename (src_tree, s1, NULL); s3 = g_build_filename (dst_dir, s1, NULL); if (! setup->update_mode || needs_update (s2, s3)) { if (setup->verbose) { if (processed == 0) printf ("%s", label); printf (" %s", s1); } copy_file (s2, s3); processed++; } g_free (s2); g_free (s3); } extra++; } if (setup->verbose && processed > 0) printf ("\n"); } } G_LOCK_DEFINE (job_items); G_LOCK_DEFINE (items_print); /* run in a cycle, returns when all completed */ static gpointer thread_func (gpointer data) { TIndexItem *item; gchar *imgname; gchar *s1, *s2, *s3; TJob *job = data; gboolean needs_update; GList *l; TJobItem *job_item; TGalleryDesignTheme *theme; do { item = NULL; job_item = NULL; /* Find unprocessed item */ G_LOCK (job_items); if (job->job_items != NULL) for (l = job->job_items; l != NULL; l = g_list_next (l)) { job_item = l->data; if (job_item->gen_done == FALSE) { job_item->gen_done = TRUE; item = job_item->item; break; } } G_UNLOCK (job_items); /* actually do some work */ if (item != NULL && job_item != NULL) { imgname = GET_ITEM_TARGET_FILENAME (item); /* Two-pass check whether images need to be updated. First check does no I/O except of stat() calls. */ needs_update = job->force_update; if (! needs_update) needs_update = generate_image (job->setup, job->items, item, job_item->real_index, job->path_info, TRUE); if (needs_update) generate_image (job->setup, job->items, item, job_item->real_index, job->path_info, FALSE); if (needs_update && job->setup->verbose) { G_LOCK (items_print); g_print (" [%d/%d] Processing item \"%s\"\n", job_item->index, job->total_items, imgname); G_UNLOCK (items_print); } if (needs_update && job->items->type == GALLERY_TYPE_ALBUM) for (l = g_list_first (job->setup->design->themes); l; l = g_list_next (l)) { theme = l->data; /* Do not generate particular image pages when theme doesn't define it */ if (theme->enabled && theme->picture_template && theme->picture_filename) { s1 = g_build_filename (job->path_info->templates_root, theme->picture_template, NULL); s2 = g_strdup_printf (theme->picture_filename, imgname); s3 = g_build_filename (job->path_info->dest_dir, s2, NULL); write_html_image (job->setup, job->path_info, theme, s1, s3, item, job->items); g_free (s1); g_free (s2); g_free (s3); } } g_free (imgname); } } while (item != NULL); return NULL; } /* * build_tree: generate complete gallery tree based on source xml files * * src_tree = source directory of the items * dst_dir = destination of the generated items * parent_index = parent album to determine our descent in the tree * */ gboolean build_tree (TGallerySetup *setup, TPathInfo *path_info, TAlbum *parent_index, int parent_item_index, int jobs) { gchar *idx_file; TAlbum *items; TIndexItem *item; gchar *s1, *s2, *s3, *s4; int i; TJob *job; GError *error; GThread *thread; GList *thread_list; gboolean force_update; TJobItem *job_item; GList *job_items; GList *l; TPathInfo *child_path_info; TGalleryDesignTheme *theme; TImageSize *image_size; printf ("Processing directory \"%s\"\n", path_info->src_dir); stats_dirs_inc (); /* Check access permissions */ if (access (path_info->src_dir, R_OK)) { log_error ("error accessing source directory: %s\n", strerror (errno)); return FALSE; } if (access (path_info->dest_dir, R_OK | W_OK | X_OK)) { if (g_mkdir_with_parents (path_info->dest_dir, DEFAULT_DATA_DIR_MODE)) { log_error ("error creating destination directory: %s\n", strerror (errno)); return FALSE; } } /* Check the index file */ idx_file = g_build_filename (path_info->src_dir, "index.xml", NULL); if (access (idx_file, R_OK)) { log_error ("error accessing index file '%s': %s\n", idx_file, strerror (errno)); g_free (idx_file); return FALSE; } /* Read the index file and fill items array */ items = parse_album_xml (idx_file, path_info); if (! items) { log_error ("error reading index file '%s'\n", idx_file); g_free (idx_file); return FALSE; } items->parent_index = parent_index; items->parent_item_index = parent_item_index; /* Copy support files */ if (! setup->supplemental_files_use_common_root || parent_index == NULL) { /* copy only if we're in root level or old-style is active */ mirror_files (setup, setup->design->supplemental_files, path_info->templates_root, path_info->dest_dir, " Copying global theme supplemental files: "); for (l = g_list_first (setup->design->themes); l; l = g_list_next (l)) { theme = l->data; g_assert (theme != NULL); if (theme->enabled) mirror_files (setup, theme->supplemental_files, path_info->templates_root, path_info->dest_dir, " Copying theme supplemental files: "); } /* favicon */ if (setup->favicon_file && strlen (setup->favicon_file) > 0) { s3 = g_path_get_dirname (setup->setup_xml_path); s1 = g_build_filename (s3, setup->favicon_file, NULL); s2 = g_build_filename (path_info->dest_dir, setup->favicon_file, NULL); if (! setup->update_mode || needs_update (s1, s2)) { if (setup->verbose) printf (" Copying favicon: %s\n", setup->favicon_file); copy_file (s1, s2); } g_free (s1); g_free (s2); g_free (s3); } } /* Cleanup for auth */ if (items->auth_type != AUTH_TYPE_NONE) { /* We're appending by default to preserve user .htaccess file supplied as an extra file. * Let's remove the target file to prevent endless appending when overwriting the dst structure. */ s1 = g_build_filename (path_info->dest_dir, ".htaccess", NULL); unlink (s1); g_free (s1); } /* Copy extra files */ mirror_files (setup, items->extra_files, path_info->src_dir, path_info->dest_dir, " Copying extra files: "); /* Write auth files */ if (items->auth_type != AUTH_TYPE_NONE) { if (setup->verbose) printf (" Writing auth files: "); s1 = g_build_filename (path_info->dest_dir, AUTH_PASSWD_FILE, NULL); if (write_auth_passwd_file (setup, s1, items) && setup->verbose) printf ("%s ", AUTH_PASSWD_FILE); g_free (s1); s1 = g_build_filename (path_info->dest_dir, ".htaccess", NULL); s2 = g_build_filename (setup->location_local_path, path_info->album_path, AUTH_PASSWD_FILE, NULL); if (write_auth_htaccess_file (setup, s1, s2, items) && setup->verbose) printf ("%s ", ".htaccess"); g_free (s2); g_free (s1); if (setup->verbose) printf ("\n"); } /* Prepare target image directories */ for (l = g_list_first (setup->design->image_sizes); l; l = g_list_next (l)) { image_size = l->data; g_assert (image_size != NULL); /* We don't allow having single pictures in index pages at the moment, might change in the future */ if (items->type != GALLERY_TYPE_INDEX || g_ascii_strcasecmp (image_size->name, "thumbnail") == 0) { s1 = g_strdup_printf ("%s%s", TARGET_IMAGE_DIR_PREFIX, image_size->name); s2 = g_build_path (G_DIR_SEPARATOR_S, path_info->dest_dir, s1, NULL); g_free (s1); if (access (s2, R_OK | W_OK | X_OK)) if (g_mkdir_with_parents (s2, DEFAULT_DATA_DIR_MODE)) { log_error ("error making target image directory '%s': %s\n", s2, strerror (errno)); g_free (s2); free_album_data (items); return FALSE; } g_free (s2); } } /* Check if update of whole album/index is necessary */ force_update = ! setup->update_mode; if (! force_update) for (l = g_list_first (setup->design->themes); l; l = g_list_next (l)) { theme = l->data; g_assert (theme != NULL); if (theme->enabled) { s1 = (items->type == GALLERY_TYPE_ALBUM || ! theme->index_filename) ? theme->album_filename : theme->index_filename; s2 = g_build_filename (path_info->dest_dir, s1, NULL); force_update = needs_update (idx_file, s2); g_free (s2); } if (force_update) break; } g_free (idx_file); /* Prepare job manager structures */ job = g_malloc0 (sizeof (TJob)); job->items = items; job->setup = setup; job->path_info = path_info; job->force_update = force_update; job->total_items = 0; /* Create references to the items for job manager to store actual job state in */ job_items = NULL; if (items->items->len > 0) { for (i = 0; i < items->items->len; i++) { item = g_ptr_array_index (items->items, i); if (item == NULL) { log_error ("run_job: error getting item %d\n", i); continue; } if (item->type == INDEX_ITEM_TYPE_PICTURE) { job->total_items++; job_item = g_malloc0 (sizeof (TJobItem)); job_item->item = item; job_item->gen_done = FALSE; job_item->index = job->total_items; job_item->real_index = i; job_items = g_list_append (job_items, job_item); } } } job->job_items = job_items; /* Generate images + particular html pages */ #ifdef G_THREADS_ENABLED thread_list = NULL; for (i = 0; i < jobs; i++) { error = NULL; thread = g_thread_create (thread_func, job, TRUE, &error); if (thread) thread_list = g_list_append (thread_list, thread); if (error) { log_error ("build_tree: error starting new thread: %s\n", error->message); g_clear_error (&error); } } /* wait for threads are finished */ for (l = thread_list; l != NULL; l = l->next) { g_thread_join (l->data); } /* TODO: free threads? */ g_list_free (thread_list); #else /* threads are disabled */ thread_func (job); #endif g_free (job); /* Cleanup generator objects */ g_list_foreach (job_items, (GFunc) g_free, NULL); g_list_free (job_items); /* Generate album page */ if (force_update) for (l = g_list_first (setup->design->themes); l; l = g_list_next (l)) { theme = l->data; g_assert (theme != NULL); if (theme->enabled) { if (items->type == GALLERY_TYPE_ALBUM || ! theme->index_template) { s1 = theme->album_template; s2 = theme->album_filename; } else { s1 = theme->index_template; s2 = theme->index_filename; } if (setup->verbose) printf (" Writing %s\n", s2); s3 = g_build_filename (path_info->templates_root, s1, NULL); s4 = g_build_filename (path_info->dest_dir, s2, NULL); if (! write_html_album (setup, path_info, theme, s3, s4, items)) log_error ("error generating target index file\n"); g_free (s3); g_free (s4); } } /* Recurse to sub-albums (in case of album index) */ if (items->type == GALLERY_TYPE_INDEX) { if (items->items->len > 0) { for (i = 0; i < items->items->len; i++) { item = g_ptr_array_index (items->items, i); if (item == NULL) { log_error ("build_tree: error getting item %d\n", i); continue; } if (item->type == INDEX_ITEM_TYPE_PICTURE) { child_path_info = g_malloc0 (sizeof (TPathInfo)); child_path_info->templates_root = g_strdup (path_info->templates_root); child_path_info->source_root = g_strdup (path_info->source_root); child_path_info->dest_root = g_strdup (path_info->dest_root); child_path_info->src_dir = g_build_path (G_DIR_SEPARATOR_S, path_info->src_dir, item->path, NULL); child_path_info->dest_dir = g_build_path (G_DIR_SEPARATOR_S, path_info->dest_dir, item->path, NULL); child_path_info->album_path = g_build_path (G_DIR_SEPARATOR_S, path_info->album_path, item->path, NULL); build_tree (setup, child_path_info, items, i, jobs); free_path_info (child_path_info); } } } } free_album_data (items); return TRUE; }