/* * kissdx - KiSS PC-Link Daemon eXtended (based on kissd) * * This file is Copyright (C) 2006 Vidar Tysse * Portions Copyright (C) 2007 Olivier Kahn * This file is Public domain. * * ************************************************************************ * * piccache.c * * Purpose: Manage a cache of downscaled picture files on disk. * * Description: See specification in DevDocs/piccache-specification.txt * * ************************************************************************ */ // We use ftw() by default. // On platforms that don't have it we must patch this file to remove the #define below. // #define USE_FTW #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_FTW #include // file tree traversal /* * The header defines the stat structure and the symbolic names * for st_mode and the file type test macros as described in .*/ #endif #include "kissdx.h" #include "config.h" #include "utils.h" #include "piccache.h" #pragma pack(4) typedef struct cache_info_s { int struct_version; int struct_size; time_t last_sizeenforcement_time; } cache_info_t; typedef struct cached_picture_info_s { int struct_version; // Version of this struct int struct_size; // Size of this struct char target_filename[PATH_MAX]; // Name of original image file char cached_filename[PATH_MAX]; // Name of image data file in cache int cached_image_max_width; // Max width config setting int cached_image_max_height; // Max height config setting int cached_image_max_zoom_percent; // Max zoom config setting time_t resized_time; // GMT time when picture was put in cache time_t target_file_modified_time; // GMT last modified time of original file } cached_picture_info_t; typedef struct precache_status_s { int struct_version; int struct_size; time_t started_time; char target_filename[PATH_MAX]; } precache_status_t; #pragma pack() static int cache_info_size = sizeof (cache_info_t); static int cached_picture_info_size = sizeof (cached_picture_info_t); static int precache_status_size = sizeof (precache_status_t); static int cache_version = 2; // (.h) #define INFOFILE_EXT "info" const char *infofile_dot_ext = "."INFOFILE_EXT; const char *infofile_pattern = "*."INFOFILE_EXT; // (.h) #define UNIQUE_FILENAME_TEMPLATE_PREFIX "pic" const char *unique_filename_template = UNIQUE_FILENAME_TEMPLATE_PREFIX"XXXXXX"; static char cache_rootdir[PATH_MAX] = ""; static char cache_info_filename[PATH_MAX] = ""; static char precaching_status_filename[PATH_MAX] = ""; static int precache_wait_timeout = 20; // Max 20 seconds // ********************************************************************** static int write_cache_info_file(const cache_info_t *cache_info_p) { int fd, chars_written; if ((fd = open(cache_info_filename, O_CREAT | O_WRONLY | O_TRUNC, standard_filemode)) < 0) { loglevel(LOGERROR,"Error: write_cache_info_file: cannot create cache info file %s: %s", cache_info_filename, strerror(errno)); return -1; } if (fchmod(fd, standard_filemode) < 0) loglevel(LOGERROR,"Error: write_cache_info_file: cannot fchmod cache info file %s: %s", cache_info_filename, strerror(errno)); if ((chars_written = write(fd, cache_info_p, cache_info_size)) < cache_info_size) loglevel(LOGERROR,"Error: write_cache_info_file: cannot write %d chars to cache info file %s: %s", cache_info_size, cache_info_filename, strerror(errno)); close(fd); return chars_written != cache_info_size; } // ********************************************************************** static int read_cache_info_file(cache_info_t *cache_info_p) { int fd, chars_read; cache_info_t cache_info; if ((fd = open(cache_info_filename, O_RDONLY)) < 0) { loglevel(LOGDEBUG,"Debug: read_cache_info_file: cannot open cache info file %s: %s", cache_info_filename, strerror(errno)); return -1; } if ((chars_read = read(fd, &cache_info, cache_info_size)) < 0) loglevel(LOGERROR,"Error: read_cache_info_file: cannot read %d chars from cache info file %s: %s", cache_info_size, cache_info_filename, strerror(errno)); close(fd); if (chars_read < sizeof cache_info.struct_version || cache_info.struct_version != cache_version || chars_read != cache_info_size || chars_read != cache_info.struct_size) { // FUTURE: We could convert from older versions of the cache_info_t struct, and set chars_read = cache_info_size and fall through if successful return -1; // We were unable to convert from older version of cache_info_t struct } memcpy(cache_info_p, &cache_info, cache_info_size); return 0; } // ********************************************************************** static int initialize_cache_if_needed(void) { static int is_initialized = 0; static int is_initialized_ok = 0; cache_info_t cache_info; if (is_initialized) return !is_initialized_ok; is_initialized_ok = 1; // Optimist by default strcpy(cache_rootdir, *config.persistentstoragepath? config.persistentstoragepath : config.picturepath); strcat(cache_rootdir, "/.piccache"); if (mkdir(cache_rootdir, standard_dir_filemode) < 0) { if (errno != EEXIST) { loglevel(LOGERROR,"Error: initialize_cache_if_needed: Couldn't create directory '%s': %s", cache_rootdir, strerror(errno)); is_initialized_ok = 0; } } else if (chmod(cache_rootdir, standard_dir_filemode) < 0) { loglevel(LOGERROR,"Error: initialize_cache_if_needed: cannot chmod cache root dir '%s': %s", cache_rootdir, strerror(errno)); is_initialized_ok = 0; } strcpy(cache_info_filename, cache_rootdir); strcat(cache_info_filename, "/.cacheinfo"); strcpy(precaching_status_filename, cache_rootdir); strcat(precaching_status_filename, "/.pre-caching-status"); if (read_cache_info_file(&cache_info)) { memset(&cache_info, 0, cache_info_size); cache_info.struct_size = cache_info_size; cache_info.struct_version = cache_version; cache_info.last_sizeenforcement_time = time(NULL); if (write_cache_info_file(&cache_info)) { is_initialized_ok = 0; loglevel(LOGDEBUG,"Debug: Initialized cache vers.(%d) size(%d) file '%s'", cache_version,cache_info_size,cache_info_filename); } } is_initialized = 1; return !is_initialized_ok; } // ********************************************************************** /* Function remove_entry() should remove the directory containing a cached picture * Only one directory of the provided name is expected to exist. */ static int remove_entry(const char *cached_filename) { char fullname[PATH_MAX]; struct dirent **namelist; int num_files, n; // Sanity check strcpy(fullname, cache_rootdir); strcat(fullname, "/"); if (!cached_filename || strncmp(cached_filename, fullname, strlen(fullname))) return -1; // tips: only the unix right of the directory from which the file is to be removed strcpy(fullname, cached_filename); loglevel(LOGDEBUG,"Debug: remove_entry: remove '%s'",fullname); if ( unlink(fullname) < 0) { loglevel(LOGERROR,"Error: remove_entry: Couldn't remove %s: %s",fullname,strerror(errno)); } strcat(fullname, infofile_dot_ext); loglevel(LOGDEBUG,"Debug: remove_entry: remove '%s'",fullname); if ( unlink(fullname) < 0) { loglevel(LOGERROR,"Error: remove_entry: Couldn't remove %s: %s",fullname,strerror(errno)); } // Also delete the cache segment directory if it is now empty *(strrchr(fullname, '/')) = '\0'; // Scan content of the 'cached picture' directory : Only empty directory is candidate to removal // Careful: basename is '*' because one file possibly exists and had no extension 'ex: picTzl0Mp) setup_scandir_selector("*","",fullname,SCANDIR_FILE_MODE); // Basename - Extension - Dirname - File/Dir mode loglevel(LOGDEBUG,"Debug: remove_entry scandir prepared: pattern:'%s' ext:'%s' dirname:'%s'", scandir_info.scandir_basename_pattern,scandir_info.scandir_file_extensions,scandir_info.scandir_dirname); if ((num_files = scandir(fullname, &namelist, (void*)scandir_selector, (void*)alphasort)) < 0) { loglevel(LOGERROR,"Error: remove_entry: scandir %s: %s", fullname, strerror(errno)); return -1; } else if (num_files > 0) { // The directory is used by more than one picture loglevel(LOGDEBUG,"Debug: remove_entry: Directory is preserved, having other cached file(%d): '%s'",num_files,fullname); } else if (num_files == 0) { loglevel(LOGINFO,"Info: remove_entry cached: '%s'",fullname); if ( rmdir(fullname) < 0) { loglevel(LOGERROR,"Error: remove_entry: Couldn't remove '%s': %s",fullname,strerror(errno)); } } // Clean memory from result of scandir in case any entry was found. Only numfiles is usefull for (n = 0; n < num_files; n++) free(namelist[n]); free(namelist); return 0; } // ********************************************************************** static int trim_cache(void) { int fd = -1, no_of_files_in_cache = 0, line_size = PATH_MAX + 64; char tempfilename[PATH_MAX] = "", line[line_size]; cache_info_t cache_info; char tempfilename_sorted[PATH_MAX] = ""; char *command_pattern = "sort --key=1,1 --output='%s' '%s'"; char command[strlen(command_pattern) + 2 * PATH_MAX + 1]; int entryno, rc; FILE *fp = NULL; ftw_info.ftw_nb_of_cachedfile = 0; // global structure to configure ftw() and ftw_extract_timestamp_file() ftw_info.ftw_temp_fd = -1; strcpy(ftw_info.ftw_tempfilename,""); // Do nothing if caching is not enabled, or if a size limit is not enforced if (config.picturecachesize <= 0) return 0; if (initialize_cache_if_needed()) return -1; // Respect the cache trimming interval if not set to 0, but only if cache size limit is > 1 if (config.picturecachetrimminginterval && config.picturecachesize > 1) { if (read_cache_info_file(&cache_info)) return -1; if (difftime(time(NULL), cache_info.last_sizeenforcement_time) / 60.0 < (double)config.picturecachetrimminginterval) return 0; // It's not time yet } /* *** Perform the cache size limit enforcement *** Strategy: 1. Do a scan of all cache entries, collecting all data file names and their modification timestamp into a temp file 2. Sort the temp file on ascending file modification timestamp 3. Read the temp file, removing one cache entry for each line read, until at most (maximum number of entries - 1) remain 4. Clean-up: Delete temporary files ************************************************ */ // 1. Do a scan of all cache entries strcpy(tempfilename, cache_rootdir); strcat(tempfilename, "/tmpXXXXXX"); if ((fd = mkstemp(tempfilename)) < 0) { log("trim_cache: mkstemp for temp file failed: %s", strerror(errno)); goto cleanup; } #ifdef USE_FTW // ftw() works like scandir() with a function trigger for each entry found in directory // this function is in common tools and need a global variable to be preset as the interface is fixed. // ** Declared in utils.c (common tools)******************************************************** // int ftw_extract_timestamp_file(const char *filename, const struct stat *filestat, int fileflag) /* * The ftw function calls the callback function given in the parameter func for every item * which is found in the directory specified by filename * * Before using ftw() and ftw_extract_timestamp_file() the following variable should be set * A global structure variable: ftw_info * char ftw_tempfilename[PATH_MAX] ; // ex: /home/olivier/media/video/test/.piccache/tmpsEnNZH * int ftw_temp_fd ; // ex: file descriptor get with fopen() * int ftw_nb_of_cachedfile ; // ex: number of files found in cached directory */ strncpy(ftw_info.ftw_tempfilename,tempfilename, sizeof(ftw_info.ftw_tempfilename)); ftw_info.ftw_temp_fd = fd; if (ftw(cache_rootdir, ftw_extract_timestamp_file, 20)) { no_of_files_in_cache = ftw_info.ftw_nb_of_cachedfile; loglevel(LOGERROR,"Error: trim_cache ftw failed %s",cache_rootdir); goto cleanup; } no_of_files_in_cache = ftw_info.ftw_nb_of_cachedfile; #else struct dirent **dirnamelist; struct dirent **filenamelist; int num_dirs=0, dirno=0, num_files=0, fileno=0; char segment_dir[PATH_MAX], fullname[PATH_MAX]; struct stat statbuf; // Get all subdirectory entry in directory 'cache_rootdir' setup_scandir_selector("","",cache_rootdir,SCANDIR_DIRECTORY_MODE); // Basename - Extension - Dirname - File/Dir mode if ((num_dirs = scandir(cache_rootdir, &dirnamelist, (void*)scandir_selector, (void*)alphasort)) < 0) { loglevel(LOGERROR,"Error: trim_cache: scandir %s: %s", cache_rootdir, strerror(errno)); goto cleanup; } for (dirno = 0; dirno < num_dirs; dirno++) { strncpy(segment_dir, cache_rootdir,sizeof segment_dir); strcat(segment_dir, "/"); strcat(segment_dir, dirnamelist[dirno]->d_name); // Get all filename entry in directory 'cache_rootdir'/'each dir entry found in scandir' num_files = scandir(segment_dir, &filenamelist, (void*)NULL, (void*)alphasort); loglevel(LOGDEBUG,"Debug: trim_cache: DIR scandir numfiles(%d): with pattern '%s'",num_files, scandir_info.scandir_basename_pattern); if (num_files < 0) { loglevel(LOGERROR,"Error: trim_cache: scandir %s: %s", segment_dir, strerror(errno)); } else { // Launch 'extract_timestamp_file()' on each filename found for (fileno = 0; fileno < num_files; fileno++) { strncpy(fullname, segment_dir,sizeof fullname); strcat(fullname, "/"); strcat(fullname, filenamelist[fileno]->d_name); if (stat(fullname, &statbuf) < 0) loglevel(LOGERROR,"Error: trim_cache: Couldn't stat %s: %s", fullname,strerror(errno)); else // Extract the GMT time from actual picture file: ex: .piccache/IMG_4584.JPG/picYtGcs1 if (extract_timestamp_file(fullname, &statbuf,fd,tempfilename) >= 0) { no_of_files_in_cache = ftw_info.ftw_nb_of_cachedfile; // loglevel(LOGINFO,"Info: extract timestamp done no_total:%d",no_of_files_in_cache); } free(filenamelist[fileno]); } free(filenamelist); } free(dirnamelist[dirno]); } free(dirnamelist); #endif close(fd); fd = -1; ftw_info.ftw_temp_fd = -1; // 2. Sort the tempfile into a second, sorted tempfile strcpy(tempfilename_sorted, cache_rootdir); strcat(tempfilename_sorted, "/tmpsXXXXXX"); if ((fd = mkstemp(tempfilename_sorted)) < 0) { loglevel(LOGERROR,"Error: trim_cache: mkstemp for sorted temp file failed: %s/%s", tempfilename_sorted, strerror(errno)); goto cleanup; } close(fd); fd = -1; snprintf(command, sizeof command, command_pattern, tempfilename_sorted, tempfilename); if ((rc = system(command))) { loglevel(LOGERROR,"Error: trim_cache: \"%s\" failed with rc=%d", command, rc); goto cleanup; } // 3. Open the sorted tempfile and read lines, removing cache entries until at most (maximum number of entries - 1) remain if (!(fp = fopen(tempfilename_sorted, "r"))) { loglevel(LOGERROR,"Error: trim_cache: cannot open sorted temp file %s: %s", tempfilename_sorted, strerror(errno)); goto cleanup; } loglevel(LOGDEBUG,"Debug: Cleaning cache: found:'%d' max:'%d'",no_of_files_in_cache,config.picturecachesize); for (entryno = no_of_files_in_cache; entryno >= config.picturecachesize; entryno--) { if (!fgets(line, line_size, fp)) { loglevel(LOGERROR,"Error: trim_cache: cannot read line %d from sorted temp file %s: %s", no_of_files_in_cache - entryno + 1, tempfilename_sorted, strerror(errno)); goto cleanup; } char *cache_data_filename_p = strchr(line, ' '); if (!cache_data_filename_p) { loglevel(LOGERROR,"Error: trim_cache: Unexpected format (no space) in line %d of sorted temp file '%s': %s", no_of_files_in_cache - entryno + 1, tempfilename_sorted, line); goto cleanup; } *cache_data_filename_p = '\0'; cache_data_filename_p++; if (!strlen(cache_data_filename_p)) { loglevel(LOGERROR,"Error: trim_cache: Unexpected format (no filename) in line %d of sorted temp file '%s': %s", no_of_files_in_cache - entryno + 1, tempfilename_sorted, line); goto cleanup; } *(cache_data_filename_p + strlen(cache_data_filename_p) - 1) = '\0'; // Remove newline // Remove cache entry loglevel(LOGINFO,"Info: trim_cache remove '%s'-'%s'", line, cache_data_filename_p); remove_entry(cache_data_filename_p); } cleanup: // 4. Clean-up if (fd >= 0) close(fd); if (fp != NULL) fclose(fp); if (*tempfilename) { loglevel(LOGDEBUG,"Debug: trim_cache: remove '%s'",tempfilename); if ( unlink(tempfilename) < 0) { loglevel(LOGERROR,"Error: trim_cache: Couldn't remove %s: %s",tempfilename,strerror(errno)); } } if (*tempfilename_sorted) { loglevel(LOGDEBUG,"Debug: trim_cache: remove '%s'",tempfilename_sorted); if ( unlink(tempfilename_sorted) < 0) { loglevel(LOGERROR,"Error: trim_cache: Couldn't remove %s: %s",tempfilename_sorted,strerror(errno)); } } // Whether successful or not, update last size enforcement timestamp in cache info file, so we won't be re-trying too often if (read_cache_info_file(&cache_info)) return -1; cache_info.last_sizeenforcement_time = time(NULL); if (write_cache_info_file(&cache_info)) return -1; return 0; } // ********************************************************************** static int get_segment_for_target(const char *target_filename, char *segment_dir) { char target[PATH_MAX]; if (initialize_cache_if_needed()) return -1; strcpy(target, target_filename); strcpy(segment_dir, cache_rootdir); strcat(segment_dir, "/"); strcat(segment_dir, basename(target)); if (mkdir(segment_dir, standard_dir_filemode) < 0) { if (errno != EEXIST) { loglevel(LOGERROR,"Error: get_segment_for_target: Couldn't create directory '%s': %s", segment_dir, strerror(errno)); return -1; } } else if (chmod(segment_dir, standard_dir_filemode) < 0) loglevel(LOGERROR,"Error: get_segment_for_target: cannot chmod cache segment dir %s: %s", segment_dir, strerror(errno)); return 0; } // ****************************************************************** static int write_info_file(const char *info_filename, const cached_picture_info_t *pict_info_p) { int fd, chars_written; if ((fd = open(info_filename, O_CREAT | O_WRONLY | O_TRUNC, standard_filemode)) < 0) { loglevel(LOGERROR,"Error: write_info_file: cannot create info file %s: %s", info_filename, strerror(errno)); return -1; } if (fchmod(fd, standard_filemode) < 0) loglevel(LOGERROR,"Error: write_info_file: cannot fchmod info file %s: %s", info_filename, strerror(errno)); if ((chars_written = write(fd, pict_info_p, cached_picture_info_size)) < cached_picture_info_size) loglevel(LOGERROR,"Error: write_info_file: cannot write %d chars to info file %s: %s", cached_picture_info_size, info_filename, strerror(errno)); close(fd); return chars_written != cached_picture_info_size; } // ****************************************************************** static int read_info_file(const char *info_filename, cached_picture_info_t *pict_info_p) { int fd, chars_read; char fullname[PATH_MAX]; cached_picture_info_t read_pict_info; if ((fd = open(info_filename, O_RDONLY)) < 0) { loglevel(LOGERROR,"Error: read_info_file: cannot open info file %s: %s", info_filename, strerror(errno)); return -1; } if ((chars_read = read(fd, &read_pict_info, cached_picture_info_size)) < 0) loglevel(LOGERROR,"Error: read_info_file: cannot read %d chars from info file %s: %s", cached_picture_info_size, info_filename, strerror(errno)); close(fd); if (chars_read >= sizeof read_pict_info.struct_version) { if (read_pict_info.struct_version != cache_version) { int conversion_success = 0; // Convert from older versions of the cached_picture_info_t struct if possible. // If conversion is not possible, fall through to let code below delete info file and cached file and return -1. if (read_pict_info.struct_version == 1) { // v1 to v2: target_file_modified_time was added if (chars_read == cached_picture_info_size - sizeof read_pict_info.target_file_modified_time) { struct stat statbuf; if (stat(read_pict_info.target_filename, &statbuf) < 0) { loglevel(LOGERROR,"Error: read_info_file: Couldn't stat %s", read_pict_info.target_filename); } else { read_pict_info.target_file_modified_time = statbuf.st_mtime; conversion_success = 1; } } } if (conversion_success) { // Update cache entry to current cache version read_pict_info.struct_version = cache_version; read_pict_info.struct_size = cached_picture_info_size; chars_read = cached_picture_info_size; if (write_info_file(info_filename, &read_pict_info)) read_pict_info.struct_version = 0; // Force fall-through to attempt to delete info file instead } } if (read_pict_info.struct_version == cache_version && chars_read == cached_picture_info_size && chars_read == read_pict_info.struct_size) { memcpy(pict_info_p, &read_pict_info, cached_picture_info_size); return 0; } } // Info file is there, but something is wrong: Delete entire cache entry and return -1. strcpy(fullname, info_filename); *(strrchr(fullname, '.')) = '\0'; remove_entry(fullname); return -1; } // ********************************************************************** static int get_entry(cached_picture_info_t *pict_info_p) { char segment_dir[PATH_MAX], fullname[PATH_MAX]; struct dirent **namelist; int num_files, n, is_found; cached_picture_info_t this_pict_info; loglevel(LOGDEBUG,"Debug: get_entry with target_filename %s",pict_info_p->target_filename); if (get_segment_for_target(pict_info_p->target_filename, segment_dir)) return -1; // Scan the segment dir, reading all .info files, looking for a match for target_filename: setup_scandir_selector(infofile_pattern,"",segment_dir,SCANDIR_FILE_MODE); // Basename - Extension - Dirname - File/Dir mode if ((num_files = scandir(segment_dir, &namelist, (void*)scandir_selector, (void*)alphasort)) < 0) { loglevel(LOGERROR,"Error: get_entry: scandir %s: %s", segment_dir, strerror(errno)); return -1; } loglevel(LOGDEBUG,"Debug: get_entry scandir numfiles(%d) with pattern:'%s' in dir:'%s'", num_files, scandir_info.scandir_basename_pattern,segment_dir); for (n = 0, is_found = 0; n < num_files; n++) { if (!is_found) { strcpy(fullname, segment_dir); strcat(fullname, "/"); strcat(fullname, namelist[n]->d_name); if (!read_info_file(fullname, &this_pict_info)) if (!strcmp(this_pict_info.target_filename, pict_info_p->target_filename)) is_found = 1; } free(namelist[n]); } free(namelist); if (is_found) { // Verify that the cached file fits the current scaling settings. If not, remove it. if (this_pict_info.cached_image_max_width == pict_info_p->cached_image_max_width && this_pict_info.cached_image_max_height == pict_info_p->cached_image_max_height && this_pict_info.cached_image_max_zoom_percent == pict_info_p->cached_image_max_zoom_percent && this_pict_info.target_file_modified_time == pict_info_p->target_file_modified_time) { char filename[PATH_MAX]; // Adjust path of cached_filename in case the cache has been moved loglevel(LOGDEBUG,"Debug: get_entry scandir found and match current scaling settings%s","."); strcpy(filename, segment_dir); strcat(filename, strrchr(this_pict_info.cached_filename, '/')); memset(this_pict_info.cached_filename, 0, sizeof this_pict_info.cached_filename); strcpy(this_pict_info.cached_filename, filename); // Copy info structure to output parameter memcpy(pict_info_p, &this_pict_info, cached_picture_info_size); return 0; } else { // need to remove cached filename and segment loglevel(LOGDEBUG,"Debug get_entry cached file not fits the current scaling settings=>%s","remove it"); remove_entry(this_pict_info.cached_filename); } } else { loglevel(LOGDEBUG,"Debug: get_entry cached file not found: '%s'",pict_info_p->target_filename); } return -1; } // ********************************************************************** static void populate_pict_info(cached_picture_info_t *pict_info_p, const char *target_filename, int cached_image_max_width, int cached_image_max_height, int cached_image_max_zoom_percent) { struct stat statbuf; memset(pict_info_p, 0, cached_picture_info_size); pict_info_p->struct_version = cache_version; pict_info_p->struct_size = cached_picture_info_size; pict_info_p->cached_image_max_width = cached_image_max_width; pict_info_p->cached_image_max_height = cached_image_max_height; pict_info_p->cached_image_max_zoom_percent = cached_image_max_zoom_percent; strcpy(pict_info_p->target_filename, target_filename); if (stat(target_filename, &statbuf) < 0) log("populate_pict_info: Couldn't stat %s", target_filename); else pict_info_p->target_file_modified_time = statbuf.st_mtime; } /********** Public Cache Access functions ***********/ int piccache_get_entry_filename(char *cached_filename, const char *target_filename, int cached_image_max_width, int cached_image_max_height, int cached_image_max_zoom_percent) { cached_picture_info_t pict_info; // Do nothing if caching is not enabled if (!config.picturecachesize) return -1; populate_pict_info(&pict_info, target_filename, cached_image_max_width, cached_image_max_height, cached_image_max_zoom_percent); loglevel(LOGDEBUG,"Debug: piccache_get_entry_filename for target_filename: '%s'",target_filename); if (get_entry(&pict_info)) return -1; strcpy(cached_filename, pict_info.cached_filename); return 0; } // ********************************************************************** int piccache_put_entry_from_memory(const void *image_data, size_t size, const char *target_filename, int cached_image_max_width, int cached_image_max_height, int cached_image_max_zoom_percent) { char segment_dir[PATH_MAX], info_filename[PATH_MAX]; cached_picture_info_t pict_info; int fd, chars_written; // Do nothing if caching is not enabled if (!config.picturecachesize){ loglevel(LOGDEBUG,"piccache_put_entry_from_memory disable (pictureCachSize:'%d'",config.picturecachesize); return -1; } // Make sure the cache doesn't grow larger than the configured maximum size if (trim_cache()) { loglevel(LOGDEBUG,"piccache_put_entry_from_memory trimcache not necessary %s"," "); return -1; } // If the picture already exists in the cache, remove it populate_pict_info(&pict_info, target_filename, cached_image_max_width, cached_image_max_height, cached_image_max_zoom_percent); if (!get_entry(&pict_info)) { loglevel(LOGDEBUG,"piccache_put_entry_from_memory remove cache file '%s'",pict_info.cached_filename); remove_entry(pict_info.cached_filename); } if (get_segment_for_target(target_filename, segment_dir)) { loglevel(LOGDEBUG,"piccache_put_entry_from_memory get_segment '%s' failed for file '%s'",segment_dir,target_filename); return -1; } // Write the image data from memory to the cached image file strcpy(pict_info.cached_filename, segment_dir); strcat(pict_info.cached_filename, "/"); strcat(pict_info.cached_filename, unique_filename_template); if ((fd = mkstemp(pict_info.cached_filename)) < 0) { loglevel(LOGERROR,"Error: piccache_put_entry_from_memory: mkstemp %s: %s", pict_info.cached_filename, strerror(errno)); return -1; } if (fchmod(fd, standard_filemode) < 0) loglevel(LOGERROR,"Error: piccache_put_entry_from_memory: cannot fchmod cache file %s: %s", pict_info.cached_filename, strerror(errno)); if ((chars_written = write(fd, image_data, size)) < size) { loglevel(LOGERROR,"Error: piccache_put_entry_from_memory: cannot write %d chars to cache file %s: %s", size, pict_info.cached_filename, strerror(errno)); } else { loglevel(LOGINFO,"Info: piccache_put_entry_from_memory succeed write file '%s'",pict_info.cached_filename); } close(fd); // Write a fresh info structure to the cache entry's info file pict_info.resized_time = time(NULL); strcpy(info_filename, segment_dir); strcat(info_filename, strrchr(pict_info.cached_filename, '/')); strcat(info_filename, infofile_dot_ext); if (write_info_file(info_filename, &pict_info)) return -1; return 0; } /********** Internal Pre-caching functions ***********/ static int get_precaching_status(precache_status_t *precaching_status_p) { struct stat statbuf; int fd, chars_read; precache_status_t precaching_status; // Do nothing if caching is not enabled or the cache is not big enough for caching if (!config.picturecachesize || config.picturecachesize == 1) return 1; if (initialize_cache_if_needed()) return -1; if (lstat(precaching_status_filename, &statbuf) < 0) { if (errno != ENOENT) loglevel(LOGERROR,"Error: get_precaching_status: Couldn't stat %s", precaching_status_filename); return 1; } if ((fd = open(precaching_status_filename, O_RDONLY)) < 0) { loglevel(LOGERROR,"Error: get_precaching_status: cannot open pre-caching status file %s: %s", precaching_status_filename, strerror(errno)); return -1; } if ((chars_read = read(fd, &precaching_status, precache_status_size)) < 0) loglevel(LOGERROR,"Error: get_precaching_status: cannot read %d chars from pre-caching status file %s: %s", precache_status_size, precaching_status_filename, strerror(errno)); close(fd); if (chars_read < 0) return -1; if (chars_read != precache_status_size) { piccache_clear_precaching_status(); return 1; } memcpy(precaching_status_p, &precaching_status, precache_status_size); // We return 0 if pre-caching is being performed, 1 if not, -1 if error. return 0; } /********** Public Pre-caching functions ***********/ int piccache_set_precaching_status(const char *target_filename) { precache_status_t precaching_status; int fd, chars_written; if (!target_filename) return -1; // Do nothing if caching is not enabled or the cache is not big enough for caching if (!config.picturecachesize || config.picturecachesize == 1) return -1; if (initialize_cache_if_needed()) return -1; memset(&precaching_status, 0, cache_info_size); precaching_status.struct_size = cache_info_size; precaching_status.struct_version = cache_version; precaching_status.started_time = time(NULL); strcpy(precaching_status.target_filename, target_filename); if ((fd = open(precaching_status_filename, O_CREAT | O_WRONLY | O_TRUNC, standard_filemode)) < 0) { loglevel(LOGERROR,"Error: piccache_set_precaching_status: cannot create pre-caching status file %s: %s", precaching_status_filename, strerror(errno)); return -1; } if (fchmod(fd, standard_filemode) < 0) loglevel(LOGERROR,"Error: piccache_set_precaching_status: cannot fchmod pre-caching status file %s: %s", precaching_status_filename, strerror(errno)); if ((chars_written = write(fd, &precaching_status, precache_status_size)) < precache_status_size) loglevel(LOGERROR,"Error: piccache_set_precaching_status: cannot write %d chars to pre-caching status file %s: %s", precache_status_size, precaching_status_filename, strerror(errno)); close(fd); return 0; } // ********************************************************************** int piccache_clear_precaching_status(void) { loglevel(LOGDEBUG,"Debug: piccache_clear_precaching_status: remove of '%s'",precaching_status_filename); unlink(precaching_status_filename); return 0; } // ********************************************************************** int piccache_is_precaching_in_progress(void) { struct stat statbuf; // Return false if caching is not enabled or the cache is not big enough for caching if (!config.picturecachesize || config.picturecachesize == 1) return 0; if (lstat(precaching_status_filename, &statbuf) < 0) { if (errno != ENOENT) loglevel(LOGERROR,"Error: piccache_is_precaching_in_progress: Couldn't stat %s", precaching_status_filename); return 0; } return 1; } // ********************************************************************** int piccache_wait_if_precaching_this(const char *target_filename) { precache_status_t precaching_status; int retval; if ((retval = get_precaching_status(&precaching_status))) { if (retval < 0) return -1; else return 0; } logv("Found pre-caching of '%s' in progress", precaching_status.target_filename); if (!strcmp(target_filename, precaching_status.target_filename)) { logv("Waiting for pre-caching of '%s' to finish", target_filename); while (difftime(time(NULL), precaching_status.started_time) < precache_wait_timeout) { sleep(1); if ((retval = get_precaching_status(&precaching_status))) { if (retval < 0) return -1; else return 0; } if (strcmp(target_filename, precaching_status.target_filename)) return 0; } logv("Waiting for pre-caching of '%s' timed out", target_filename); piccache_clear_precaching_status(); } return 0; } // **********************************************************************