/* Copyright (C) 2010-2019 The RetroArch team * * --------------------------------------------------------------------------------------- * The following license statement only applies to this file (runtime_file.c). * --------------------------------------------------------------------------------------- * * Permission is hereby granted, free of charge, * to any person obtaining a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include "../configuration.h" #include "../msg_hash.h" #include "../paths.h" #include "../file_path_special.h" #include "menu_driver.h" #include "widgets/menu_entry.h" #include "menu_thumbnail_path.h" /* Used fixed size char arrays here, just to avoid * the inconvenience of having to calloc()/free() * each individual entry by hand... */ struct menu_thumbnail_path_data { char system[PATH_MAX_LENGTH]; char content_path[PATH_MAX_LENGTH]; char content_label[PATH_MAX_LENGTH]; char content_core_name[PATH_MAX_LENGTH]; char content_db_name[PATH_MAX_LENGTH]; char content_img[PATH_MAX_LENGTH]; char right_path[PATH_MAX_LENGTH]; char left_path[PATH_MAX_LENGTH]; }; /* Initialisation */ /* Creates new thumbnail path data container. * Returns handle to new menu_thumbnail_path_data_t object. * on success, otherwise NULL. * Note: Returned object must be free()d */ menu_thumbnail_path_data_t *menu_thumbnail_path_init() { menu_thumbnail_path_data_t *path_data = NULL; path_data = (menu_thumbnail_path_data_t*)calloc(1, sizeof(*path_data)); if (!path_data) return NULL; return path_data; } /* Resets thumbnail path data * (blanks all internal string containers) */ void menu_thumbnail_path_reset(menu_thumbnail_path_data_t *path_data) { if (!path_data) return; path_data->system[0] = '\0'; path_data->content_path[0] = '\0'; path_data->content_label[0] = '\0'; path_data->content_core_name[0] = '\0'; path_data->content_db_name[0] = '\0'; path_data->content_img[0] = '\0'; path_data->right_path[0] = '\0'; path_data->left_path[0] = '\0'; } /* Utility Functions */ /* Returns currently set thumbnail 'type' (Named_Snaps, * Named_Titles, Named_Boxarts) for specified thumbnail * identifier (right, left) */ const char *menu_thumbnail_get_type(enum menu_thumbnail_id thumbnail_id) { settings_t *settings = config_get_ptr(); unsigned type = 0; if (!settings) return msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF); switch (thumbnail_id) { case MENU_THUMBNAIL_RIGHT: type = settings->uints.menu_thumbnails; break; case MENU_THUMBNAIL_LEFT: type = settings->uints.menu_left_thumbnails; break; default: return msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF); } switch (type) { case 1: return "Named_Snaps"; case 2: return "Named_Titles"; case 3: return "Named_Boxarts"; case 0: default: break; } return msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF); } /* Returns true if specified thumbnail is enabled * (i.e. if 'type' is not equal to MENU_ENUM_LABEL_VALUE_OFF) */ bool menu_thumbnail_is_enabled(enum menu_thumbnail_id thumbnail_id) { settings_t *settings = config_get_ptr(); if (!settings) return false; switch (thumbnail_id) { case MENU_THUMBNAIL_RIGHT: return settings->uints.menu_thumbnails != 0; case MENU_THUMBNAIL_LEFT: return settings->uints.menu_left_thumbnails != 0; break; default: break; } return false; } /* Setters */ /* Fills content_img field of path_data using existing * content_label field (for internal use only) */ static void fill_content_img(menu_thumbnail_path_data_t *path_data) { char *scrub_char_pointer = NULL; /* Copy source label string */ strlcpy(path_data->content_img, path_data->content_label, sizeof(path_data->content_img)); /* Scrub characters that are not cross-platform and/or violate the * No-Intro filename standard: * http://datomatic.no-intro.org/stuff/The%20Official%20No-Intro%20Convention%20(20071030).zip * Replace these characters in the entry name with underscores */ while((scrub_char_pointer = strpbrk(path_data->content_img, "&*/:`\"<>?\\|"))) *scrub_char_pointer = '_'; /* Add PNG extension */ strlcat(path_data->content_img, file_path_str(FILE_PATH_PNG_EXTENSION), sizeof(path_data->content_img)); } /* Sets current 'system' (default database name). * Returns true if 'system' is valid. * > Used as a fallback when individual content lacks an * associated database name */ bool menu_thumbnail_set_system(menu_thumbnail_path_data_t *path_data, const char *system) { if (!path_data) return false; /* When system is updated, must regenerate right/left * thumbnail paths */ path_data->right_path[0] = '\0'; path_data->left_path[0] = '\0'; /* 'Reset' path_data system string */ path_data->system[0] = '\0'; if (string_is_empty(system)) return false; /* Hack: There is only one MAME thumbnail repo, * so filter any input starting with 'MAME...' */ if (strncmp(system, "MAME", 4) == 0) strlcpy(path_data->system, "MAME", sizeof(path_data->system)); else strlcpy(path_data->system, system, sizeof(path_data->system)); return true; } /* Sets current thumbnail content according to the specified label. * Returns true if content is valid */ bool menu_thumbnail_set_content(menu_thumbnail_path_data_t *path_data, const char *label) { if (!path_data) return false; /* When content is updated, must regenerate right/left * thumbnail paths */ path_data->right_path[0] = '\0'; path_data->left_path[0] = '\0'; /* 'Reset' path_data content strings */ path_data->content_path[0] = '\0'; path_data->content_label[0] = '\0'; path_data->content_core_name[0] = '\0'; path_data->content_db_name[0] = '\0'; path_data->content_img[0] = '\0'; if (string_is_empty(label)) return false; /* Cache content label */ strlcpy(path_data->content_label, label, sizeof(path_data->content_label)); /* Determine content image name */ fill_content_img(path_data); /* Redundant error check... */ if (string_is_empty(path_data->content_img)) return false; return true; } /* Sets current thumbnail content to the specified image. * Returns true if content is valid */ bool menu_thumbnail_set_content_image(menu_thumbnail_path_data_t *path_data, const char *img_dir, const char *img_name) { char *content_img_no_ext = NULL; if (!path_data) return false; /* When content is updated, must regenerate right/left * thumbnail paths */ path_data->right_path[0] = '\0'; path_data->left_path[0] = '\0'; /* 'Reset' path_data content strings */ path_data->content_path[0] = '\0'; path_data->content_label[0] = '\0'; path_data->content_core_name[0] = '\0'; path_data->content_db_name[0] = '\0'; path_data->content_img[0] = '\0'; if (string_is_empty(img_dir)) return false; if (string_is_empty(img_name)) return false; if (path_is_media_type(img_name) != RARCH_CONTENT_IMAGE) return false; /* Cache content image name */ strlcpy(path_data->content_img, img_name, sizeof(path_data->content_img)); /* Get image label */ content_img_no_ext = path_remove_extension(path_data->content_img); if (!string_is_empty(content_img_no_ext)) strlcpy(path_data->content_label, content_img_no_ext, sizeof(path_data->content_label)); else strlcpy(path_data->content_label, path_data->content_img, sizeof(path_data->content_label)); /* Set file path */ fill_pathname_join(path_data->content_path, img_dir, img_name, sizeof(path_data->content_path)); /* Set core name to "imageviewer" */ strlcpy(path_data->content_core_name, "imageviewer", sizeof(path_data->content_core_name)); /* Set database name (arbitrarily) to "_images_" * (required for compatibility with menu_thumbnail_update_path(), * but not actually used...) */ strlcpy(path_data->content_db_name, "_images_", sizeof(path_data->content_db_name)); /* Redundant error check */ if (string_is_empty(path_data->content_path)) return false; return true; } /* Sets current thumbnail content to the specified playlist entry. * Returns true if content is valid. * > Note: It is always best to use playlists when setting * thumbnail content, since there is no guarantee that the * corresponding menu entry label will contain a useful * identifier (it may be 'tainted', e.g. with the current * core name). 'Real' labels should be extracted from source */ bool menu_thumbnail_set_content_playlist(menu_thumbnail_path_data_t *path_data, playlist_t *playlist, size_t idx) { const char *content_path = NULL; const char *content_label = NULL; const char *core_name = NULL; const char *db_name = NULL; const struct playlist_entry *entry = NULL; if (!path_data) return false; /* When content is updated, must regenerate right/left * thumbnail paths */ path_data->right_path[0] = '\0'; path_data->left_path[0] = '\0'; /* 'Reset' path_data content strings */ path_data->content_path[0] = '\0'; path_data->content_label[0] = '\0'; path_data->content_core_name[0] = '\0'; path_data->content_db_name[0] = '\0'; path_data->content_img[0] = '\0'; if (!playlist) return false; if (idx >= playlist_get_size(playlist)) return false; /* Read playlist values */ playlist_get_index(playlist, idx, &entry); content_path = entry->path; content_label = entry->label; core_name = entry->core_name; db_name = entry->db_name; /* Content without a path is invalid by definition */ if (string_is_empty(content_path)) return false; /* Cache content path * (This is required for imageviewer content) */ strlcpy(path_data->content_path, content_path, sizeof(path_data->content_path)); /* Cache core name * (This is required for imageviewer content) */ if (!string_is_empty(core_name)) strlcpy(path_data->content_core_name, core_name, sizeof(path_data->content_core_name)); /* Get content label */ if (!string_is_empty(content_label)) strlcpy(path_data->content_label, content_label, sizeof(path_data->content_label)); else fill_short_pathname_representation(path_data->content_label, content_path, sizeof(path_data->content_label)); /* Determine content image name */ fill_content_img(path_data); /* Redundant error check... */ if (string_is_empty(path_data->content_img)) return false; /* Thumbnail image name is done -> now check if * per-content database name is defined */ if (!string_is_empty(db_name)) { /* Hack: There is only one MAME thumbnail repo, * so filter any input starting with 'MAME...' */ if (strncmp(db_name, "MAME", 4) == 0) strlcpy(path_data->content_db_name, "MAME", sizeof(path_data->content_db_name)); else { char *db_name_no_ext = NULL; char tmp_buf[PATH_MAX_LENGTH]; tmp_buf[0] = '\0'; /* Remove .lpl extension * > path_remove_extension() requires a char * (not const) * so have to use a temporary buffer... */ strlcpy(tmp_buf, db_name, sizeof(tmp_buf)); db_name_no_ext = path_remove_extension(tmp_buf); if (!string_is_empty(db_name_no_ext)) strlcpy(path_data->content_db_name, db_name_no_ext, sizeof(path_data->content_db_name)); else strlcpy(path_data->content_db_name, tmp_buf, sizeof(path_data->content_db_name)); } } return true; } /* Updaters */ /* Updates path for specified thumbnail identifier (right, left). * Must be called after: * - menu_thumbnail_set_system() * - menu_thumbnail_set_content*() * ...and before: * - menu_thumbnail_get_path() * Returns true if generated path is valid */ bool menu_thumbnail_update_path(menu_thumbnail_path_data_t *path_data, enum menu_thumbnail_id thumbnail_id) { settings_t *settings = config_get_ptr(); const char *type = menu_thumbnail_get_type(thumbnail_id); const char *system_name = NULL; char *thumbnail_path = NULL; if (!path_data) return false; /* Determine which path we are updating... */ switch (thumbnail_id) { case MENU_THUMBNAIL_RIGHT: thumbnail_path = path_data->right_path; break; case MENU_THUMBNAIL_LEFT: thumbnail_path = path_data->left_path; break; default: return false; } thumbnail_path[0] = '\0'; /* Sundry error checking */ if (!settings) return false; if (string_is_empty(settings->paths.directory_thumbnails)) return false; if (!menu_thumbnail_is_enabled(thumbnail_id)) return false; /* Generate new path */ /* > Check path_data for empty strings */ if (string_is_empty(path_data->content_img) || (string_is_empty(path_data->system) && string_is_empty(path_data->content_db_name))) return false; /* > Get current system */ system_name = string_is_empty(path_data->content_db_name) ? path_data->system : path_data->content_db_name; /* > Special case: thumbnail for imageviewer content * is the image file itself */ if (string_is_equal(system_name, "images_history") || string_is_equal(path_data->content_core_name, "imageviewer")) { if (string_is_empty(path_data->content_path)) return false; /* imageviewer content is identical for left and right thumbnails */ if (path_is_media_type(path_data->content_path) == RARCH_CONTENT_IMAGE) strlcpy(thumbnail_path, path_data->content_path, PATH_MAX_LENGTH * sizeof(char)); } else { char tmp_buf[PATH_MAX_LENGTH]; tmp_buf[0] = '\0'; /* > Normal content: assemble path */ /* >> Base + system name */ fill_pathname_join(thumbnail_path, settings->paths.directory_thumbnails, system_name, PATH_MAX_LENGTH * sizeof(char)); /* >> Add type */ fill_pathname_join(tmp_buf, thumbnail_path, type, sizeof(tmp_buf)); /* >> Add content image */ thumbnail_path[0] = '\0'; fill_pathname_join(thumbnail_path, tmp_buf, path_data->content_img, PATH_MAX_LENGTH * sizeof(char)); } /* Final error check - is cached path empty? */ if (string_is_empty(thumbnail_path)) return false; return true; } /* Getters */ /* Fetches the current thumbnail file path of the * specified thumbnail 'type'. * Returns true if path is valid. */ bool menu_thumbnail_get_path(menu_thumbnail_path_data_t *path_data, enum menu_thumbnail_id thumbnail_id, const char **path) { char *thumbnail_path = NULL; if (!path_data) return false; if (!path) return false; switch (thumbnail_id) { case MENU_THUMBNAIL_RIGHT: thumbnail_path = path_data->right_path; break; case MENU_THUMBNAIL_LEFT: thumbnail_path = path_data->left_path; break; default: return false; } if (string_is_empty(thumbnail_path)) return false; *path = thumbnail_path; return true; } /* Fetches current thumbnail label. * Returns true if label is valid. */ bool menu_thumbnail_get_label(menu_thumbnail_path_data_t *path_data, const char **label) { if (!path_data) return false; if (!label) return false; if (string_is_empty(path_data->content_label)) return false; *label = path_data->content_label; return true; } /* Fetches current thumbnail core name. * Returns true if core name is valid. */ bool menu_thumbnail_get_core_name(menu_thumbnail_path_data_t *path_data, const char **core_name) { if (!path_data) return false; if (!core_name) return false; if (string_is_empty(path_data->content_core_name)) return false; *core_name = path_data->content_core_name; return true; }