├── config └── .gitkeep ├── TermSP ├── icon.png ├── defaults ├── bg.png ├── icon.png ├── icon_selected.png └── launch.sh ├── lib ├── libsdlfox.so ├── libreadline.so └── libvterm.so.0 ├── fonts ├── DejaVuSansMono.ttf └── DejaVuSansMono-Bold.ttf ├── config.json ├── scripts ├── formats.sh ├── search.sh ├── utils.sh ├── collections.sh ├── shortcuts.sh ├── bulk.sh └── menus.sh ├── launch.sh ├── README.md ├── config.sh └── collagen.sh /config/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TermSP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkr87/CollaGen/HEAD/TermSP -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkr87/CollaGen/HEAD/icon.png -------------------------------------------------------------------------------- /defaults/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkr87/CollaGen/HEAD/defaults/bg.png -------------------------------------------------------------------------------- /lib/libsdlfox.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkr87/CollaGen/HEAD/lib/libsdlfox.so -------------------------------------------------------------------------------- /defaults/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkr87/CollaGen/HEAD/defaults/icon.png -------------------------------------------------------------------------------- /lib/libreadline.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkr87/CollaGen/HEAD/lib/libreadline.so -------------------------------------------------------------------------------- /lib/libvterm.so.0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkr87/CollaGen/HEAD/lib/libvterm.so.0 -------------------------------------------------------------------------------- /fonts/DejaVuSansMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkr87/CollaGen/HEAD/fonts/DejaVuSansMono.ttf -------------------------------------------------------------------------------- /defaults/icon_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkr87/CollaGen/HEAD/defaults/icon_selected.png -------------------------------------------------------------------------------- /fonts/DejaVuSansMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rkr87/CollaGen/HEAD/fonts/DejaVuSansMono-Bold.ttf -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "CollaGen", 3 | "icon": "icon.png", 4 | "iconsel": "icon.png", 5 | "launch": "launch.sh", 6 | "description": "Collection Manager" 7 | } -------------------------------------------------------------------------------- /scripts/formats.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # FORMAT CODES 4 | NO_FORMAT="\033[0m" 5 | BOLD="\033[1m" 6 | BLACK="\033[0;30m" 7 | RED="\033[0;31m" 8 | GREEN="\033[0;32m" 9 | YELLOW="\033[0;33m" 10 | BLUE="\033[0;34m" 11 | MAGENTA="\033[0;35m" 12 | CYAN="\033[0;36m" 13 | WHITE="\033[0;37m" -------------------------------------------------------------------------------- /launch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | export LD_LIBRARY_PATH=/mnt/SDCARD/System/lib:/lib:/usr/lib:/usr/trimui/lib:/mnt/SDCARD/Apps/CollaGen/lib 3 | export PATH="/bin:/usr/bin:/usr/local/bin:/mnt/SDCARD/System/bin" 4 | appDir="$(cd "$(dirname "$0")" && pwd)" 5 | "$appDir/TermSP" -k -f "$appDir/fonts/DejaVuSansMono.ttf" -b "$appDir/fonts/DejaVuSansMono-Bold.ttf" -s 20 -e "$appDir/collagen.sh" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CollaGen 2 | CollaGen is a CLI based application for **Trimui Smart Pro** devices running **[CrossMix-OS](https://github.com/cizia64/CrossMix-OS)**. Its purpose to allow quick and easy dynamic generation of game collections using keyword filters on device. 3 | 4 | # Installation 5 | * Download the [latest-release](https://github.com/rkr87/CollaGen/releases/latest/download/CollaGen.zip). 6 | * Extract the ZIP file. 7 | * Transfer to the **Apps** directory of your SD card. 8 | 9 | # Demo 10 | [YouTube](https://youtu.be/J7xB8rnWBoU) 11 | -------------------------------------------------------------------------------- /config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ## OS 4 | ROOT_DIR="/mnt/SDCARD" 5 | BEST_DIR="Best" 6 | ROM_DIR="Roms" 7 | IMG_DIR="Imgs" 8 | EMU_DIR="Emus" 9 | EMU_EXT_KEY="extlist" 10 | ROM_CACHE_FILE="Roms_cache7.db" 11 | 12 | ## SYSTEM 13 | SYSTEM_FILE="/mnt/UDISK/system.json" 14 | SYSTEM_LANGUAGE_KEY="language" 15 | LANG_DIR="/usr/trimui/res/lang" 16 | LANG_BEST_TAB_KEY="140" 17 | 18 | ## APP 19 | APP_NAME="CollaGen" 20 | VERSION="2024.9.13.1" 21 | CONFIG_DIR="config" 22 | SCRIPTS_DIR="scripts" 23 | 24 | DEFAULTS_DIR="defaults" 25 | DEFAULT_CONFIG="config.json" 26 | DEFAULT_ICON="icon.png" 27 | DEFAULT_ICON_SEL="icon_selected.png" 28 | DEFAULT_BG="bg.png" 29 | DEFAULT_LAUNCH="launch.sh" 30 | 31 | APP_INCLUDE="include.txt" 32 | APP_EXCLUDE="exclude.txt" 33 | 34 | EXTENSION_EXCLUSIONS="srm,db,sav" -------------------------------------------------------------------------------- /collagen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH="/mnt/SDCARD/System/bin:$PATH" 4 | export LD_LIBRARY_PATH="/mnt/SDCARD/System/lib:$LD_LIBRARY_PATH" 5 | 6 | APP_DIR="$(cd "$(dirname "$0")" && pwd)" 7 | 8 | . "$APP_DIR/config.sh" 9 | . "$APP_DIR/scripts/formats.sh" 10 | . "$APP_DIR/scripts/utils.sh" 11 | . "$APP_DIR/scripts/menus.sh" 12 | . "$APP_DIR/scripts/collections.sh" 13 | . "$APP_DIR/scripts/shortcuts.sh" 14 | . "$APP_DIR/scripts/search.sh" 15 | . "$APP_DIR/scripts/bulk.sh" 16 | 17 | init() { 18 | local systemLanguage=$(get_json_value "$SYSTEM_FILE" $SYSTEM_LANGUAGE_KEY) 19 | langFile="$LANG_DIR/$systemLanguage" 20 | if [ ! -d "$APP_DIR/config" ]; then 21 | mkdir -p "$APP_DIR/config" 22 | fi 23 | echo -e "\0033\0143" 24 | show_help 25 | show_main_menu 26 | } 27 | 28 | init -------------------------------------------------------------------------------- /defaults/launch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo $0 $* 3 | 4 | PATH="/mnt/SDCARD/System/bin:$PATH" 5 | 6 | RomFullPath=$1 7 | RomPath=$(dirname "$1") 8 | RomDir=$(basename "$RomPath") 9 | Launcher=$(jq -r '.launch' "/mnt/SDCARD/Emus/$RomDir/config.json") 10 | LaunchPath="/mnt/SDCARD/Emus/$RomDir/$Launcher" 11 | 12 | extension="${RomFullPath##*.}" 13 | if [ "$extension" = "txt" ]; then 14 | RomFullPath=$(cat "$RomFullPath" | head -n 1) # Trick to have shortcuts: the real ROM filename is inside the text file 15 | fi 16 | 17 | echo "***************************************************************************" 18 | echo "RomFullPath $RomFullPath" 19 | echo "RomPath $RomPath" 20 | echo "RomDir $RomDir" 21 | echo "LaunchPath $LaunchPath" 22 | echo "***************************************************************************" 23 | 24 | # Example: 25 | # *************************************************************************** 26 | # RomFullPath /mnt/SDCARD/Best/Free Games Collection/./Roms/ATARI2600/Sheep It Up.zip 27 | # RomPath /mnt/SDCARD/Best/Free Games Collection/./Roms/ATARI2600 28 | # RomDir ATARI2600 29 | # LaunchPath /mnt/SDCARD/Emus/ATARI2600/launch.sh 30 | # *************************************************************************** 31 | 32 | 33 | if [ -f "$LaunchPath" ]; then 34 | 35 | "$LaunchPath" "$RomFullPath" 36 | 37 | else 38 | 39 | ######################################################################################### 40 | # this section is only useful if standard Roms directory are not used # 41 | # Select core based on name of containing folder 42 | case $(echo "$(basename "$RomPath")" | awk '{print toupper($0)}') in 43 | ARCADE) 44 | core="mame2003_plus_libretro.so" 45 | ;; 46 | # PS) 47 | # core="duckstation_libretro.so" 48 | # ;; 49 | *) ;; 50 | esac 51 | 52 | cd /mnt/SDCARD/RetroArch/ 53 | HOME=/mnt/SDCARD/RetroArch/ ./ra64.trimui -v -L ".retroarch/cores/$core" "$RomFullPath" 54 | ######################################################################################### 55 | 56 | fi 57 | -------------------------------------------------------------------------------- /scripts/search.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | search_db_by_keyword() { 4 | local keyword="$1" 5 | local exclusions="$2" 6 | echo " Searching cache for '$keyword'" 7 | find "$ROOT_DIR/$ROM_DIR" -maxdepth 2 -type f -name "*cache7.db" | while IFS= read -r dbFile; do 8 | local fileName=$(basename "$dbFile") 9 | local tableName="${fileName%%_*}_roms" 10 | if sqlite3 "$dbFile" "SELECT name FROM sqlite_master WHERE type='table' AND name='$tableName';" | grep -q "$tableName"; then 11 | sqlite3 "$dbFile" "SELECT pinyin, path FROM $tableName WHERE path NOT LIKE '%.launch' AND pinyin COLLATE NOCASE LIKE '%$keyword%';" | while IFS='|' read -r displayName filePath; do 12 | create_shortcut "${filePath#*../../$ROM_DIR/}" "$(echo "$displayName" | sed 's/[\/:]/-/g')" "$exclusions" 13 | done 14 | fi 15 | done 16 | } 17 | 18 | search_db_by_keywords() { 19 | local keywords="$1" 20 | local exclusions="$2" 21 | echo "$keywords" | tr ',' '\n' | while IFS= read -r keyword; do 22 | keyword=$(echo "$keyword" | xargs) 23 | if [ -n "$keyword" ]; then 24 | search_db_by_keyword "$keyword" "$exclusions" 25 | fi 26 | done 27 | } 28 | 29 | search_files_by_keyword() { 30 | local keyword="$1" 31 | local exclusions="$2" 32 | echo " Searching files for '$keyword'" 33 | set -- find "$ROOT_DIR/$ROM_DIR" -type f -iname "*$keyword*" ! -path "*/.*/*" 34 | 35 | local ext 36 | OLDIFS="$IFS" 37 | IFS=',' 38 | for ext in $EXTENSION_EXCLUSIONS; do 39 | set -- "$@" ! -iname "*.$ext" 40 | done 41 | IFS="$OLDIFS" 42 | 43 | "$@" | while IFS= read -r foundFile; do 44 | local baseName=$(basename "$foundFile") 45 | create_shortcut "${foundFile#$ROOT_DIR/$ROM_DIR/}" "${baseName%.*}" "$exclusions" 46 | done 47 | } 48 | 49 | search_files_by_keywords() { 50 | local keywords="$1" 51 | local exclusions="$2" 52 | echo "$keywords" | tr ',' '\n' | while IFS= read -r keyword; do 53 | keyword=$(echo "$keyword" | xargs) 54 | if [ -n "$keyword" ]; then 55 | search_files_by_keyword "$keyword" "$exclusions" 56 | fi 57 | done 58 | } 59 | 60 | search_roms() { 61 | local searchTerms="$1" 62 | local excludeTerms="$2" 63 | 64 | addedFilesTemp=$(mktemp) 65 | excludedFilesTemp=$(mktemp) 66 | echo "" 67 | echo "Identifying valid $collectionName collection items..." 68 | search_db_by_keywords "$searchTerms" "$excludeTerms" 69 | search_files_by_keywords "$searchTerms" "$excludeTerms" 70 | rm -f "$addedFilesTemp" 71 | rm -f "$excludedFilesTemp" 72 | sync 73 | } -------------------------------------------------------------------------------- /scripts/utils.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | get_user_input() { 4 | local prompt="$1" 5 | local default="$2" 6 | while true; do 7 | echo "" >&2 8 | echo "$prompt" >&2 9 | read -r userInput 10 | 11 | if [ -n "$userInput" ]; then 12 | echo "$userInput" | xargs 13 | return 14 | elif [ -n "$default" ]; then 15 | echo "$default" | xargs 16 | return 17 | else 18 | echo "" 19 | echo "Input cannot be empty. Please try again." >&2 20 | fi 21 | done 22 | } 23 | 24 | save_list_to_file() { 25 | local words="$1" 26 | local filename="$2" 27 | echo "$words" | tr ',' '\n' | while read -r word; do 28 | trimmed_word=$(echo "$word" | xargs) 29 | if [ -n "$trimmed_word" ]; then 30 | echo "$trimmed_word" >> "$filename" 31 | fi 32 | done 33 | } 34 | 35 | get_list_from_file() { 36 | local filename="$1" 37 | local result="" 38 | while IFS= read -r line; do 39 | if [ -z "$result" ]; then 40 | result="$line" 41 | else 42 | result="$result,$line" 43 | fi 44 | done < "$filename" 45 | echo "$result" 46 | } 47 | 48 | delete_file_content() { 49 | local filename="$1" 50 | > "$filename" 51 | } 52 | 53 | get_json_value() { 54 | local file="$1" 55 | local key="$2" 56 | local default_value="${3:-}" 57 | 58 | local value=$(sed -n "s/.*\"$key\":\s*\"\([^\"]*\)\".*/\1/p" "$file" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') 59 | 60 | if [ -z "$value" ]; then 61 | value="$default_value" 62 | fi 63 | 64 | echo "$value" 65 | } 66 | 67 | set_json_value() { 68 | local file="$1" 69 | local key="$2" 70 | local new_value="$3" 71 | local escaped_value=$(printf '%s' "$new_value" | sed 's/[&/\]/\\&/g') 72 | sed -i "s/\"$key\":\s*\"[^\"]*\"/\"$key\": \"$escaped_value\"/" "$file" 73 | sync 74 | } 75 | 76 | lower_case() { 77 | local string="$1" 78 | local stringLower=$(echo "$string" | tr '[:upper:]' '[:lower:]') 79 | echo "$stringLower" 80 | } 81 | 82 | upper_case() { 83 | local string="$1" 84 | local stringLower=$(echo "$string" | tr '[:lower:]' '[:upper:]') 85 | echo "$stringLower" 86 | } 87 | 88 | list_to_string() { 89 | local commaList=$(echo "$1" | tr '\n' ',' | sed 's/,$//') 90 | echo "$commaList" 91 | } 92 | 93 | echo_trim() { 94 | trimmable="$1" 95 | maxLen=$2 96 | prefix="$3" 97 | suffix="$4" 98 | 99 | preLen="${#prefix}" 100 | trimLen="${#trimmable}" 101 | sufLen="${#suffix}" 102 | 103 | strLen=$(($preLen + $trimLen + $sufLen)) 104 | 105 | if [ $strLen -gt $maxLen ]; then 106 | relLen=$(($maxLen - $strLen + $trimLen - 3)) 107 | trimmable="${trimmable:0:relLen}..." 108 | fi 109 | echo "$prefix$trimmable$suffix" 110 | } 111 | 112 | has_valid_extension() { 113 | local file_path="$1" 114 | local extensions="$2" 115 | local delimiter="${3:-|}" 116 | 117 | local file_extension="${file_path##*.}" 118 | 119 | if [ -z "$extensions" ]; then 120 | echo 1 121 | return 0 122 | fi 123 | 124 | local valid=0 125 | local ext 126 | while [ -n "$extensions" ]; do 127 | ext="${extensions%%"$delimiter"*}" 128 | [ "$extensions" = "$ext" ] && extensions="" || extensions="${extensions#*"$delimiter"}" 129 | 130 | if [ "$file_extension" = "$ext" ]; then 131 | valid=1 132 | break 133 | fi 134 | done 135 | 136 | echo $valid 137 | } -------------------------------------------------------------------------------- /scripts/collections.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set_collection() { 4 | if [ "$#" -eq 0 ]; then 5 | collectionName=$(get_user_input "Enter a display name for the new collection:") 6 | else 7 | collectionName="$1" 8 | fi 9 | collectionDir="$ROOT_DIR/$BEST_DIR/$collectionName" 10 | local appCollDir="$APP_DIR/$CONFIG_DIR/$collectionName" 11 | mkdir -p "$appCollDir" 12 | touch "$appCollDir/$APP_INCLUDE" 13 | touch "$appCollDir/$APP_EXCLUDE" 14 | sync 15 | } 16 | 17 | create_collection() { 18 | set_collection 19 | if [ -d "$collectionDir" ]; then 20 | echo "" 21 | echo "Collection already exists, going to edit menu." 22 | show_edit_collection_menu "$collectionName" 23 | else 24 | mkdir -p "$collectionDir" 25 | if [ $? -eq 0 ]; then 26 | mkdir -p "$collectionDir/$IMG_DIR" 27 | mkdir -p "$collectionDir/$ROM_DIR" 28 | cp "$APP_DIR/$DEFAULTS_DIR/$DEFAULT_ICON" "$collectionDir/" 29 | cp "$APP_DIR/$DEFAULTS_DIR/$DEFAULT_ICON_SEL" "$collectionDir/" 30 | cp "$APP_DIR/$DEFAULTS_DIR/$DEFAULT_BG" "$collectionDir/" 31 | cp "$APP_DIR/$DEFAULTS_DIR/$DEFAULT_LAUNCH" "$collectionDir/" 32 | create_collection_config 33 | echo "" 34 | echo "$collectionName collection created with default artwork." 35 | local collectionConfig="$APP_DIR/$CONFIG_DIR/$collectionName" 36 | delete_file_content "$collectionConfig/$APP_INCLUDE"; 37 | delete_file_content "$collectionConfig/$APP_EXCLUDE"; 38 | echo "" 39 | echo "Provide initial search terms (include)." 40 | edit_terms "$collectionConfig/$APP_INCLUDE" "" "search" 41 | sync 42 | fi 43 | fi 44 | } 45 | 46 | create_collection_config() { 47 | local configFile="$collectionDir/$DEFAULT_CONFIG" 48 | 49 | cat << EOF > "$configFile" 50 | { 51 | "label": "$collectionName", 52 | "icon": "$DEFAULT_ICON", 53 | "iconsel": "$DEFAULT_ICON_SEL", 54 | "background": "$DEFAULT_BG", 55 | "themecolor": "f0b402", 56 | "launch": "$DEFAULT_LAUNCH", 57 | "rompath": "./$ROM_DIR", 58 | "imgpath": "./$IMG_DIR", 59 | "useswap": 0, 60 | "shortname": 0, 61 | "hidebios": 1, 62 | "extlist": "txt" 63 | } 64 | EOF 65 | } 66 | 67 | delete_collection() { 68 | notified=0 69 | if [ -d "$collectionDir" ]; then 70 | rm -r "$collectionDir" 71 | echo "Deleted: $collectionName" 72 | notified=1 73 | fi 74 | 75 | appCollDir="$APP_DIR/$CONFIG_DIR/$collectionName" 76 | if [ -d "$appCollDir" ]; then 77 | rm -r "$appCollDir" 78 | if [ "$notified" -eq 0 ]; then 79 | echo "Deleted: $collectionName" 80 | notified=1 81 | fi 82 | fi 83 | if [ "$notified" -eq 0 ]; then 84 | echo "$collectionName doesn't exist" 85 | notified=1 86 | fi 87 | } 88 | 89 | edit_terms() { 90 | local file="$1" 91 | local currentTerms="$2" 92 | local triggerSearch="$3" 93 | local fileName=$(basename "$file") 94 | local termsType="${fileName%.*}" 95 | 96 | echo "" 97 | echo "Provide a comma separated list of terms. Each term is analysed independently, IE, entering 'mario,pokemon' will include or exclude all games that contain the word 'mario' OR 'pokemon'." 98 | if [ "$#" -eq 2 ]; then 99 | echo "" 100 | echo "Current terms: $currentTerms" 101 | fi 102 | local searchTerms=$(get_user_input "Enter terms:") 103 | save_list_to_file "$searchTerms" "$file" 104 | sync 105 | local newTerms=$(get_list_from_file "$file") 106 | if [ "$triggerSearch" == "search" ]; then 107 | search_roms "$newTerms" "" 108 | show_edit_collection_menu 109 | else 110 | show_edit_terms_menu "$file" "$newTerms" 111 | fi 112 | } -------------------------------------------------------------------------------- /scripts/shortcuts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | create_shortcut() { 4 | local relativePath="$1" 5 | local shortcutName="$2" 6 | local exclusions="$3" 7 | 8 | local subDir="${relativePath%%/*}" 9 | local fileName=$(basename "$relativePath") 10 | 11 | local shortcutNameLower=$(lower_case "$shortcutName") 12 | 13 | local targetDir="$collectionDir/$ROM_DIR/$subDir" 14 | local targetTextFile="$targetDir/$shortcutName.txt" 15 | local targetImgFile="$collectionDir/$IMG_DIR/$shortcutName.png" 16 | 17 | local validEmuExt=$(get_json_value "$ROOT_DIR/$EMU_DIR/$subDir/$DEFAULT_CONFIG" "$EMU_EXT_KEY" "") 18 | 19 | local includeRom=1 20 | local excludeRom=0 21 | if grep -q "^$relativePath\$" "$addedFilesTemp"; then 22 | includeRom=0 23 | elif [[ $(has_valid_extension "$relativePath" "$validEmuExt") -eq 0 ]]; then 24 | includeRom=0 25 | elif grep -q "^$relativePath\$" "$excludedFilesTemp"; then 26 | includeRom=0 27 | excludeRom=1 28 | else 29 | oldIFS=$IFS 30 | IFS=',' 31 | set -- $exclusions 32 | while [ "$#" -gt 0 ]; do 33 | exclusion=$(echo "$1" | xargs) 34 | local exclusionLower="$(lower_case "$exclusion")" 35 | if [[ "$shortcutNameLower" == *"$exclusionLower"* ]]; then 36 | includeRom=0 37 | excludeRom=1 38 | echo "$relativePath" >> "$excludedFilesTemp" 39 | echo_trim "$relativePath" 53 " Excluding: " " ($exclusionLower)" 40 | break 41 | fi 42 | shift 43 | done 44 | IFS=$oldIFS 45 | fi 46 | 47 | if [[ $includeRom -eq 1 ]]; then 48 | mkdir -p "$targetDir" 49 | echo "$ROOT_DIR/$ROM_DIR/$relativePath" > "$targetTextFile" 50 | echo "$relativePath" >> "$addedFilesTemp" 51 | local imgFile="$ROOT_DIR/$IMG_DIR/${relativePath%.*}.png" 52 | if [ -f "$imgFile" ] && [ ! -f "$targetImgFile" ]; then 53 | cp "$imgFile" "$targetImgFile" 54 | fi 55 | elif [[ $excludeRom -eq 1 ]]; then 56 | if [ -f "$targetTextFile" ]; then 57 | rm "$targetTextFile" 58 | fi 59 | if [ -f "$targetImgFile" ]; then 60 | rm "$targetImgFile" 61 | fi 62 | fi 63 | } 64 | 65 | cleanup_collection() { 66 | echo "Cleaning up $collectionName collection..." 67 | remove_broken_shortcuts 68 | delete_empty_folders 69 | remove_redundant_images 70 | delete_cache 71 | } 72 | 73 | delete_cache() { 74 | cacheFile="$collectionDir/$ROM_DIR/$ROM_CACHE_FILE" 75 | if [ -f "$cacheFile" ]; then 76 | rm "$cacheFile" 77 | fi 78 | } 79 | 80 | remove_broken_shortcuts() { 81 | find "$collectionDir" -type f -name '*.txt' | while read -r file; do 82 | content=$(head -n 1 "$file") 83 | if [ ! -f "$content" ]; then 84 | rm "$file" 85 | local fileName=$(basename "$file") 86 | local imgFile="$collectionDir/$IMG_DIR/${fileName%.*}.png" 87 | if [ -f "$imgFile" ]; then 88 | rm "$imgFile" 89 | fi 90 | echo_trim "${file#*$ROM_DIR*}" 53 " Broken shortcut: ~" 91 | echo_trim "${content#*$ROM_DIR*}" 53 " Target: ~" 92 | fi 93 | done 94 | } 95 | 96 | delete_empty_folders() { 97 | find "$collectionDir" -type d -empty | while read -r dir; do 98 | rm -r "$dir" 99 | dirname=$(basename "$dir") 100 | echo_trim "$dirname" 53 " Empty folder: " 101 | done 102 | } 103 | 104 | remove_redundant_images() { 105 | tmpFile=$(mktemp) 106 | find "$collectionDir/$ROM_DIR" -type f -name '*.txt' -exec basename {} .txt \; > "$tmpFile" 107 | find "$collectionDir/$IMG_DIR" -type f -name '*.png' | while IFS= read -r img; do 108 | imgName=$(basename "$img" .png) 109 | if ! grep -Fxq "$imgName" "$tmpFile"; then 110 | rm "$img" 111 | echo_trim "$$imgName" 53 " Unlinked image: " ".png" 112 | fi 113 | done 114 | rm "$tmpFile" 115 | } 116 | 117 | delete_collection_shortcuts() { 118 | if [ -d "$collectionDir/$ROM_DIR" ]; then 119 | rm -rf "$collectionDir/$ROM_DIR/"* 120 | fi 121 | if [ -d "$collectionDir/$IMG_DIR" ]; then 122 | rm -rf "$collectionDir/$IMG_DIR/"* 123 | fi 124 | } -------------------------------------------------------------------------------- /scripts/bulk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | identify_valid_jobs() { 4 | validJobs=$(mktemp) 5 | request="$1" 6 | local configDir="$APP_DIR/$CONFIG_DIR" 7 | local osDir="$ROOT_DIR/$BEST_DIR" 8 | 9 | echo "" 10 | echo "Scanning collections..." 11 | echo "" 12 | 13 | local checkedCollections="" 14 | 15 | for appColl in "$configDir"/*; do 16 | if [ -d "$appColl" ]; then 17 | local appCollInclude="$appColl/$APP_INCLUDE" 18 | local appCollName=$(basename "$appColl") 19 | local osCollDir="$osDir/$appCollName" 20 | checkedCollections="$checkedCollections$appCollName\n" 21 | 22 | if [ "$request" == "delete" ]; then 23 | echo -e "$appCollName: ${GREEN}VALID${NO_FORMAT}" 24 | echo "$appCollName" >> "$validJobs" 25 | continue 26 | fi 27 | 28 | if [ ! -f "$appCollInclude" ]; then 29 | echo -e "$appCollName: ${RED}SEARCH TERM DEFINITIONS MISSING${NO_FORMAT}" 30 | continue 31 | fi 32 | 33 | if [ ! -s "$appCollInclude" ]; then 34 | echo -e "$appCollName: ${RED}NO SEARCH TERMS DEFINED${NO_FORMAT}" 35 | continue 36 | fi 37 | 38 | if [ ! -d "$osCollDir" ]; then 39 | echo -e "$appCollName: ${RED}COLLECTION DOESN'T EXIST${NO_FORMAT}" 40 | continue 41 | fi 42 | 43 | if [ ! -d "$osCollDir/$ROM_DIR" ] || [ ! -d "$osCollDir/$IMG_DIR" ] || [ ! -f "$osCollDir/$DEFAULT_CONFIG" ] || [ ! -f "$osCollDir/$DEFAULT_LAUNCH" ]; then 44 | echo -e "$appCollName: ${RED}COLLECTION MISSING REQUIRED FILES${NO_FORMAT}" 45 | continue 46 | fi 47 | 48 | echo -e "$appCollName: ${GREEN}VALID${NO_FORMAT}" 49 | echo "$appCollName" >> "$validJobs" 50 | fi 51 | done 52 | 53 | for osCollDir in "$osDir"/*; do 54 | if [ -d "$osCollDir" ]; then 55 | local osCollName=$(basename "$osCollDir") 56 | if echo -e "$checkedCollections" | grep -q -e "^$osCollName"; then 57 | continue 58 | fi 59 | appNameUpper=$(upper_case $APP_NAME) 60 | echo -e "$osCollName: ${RED}NOT DEFINED IN $appNameUpper${NO_FORMAT}" 61 | checkedCollections="$checkedCollections$appCollName\n" 62 | fi 63 | done 64 | local totalChecked=$(echo -e "$checkedCollections" | wc -l) 65 | local totalValid=$(wc -l < "$validJobs") 66 | totalChecked=$((totalChecked - 1)) 67 | totalValid=$((totalValid)) 68 | totalInvalid=$((totalChecked-totalValid)) 69 | echo "" 70 | if [ "$totalChecked" -eq 0 ]; then 71 | echo -e "RESULTS: ${RED}No collections found${NO_FORMAT}" 72 | else 73 | echo -e "RESULTS: $totalChecked|${GREEN}$totalValid${NO_FORMAT}|${RED}$totalInvalid${NO_FORMAT}" 74 | fi 75 | } 76 | 77 | refresh_all_collections() { 78 | regen="$1" 79 | validJobs=$(mktemp) 80 | identify_valid_jobs 81 | 82 | echo "" 83 | local totalValid=$(wc -l < "$validJobs") 84 | 85 | if [ "$totalValid" -eq 0 ]; then 86 | echo "No valid collections found" 87 | show_main_menu 88 | return 0 89 | fi 90 | 91 | local validJobString=$(get_list_from_file "$validJobs") 92 | if [ "$regen" == "regen" ]; then 93 | echo "Regenerating a collection will remove any shortcuts you've manually added to a collection." 94 | echo "$APP_NAME will only regenerate collections it has created or has been linked to, the below collections have been identified for regeneration." 95 | echo "" 96 | echo "$validJobString" 97 | local choice=$(get_user_input "Proceed y/[n]:" "n") 98 | case "$choice" in 99 | y) continue ;; 100 | *) show_main_menu; rm -f "$validJobs"; return 0 ;; 101 | esac 102 | fi 103 | 104 | while IFS= read -r job; do 105 | if [ -n "$job" ]; then 106 | set_collection "$job" 107 | if [ "$regen" == "regen" ]; then 108 | delete_collection_shortcuts 109 | fi 110 | execute_refresh "$job" 111 | if [ "$regen" != "regen" ]; then 112 | cleanup_collection 113 | fi 114 | fi 115 | done < "$validJobs" 116 | rm -f "$validJobs" 117 | show_main_menu 118 | } 119 | 120 | execute_refresh() { 121 | local job="$1" 122 | inc="$(get_list_from_file "$APP_DIR/$CONFIG_DIR/$collectionName/$APP_INCLUDE")" 123 | exc="$(get_list_from_file "$APP_DIR/$CONFIG_DIR/$collectionName/$APP_EXCLUDE")" 124 | search_roms "$inc" "$exc" 125 | } 126 | 127 | delete_all_collections() { 128 | validJobs=$(mktemp) 129 | identify_valid_jobs "delete" 130 | 131 | echo "" 132 | local totalValid=$(wc -l < "$validJobs") 133 | 134 | if [ "$totalValid" -eq 0 ]; then 135 | echo "No valid collections found" 136 | show_main_menu 137 | return 0 138 | fi 139 | 140 | local validJobString=$(get_list_from_file "$validJobs") 141 | echo "$APP_NAME will only delete collections it has created or has been linked to, the below collections have been identified for deletion." 142 | echo "" 143 | echo "$validJobString" 144 | local choice=$(get_user_input "Proceed y/[n]:" "n") 145 | case "$choice" in 146 | y) execute_deletions "$validJobs" ;; 147 | *) show_main_menu ;; 148 | esac 149 | } 150 | 151 | execute_deletions() { 152 | local jobs_file="$1" 153 | echo "" 154 | while IFS= read -r job; do 155 | if [ -n "$job" ]; then 156 | set_collection "$job" 157 | delete_collection 158 | fi 159 | done < "$jobs_file" 160 | show_main_menu 161 | rm -f "$validJobs" 162 | } -------------------------------------------------------------------------------- /scripts/menus.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | show_help() { 4 | if [ "$#" -ne 0 ]; then 5 | echo "" 6 | fi 7 | echo "$APP_NAME v$VERSION" 8 | echo "" 9 | echo "Y: Toggle keyboard" 10 | echo "X: Reposition keyboard" 11 | echo "B: BACKSPACE" 12 | echo "HOLD-L1: SHIFT" 13 | echo "R1: Lock key (Lock SHIFT for CAPS LOCK)" 14 | echo "START: Enter/Submit" 15 | echo "MENU: Exit app (with keyboard visible)" 16 | } 17 | 18 | show_main_menu() { 19 | local bestTabName=$(get_json_value "$langFile" "$LANG_BEST_TAB_KEY") 20 | echo "" 21 | echo "Select an option:" 22 | echo " 1: Create collection" 23 | echo " 2: Edit/Delete collection" 24 | echo " 3: Refresh all collections (append)" 25 | echo " 4: Regenerate all collections (reset and rebuild)" 26 | echo " 5: Delete all collections" 27 | echo " 6: Rename '$bestTabName' tab" 28 | echo "" 29 | echo " 0: Show help" 30 | local choice=$(get_user_input "Enter your choice:") 31 | case "$choice" in 32 | 1) create_collection ;; 33 | 2) show_collections_menu ;; 34 | 3) refresh_all_collections ;; 35 | 4) refresh_all_collections "regen" ;; 36 | 5) delete_all_collections ;; 37 | 6) show_rename_best_tab_menu "$bestTabName" ;; 38 | 0) show_help -nl; show_main_menu ;; 39 | *) echo "Invalid selection, please try again."; show_main_menu ;; 40 | esac 41 | } 42 | 43 | show_collections_menu() { 44 | local directory="$ROOT_DIR/$BEST_DIR" 45 | 46 | echo "" 47 | echo "Select a collection:" 48 | local i=1 49 | local selected="" 50 | 51 | for dir in "$directory"/*/; do 52 | if [ -d "$dir" ]; then 53 | echo " $i: $(basename "$dir")" 54 | eval "dir_$i=\"$(basename "$dir")\"" 55 | i=$((i+1)) 56 | fi 57 | done 58 | 59 | if [ "$i" -eq 1 ]; then 60 | echo "No existing collections found." 61 | show_main_menu 62 | fi 63 | 64 | echo "" 65 | echo " 0: Return to main menu" 66 | 67 | local choice=$(get_user_input "Enter your choice:") 68 | if [ "$choice" -ge 0 ] && [ "$choice" -lt "$i" ]; then 69 | if [ "$choice" -eq 0 ]; then 70 | show_main_menu 71 | else 72 | eval "selected=\$dir_$choice" 73 | set_collection "$selected" 74 | show_edit_collection_menu 75 | fi 76 | else 77 | echo "Invalid selection, please try again." 78 | show_collections_menu 79 | fi 80 | } 81 | 82 | show_edit_collection_menu() { 83 | 84 | local includeFile="$APP_DIR/$CONFIG_DIR/$collectionName/$APP_INCLUDE" 85 | local excludeFile="$APP_DIR/$CONFIG_DIR/$collectionName/$APP_EXCLUDE" 86 | local existingTerms="" 87 | local existingExclusions="" 88 | 89 | echo "" 90 | echo "Editing collection: $collectionName" 91 | 92 | if [ -f "$includeFile" ]; then 93 | existingTerms="$(get_list_from_file "$includeFile")" 94 | echo "Existing search terms: $existingTerms" 95 | fi 96 | 97 | if [ -f "$excludeFile" ]; then 98 | existingExclusions="$(get_list_from_file "$excludeFile")" 99 | echo "Existing exclusion terms: $existingExclusions" 100 | fi 101 | 102 | echo "" 103 | echo "Select an option:" 104 | echo " 1: Edit inclusion terms" 105 | echo " 2: Edit exclusion terms" 106 | echo " 3: Refresh collection (append)" 107 | echo " 4: Regenerate collection (reset and rebuild)" 108 | echo " 5: Delete collection" 109 | echo "" 110 | echo " 9: Return to collections menu" 111 | echo " 0: Return to main menu" 112 | local choice=$(get_user_input "Enter your choice:") 113 | case "$choice" in 114 | 1) show_edit_terms_menu "$includeFile" "$existingTerms" ;; 115 | 2) show_edit_terms_menu "$excludeFile" "$existingExclusions" ;; 116 | 3) search_roms "$existingTerms" "$existingExclusions"; cleanup_collection; show_edit_collection_menu ;; 117 | 4) delete_collection_shortcuts; search_roms "$existingTerms" "$existingExclusions"; show_edit_collection_menu ;; 118 | 5) echo ""; delete_collection; show_main_menu ;; 119 | 9) show_collections_menu ;; 120 | 0) show_main_menu ;; 121 | *) echo "Invalid selection, please try again."; show_edit_collection_menu ;; 122 | esac 123 | } 124 | 125 | show_edit_terms_menu() { 126 | local file="$1" 127 | local currentTerms="$2" 128 | local fileName=$(basename "$1") 129 | local termsType="${fileName%.*}" 130 | 131 | echo "" 132 | echo "Editing collection: $collectionName" 133 | echo "Existing $termsType terms: $currentTerms" 134 | 135 | echo "" 136 | echo "Select an option:" 137 | echo " 1: Reset term(s)" 138 | echo " 2: Add term(s)" 139 | echo " 3: Remove term(s)" 140 | echo "" 141 | echo " 8: Return to $collectionName collection menu" 142 | echo " 9: Return to collections menu" 143 | echo " 0: Return to main menu" 144 | local choice=$(get_user_input "Enter your choice:") 145 | case "$choice" in 146 | 1) delete_file_content "$file"; edit_terms "$file" ;; 147 | 2) edit_terms "$file" "$currentTerms" ;; 148 | 3) show_remove_terms_menu "$file" ;; 149 | 8) show_edit_collection_menu ;; 150 | 9) show_collections_menu ;; 151 | 0) show_main_menu ;; 152 | *) echo "Invalid selection, please try again."; show_edit_terms_menu "$file" "$currentTerms" ;; 153 | esac 154 | } 155 | 156 | 157 | show_remove_terms_menu() { 158 | local file="$1" 159 | 160 | if [ ! -s "$file" ]; then 161 | echo "No existing terms found." 162 | show_edit_terms_menu "$file" "" 163 | return 1 164 | fi 165 | 166 | echo "" 167 | echo "Select a term to delete:" 168 | nl -w2 -s': ' "$file" | sed 's/^/ /' 169 | echo "" 170 | echo " 0: Cancel" 171 | 172 | local term_count=$(wc -l < "$file") 173 | 174 | local choice=$(get_user_input "Enter your choice:") 175 | if [ "$choice" -ge 0 ] 2>/dev/null && [ "$choice" -le "$term_count" ]; then 176 | if [ "$choice" -eq 0 ]; then 177 | local readTerms=$(get_list_from_file "$file") 178 | show_edit_terms_menu "$file" "$readTerms" 179 | else 180 | sed -i "${choice}d" "$file" 181 | show_remove_terms_menu "$file" 182 | fi 183 | else 184 | echo "Invalid selection, please try again." 185 | show_remove_terms_menu "$file" 186 | fi 187 | } 188 | 189 | show_rename_best_tab_menu() { 190 | local currentName="$1" 191 | echo "" 192 | echo "Current Tab Name: $currentName" 193 | echo "" 194 | echo "Select an option:" 195 | echo " 1: Rename to 'Collections'" 196 | echo " 2: Rename to 'Best'" 197 | echo " 3: Enter custom name" 198 | echo "" 199 | echo " 0: Return to main menu" 200 | local choice=$(get_user_input "Enter your choice:") 201 | case "$choice" in 202 | 1) set_json_value "$langFile" "$LANG_BEST_TAB_KEY" "Collections" ;; 203 | 2) set_json_value "$langFile" "$LANG_BEST_TAB_KEY" "Best" ;; 204 | 3) local newName=$(get_user_input "Enter a custom name:"); set_json_value "$langFile" "$LANG_BEST_TAB_KEY" "$newName" ;; 205 | 0) show_main_menu ;; 206 | *) echo "Invalid selection, please try again."; show_rename_best_tab_menu "$currentName" ;; 207 | esac 208 | show_main_menu 209 | } --------------------------------------------------------------------------------